zl程序教程

您现在的位置是:首页 >  其他

当前栏目

工具说明书 - Git: stash & clean

amp工具Git Clean 说明书 stash
2023-09-11 14:22:08 时间

经常性的,我们的开发工作专注于项目的某一部分,而各种事情还处于混乱状态,这时你想切换个分支干点其他事。问题是,现在的工作还没做完,还不能提交到本地或者远程仓库,一会还得切回这个状态继续干。这种问题的解决方法就是使用“git stash”命令。

Stashing带走工作目录的dirty state,就是那些modified tracked files and staged changes。

这些未完成的修改被保存到一个stack中,你可以在随时恢复,不限于特定branch。

使用这个命令,把当前的working directory修改和index的内容保存下来,回到HEAD commit。

所以stash命令和已经commit的内容是无关的,无论是本地还是远程仓库。

stash命令将当前工作状态(Working directory and index state WIP)保存,不会产生conflict。

可以使用help选项查看git stash的帮助信息:

$git stash --help

Stashing Your Work

让我们来演示一下stashing操作,现在你要进入一个项目开始工作,修改一些文件,然后缓存某一个修改。

如果执行git status,就会看到当前dirty state (反之为clean state):

$ git status

Changes to be committed:

  (use "git reset HEAD <file>..." to unstage)

        modified:   index.html

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   lib/simplegit.rb

现在你想切换分支,但不想提交代码,就可以stash这些变更。在stack推送一个新的stash,执行git stash 或者git stash push:

$ git stash

Saved working directory and index state \

  "WIP on master: 049d078 Create index file"

HEAD is now at 049d078 Create index file

(To restore them type "git stash apply")

现在看到你的工作目录是无变化的:

$ git status

# On branch master

nothing to commit, working directory clean

现在就能切换分支去干其他的,你的那些更改存到stack里了。想看你都存了那些更改,使用git stash list:

$ git stash list

stash@{0}: WIP on master: 049d078 Create index file

stash@{1}: WIP on master: c264051 Revert "Add file_size"

stash@{2}: WIP on master: 21d80a5 Add number to log

在本例中,前面已经存了2个stashes,你可以操作这三个不同的保存下来的变更。

最新的stash存在index 0,每进行一次stash操作,stash列表顺序后移。

你可以使用git stash apply,来直接恢复你最新stash的变更。

如果想恢复早先的变更,可以通过名字指定某一个,比如:git stash apply stash@{2}。

如果你不指定,git默认就是用最新的stash直接恢复:

$ git stash apply

On branch master

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   index.html

        modified:   lib/simplegit.rb

no changes added to commit (use "git add" and/or "git commit -a")

在Git 2.11版本以后,可以直接使用数字来表示stash的索引。

$ git stash drop 1

$ git stash pop 1

$ git stash apply 1

$ git stash show 1

在数字前面加上--index也可以:

$ git stash pop --index 1

git stash apply后,你会发现你保存下来的变更被恢复。

这种情况,你在你的干净的工作目录上执行恢复操作,而且这个branch就是你之前保存变更的那个。

但这两个条件不是必须的,分支可以切换,工作目录也可以有已修改的和未提交的文件,不影响执行apply a stash。

注意,git stash操作本质上也是一次临时commit,有commit id号,自然也记录下了每个文件的新旧File index变化。

在Git stash apply时,如果stash里某个文件有变更,而工作目录里这个文件也有变更,是不能成功stash apply或pop的。

要把工作目录里的文件先git add一下,加到暂存区里,或者commit也可以。

在git stash apply时,针对文件的不同状态,会产生不同的行为。

拿某一个文件来说,修改后执行stash缓存,本地无更改,然后stash apply,就会直接merge。因为stash这个临时commit里,此文件有两个File Index,一个是旧的,一个是新的。而本地工作目录中此文件的最新提交,也有两个File Index,一个旧的,一个新的。Stash里的旧的File Index和本地的新的File Index是一致的,所以文件的变化状态是连续的,文件的File Index也是连续的,直接执行了更新操作。

如果此文件被修改并执行stash缓存操作,本地文件也进行了修改。那此文件工作目录下的最新提交的旧File Index和Stash中的旧File Index一致,但新的File Index不同。这是stash apply,会执行auto merge操作,相当于基于同一个File Index进行了两次修改,然后合并两个修改,如果出现冲突,就要手动处理。

如果此文件被修改并执行stash缓存操作,而本地文件进行了多次修改或者切换了工作分支,这时stash缓存中的此文件的旧File Index和此文件本地工作目录中的最新提交的新旧File Index都不同,这是就会出现Merge Conflict,就需要手动merge。因为同一个文件,无法merge一个没有关联的修改。这时git就会在文件里把两个版本的文件内容都显示出来,并告诉用户出现conflict,需要用户自己手动处理。

恢复修改时,如果是已经执行过git add,进入了staged状态的修改会回退,如果要保留staged的修改,需要加上--index选项。这样执行git stash apply之后,就会和初始状态一致。

就是说,工作目录的改动和index内的改动(git add后)在被stash之后,再apply或者pop,就都会被还原成工作目录中的改动,如果要保留index的改动不被回退到index中,就要加上这个选项。

$ git stash apply --index

On branch master

Changes to be committed:

  (use "git reset HEAD <file>..." to unstage)

        modified:   index.html

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   lib/simplegit.rb

使用apply命令恢复stack上保存的工作,这个保存的修改还继续存在,要把这个修改从stack上删除,要执行git stash drop:

$ git stash list

stash@{0}: WIP on master: 049d078 Create index file

stash@{1}: WIP on master: c264051 Revert "Add file_size"

stash@{2}: WIP on master: 21d80a5 Add number to log

$ git stash drop stash@{0}

Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

还有一种方法,执行git stash pop,会恢复变更,然后立刻将这个变更删除。

如果在git stash pop出现conflict时,不会自动删除这个stash,要再自己手动drop stash。

出现的conflict自己修改,conflict解决后,再执行相关的git操作,add、commit、push等。

Creative Stashing (多种stash用法)

还有一些其他有用的stash使用方式。

第一个常用的选项是 --keep-index,在使用git stash命令时使用。

这个选项的作用是告诉git,不要把index里的内容加到stash里。

也就是如果你git add(stage)了一个修改的文件后,再执行git stash --keep-index,则这个修改不会被stash。

$ git status -s

M  index.html

M lib/simplegit.rb

$ git stash --keep-index

Saved working directory and index state WIP on master: 1b65b17 added the index file

HEAD is now at 1b65b17 added the index file

$ git status -s

M  index.html

另外一个常用的功能是stash时也包含未跟踪的文件。默认情况下,git stash只暂存修改的和staged的跟踪(tracked)文件。如果加上选项--include-untracked或者-u,git stash时会将未跟踪文件也暂存起来。但是,这时仍是未包含ignored(忽略)文件。若要包含ingnored文件,需要使用选项 --all (或者就用-a)。

$ git status -s

M  index.html

M lib/simplegit.rb

?? new-file.txt

$ git stash -u

Saved working directory and index state WIP on master: 1b65b17 added the index file

HEAD is now at 1b65b17 added the index file

$ git status -s

$

最后,如果你使用了--patch标记,git stash就什么内容都不会暂存,而是进入一种交互模式,让你选择哪些变更要暂存,哪些变更继续保留在工作目录。

$ git stash --patch

diff --git a/lib/simplegit.rb b/lib/simplegit.rb

index 66d332e..8bb5674 100644

--- a/lib/simplegit.rb

+++ b/lib/simplegit.rb

@@ -16,6 +16,10 @@ class SimpleGit

         return `#{git_cmd} 2>&1`.chomp

       end

     end

+

+    def show(treeish = 'master')

+      command("git show #{treeish}")

+    end

end

test

Stash this hunk [y,n,q,a,d,/,e,?]? y

Saved working directory and index state WIP on master: 1b65b17 added the index file

Creating a Branch from a Stash (根据stash来创建分支)

将本地修改stash后,有时pop时会出现merge conflict,这是一个坏消息,为了避免这种情况,我们最好能恢复到当时创建stash时的环境。

可以使用git stash branch <new branchname>, 直接根据当时的状态创建出一个新的branch,然后再自动执行git stash pop。

$ git stash branch <branchname> [<stash>]

Create并checkout一个新的branch,是基于指定stash创建的commit ID。如果不指定stash,则apply最新的stash,然后创建一个分支,并drop最新的stash。

此操作会保留当前的stash reflog,只会删除创建branch所使用的stash,不会删除其他的stash。

$ git stash branch testchanges

M       index.html

M       lib/simplegit.rb

Switched to a new branch 'testchanges'

On branch testchanges

Changes to be committed:

  (use "git reset HEAD <file>..." to unstage)

        modified:   index.html

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   lib/simplegit.rb

Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

其他

Stash Push详细说明

把本地的修改(包含了working tree和index里的)存储到一个新的stash entry,然后回滚当前状态到HEAD。

$ git stash push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>…]

-p 是挨个hunk确认(Interactively / 交互式操作)是否stage。各个字母的提示输入“?”可以显示帮助说明。类似git add --patch。

-a 是包含了ignore的file

-- 此符号接路径或文件参数,指定需要stash的文件或路径。The new stash entry records the modified states only for the files that match the pathspec.

-q  静默模式,不显示提示信息。

quiet,suppress feedback messages。

--pathspec-from-file=<file>

指定文件作为文件或路径(pathspec)输入源,文件内容使用LF或CR/LF分隔,里面每行内容是要stash的文件或文件夹。

修改描述信息

如果直接使用git stash命令,存储的stash的描述信息就是当前最新的commit的commit ID加上log信息。

这样阅读起来很不方便,最好使用-m来加入定制的描述信息。

$ git stash push -m "My description."

如果已经加入stash了,要改名就比较麻烦,可以先drop,然后再push -m加入。

Stash的存储:

git stash操作,本质是一次commit操作,有commit id,存放在 refs/stash。

$ cat  .git/refs/stash

dbda2ec3a585c5be99500c021d8a0d564a1f3dce

上面命令显示的就是最新一次stash push操作的commit ID。

如果做了多次stash操作,则旧的stash存在logs里:

$ cat  .git/logs/refs/stash

0000000000000000000000000000000000000000 456a5762e3e30dba7604d7bf49ea7e36c88a6575 username <user@abc.com> 1616053132 +0800    WIP on lark_tmp: 7838a408c Change illumination to less bright

456a5762e3e30dba7604d7bf49ea7e36c88a6575 ce59c24b9fa358e5fc885333e7af64eed7f10177 username <user@abc.com> 1616053150 +0800    WIP on lark_tmp: 7838a408c Change illumination to less bright

一个stash entry表示一个commit 提交,记录了工作目录的状态( records the state of the working tree)。

查看

可以使用git stash show查看具体内容:

$ git stash show

CMakeLists.txt                                     |    5 +-

libs/BT_Stack/CMakeLists.txt                       |   14 -

libs/BT_Stack/ReleaseNote.txt                      |   96 --

libs/BT_Stack/include/addflags.h                   |  493 -------

libs/BT_Stack/include/blueapi.h                    | 1383 --------------------

$ git stash show [<diff-options>] [<stash>]

git stash show -p stash@{1}

默认显示diffstat,几个文件修改,改了几处(包含插入和删除各几处)。

加了-p显示具体内容。

option就是git diff命令可以使用的。

查看有几条stash entry:

$ git stash list [<log-options>]

可以使用:

git stash list -2, 显示最近的两条stash的entry。

Stash操作的意义

第一种情况,向仓库拉取最新状态到本地工作目录(Pulling into a dirty tree).

Git的pull规则,如果远程仓库的最新提交修改了某个文件,而你本地工作目录也修改了这个文件,那是没法pull成功的。

这时有两个选择:

a. 将本地改动加入到staging area或index中,然后commit提交。这时再pull,就会进行merge,如果有冲突或其他问题会提示,可能需要手动操作。

b.将本地改动使用stash暂存下来,把这些改动放到一边,然后git pull远程仓库代码,完成后执行stash pop操作,如果有用冲突或其他问题会提示,可能需要手动操作。

第二种情况,被中断的工作状态(Interrupted workflow)。

事情做到一半存下来即可。这样可以不用提交就能直接切换分支。

另一个分支事情做完了再切回来,然后stash pop就回到最初状态了。

删除stash

$ git stash pop [--index] [-q|--quiet] [<stash>]

删除某个单独的暂存状态,并将其应用到当前的工作树状态上。

是git stash push的反操作,inverse operation。

--index表示index里面的文件状态保留。

stash参数不指定默认就是stash@{0}, 使用stash@{<revision>}

如果恢复时出现冲突,需要手动处理冲突,然后手动 git stash drop删除stash。

$ git stash clear

删除所有的stash。

$ git stash drop [-q|--quiet] [<stash>]

Remove a single stash entry from the list of stash entries. 

删除某个stash。如果不指定就删除最新的。

注:

1. git stash save已经不推荐使用,建议使用git stash push。

因为push命令后面能跟路径/文件参数,而save命令不支持。

2. git stash不加参数相当于git stash push

3 . 恢复(restore) 一个stash:

#git stash apply // 应用但不删除

#git stash pop // 应用并删除

但pop命令只能使用git stash暂存的stash,但apply可以使用各种commit,比如使用git stash push和git stash create的。

4. Stash的名字: WIP on branchname

WIP: work-in-progress

5. stash@{0}是最新创建的,1是前一个。

所以引用stash可以使用序号,比如序号n,就是stash@{n}

==================================== 

Cleaning your Working Directory

上面讲解了git stash命令,用来保存那些你想保留的修改。但另外一种情况,有种可能你想清除所有改动,回到HEAD状态。

怎么办?使用git clean。

在Git仓库中,可能有各种操作,比如代码merge,外部工具对文件进行需改,还有编译时产生的中间文件。

使用Git clean就可以把这些不属于Git仓库,没有跟踪的文件删除掉。

$ git clean -f -d 

-f选项表示“force”,“really do this”。 如果git配置里,clean.requireForce,这个是true的话,git clean必须带-f才能操作。

-d选项表示对文件夹遍历。

如果你对clean操作不放心,也可以先尝试下看有哪些文件会被删除,可以加上选项--dry-run或者-n。

$ git clean -d -n

Would remove test.o

Would remove tmp/

默认情况下git clean 只会删除未跟踪的文件,而不会删除忽略的文件。

如果你要删除这些没加入git的文件,比如一些编译过程中的.o文件,需要加上-x选项。

$ git status -s

M lib/simplegit.rb

?? build.TMP

?? tmp/

$ git clean -n -d

Would remove build.TMP

Would remove tmp/

$ git clean -n -d -x

Would remove build.TMP

Would remove test.o

Would remove tmp/

除了-f直接删除,-n显示要删除,还有-i选项,是个interactive flag。

$ git clean -x -i

Would remove the following items:

  build.TMP  test.o

*** Commands ***

    1: clean                2: filter by pattern    3: select by numbers    4: ask each             5: quit

    6: help

What now>

这种方式会每个文件单独确认,由用户来确认选择何种操作。

参考:

Git - Stashing and Cleaning

Git - git-stash Documentation