使用 git 制作工作目录的快照
Make a snapshot of working directory with git
我有时需要为当前(可能是脏的)工作目录做一个快照。 git stash save -u
和我需要的非常相似,但是有两个问题:
- 我希望我的工作目录保持相同状态(保持未跟踪文件未跟踪)
- 万一我需要回到保存的状态(可能一个月后),
git stash apply
就不容易了,因为我需要先找到git stash
之前的状态。
我目前有以下适合我的命令序列,但我想知道是否有更优雅的方法。
# on branch master
git add .
git commit -m "Temporary commit on the original branch"
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss HEAD~
git merge master
git checkout master
git reset HEAD^
谢谢大家的解答和解释!我将主要根据@XavierGuihot 的回答做类似的事情
git stash -u # -u to include untracked files
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss
git stash apply
git add --all
git commit -m "Snapshot"
git checkout master
git stash pop --index # --index to recover the state of indexed files
您可以存储更改、创建新分支、应用更改、仅在新分支上执行提交,然后在最终再次应用更改之前再次检出 master。
git stash
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss
git stash apply
git add --all
git commit -m "Snapshot"
git checkout master
git stash pop
没有工作树变动的最有效方法是直接使用核心命令执行此操作,假设您没有任何飞行中的合并冲突或索引中的 intent-to-add 标记它是
statenow=`git write-tree`
stashedindex=`git commit-tree -p @ -m "index snap" $statenow`
git add -f .
git tag snap-`date +%Y-%m-%dT%H.%M.%S` \
$(git commit-tree -p @ -p $stashedindex \
-m "worktree snap" \
$(git write-tree)
)
git read-tree $statenow
但如果您不关心被忽略的文件或 no-effect 工作树变动,最简单的方法是
git stash -a -u
git tag snap-`date +%Y-%m-%dT%H.%M.%S stash
git stash pop
然后对于任何一个,恢复你所做的状态,例如
git clean -dfx
git checkout snap-2018-02-18T14.19.15 # to move HEAD + worktree there
# or
git read-tree -um @ snap-2018-02-18T14.19.15 # just the worktree
git read-tree @ snap-2018-02-18T14.19.15^2 # then restore the index
TL;DR
使用 ,这是他在我写下面的长答案之间离开键盘时写的。
长
在你决定这个问题的任何一个答案之前(参见 我认为在某种意义上是 "the best" 的集合),考虑一下:每当你有一个状态不是只是 "everything tracked in the work-tree exactly matches HEAD
",每个文件有 三个 个版本需要担心。
也就是说,当你第一次 运行:
git clone <url>
或者,从一个完全干净的(没有未跟踪的文件,没有修改的文件等)状态做:
git checkout <somebranch>
你从每个文件的三个副本开始,例如README
和Makefile
等等,现在可用:
HEAD
中的一个(您签出的任何分支的提示提交):这个是read-only,当然与HEAD
中的匹配,因为它 是 HEAD
中的那个。此 HEAD:README
文件以 Git 使用的特殊 Git-only 格式存储。 (使用git show HEAD:README
可以看到。)
索引中的一个。索引是您将构建下一次提交的地方,但现在,它只包含 current 提交中所有内容的副本。所以 :0:README
——你可以使用 git show :0:README
查看这个副本——完全匹配 HEAD:README
。这个额外的副本也以特殊的 Git-only 格式存储,这意味着它基本上不需要 space。 :0:README
和 HEAD:README
的区别是你可以 覆盖 这个: git add README
复制 work-tree README
到:0:README
,例如。 (你在这里制作的副本将占用一些 space,但随后将进入下一个 git commit
,之后他们将共享冻结/read-only 版本,直到你复制另一个。)
每个文件的最后一个副本,如README
,在work-tree中。该文件采用其正常的日常格式,因此所有程序都可以读取和写入它。不需要用git show
查看,因为它只是一个普通文件!
最初,所有三个版本都匹配,并且没有未跟踪的文件。
所以:
I sometimes need to make a snapshot of the current (possibly dirty) working directory.
对于路径为 P
的每个文件,除了 untracked 文件,我们有:
- HEAD 提交中
P
的版本:您永远不需要保存这个版本,因为它已经通过提交永久保存;
- 索引中
P
的版本:要保存这个吗?;和
P
的版本在 work-tree 中:你无疑想要保存这个。
这也留下了如何处理 未跟踪 文件的问题。
让我们在这里注意,未跟踪的文件只是索引中不存在的 work-tree 中的文件。 (这包括 HEAD
中但您小心地 从 索引中删除的文件——它们当前未被跟踪,只要该文件的 work-tree 版本存在。)
git stash save -u
is very similar to what I need ...
这是一个很好的线索,因为 git stash save -u
节省了:
- 当前索引(作为一次提交);
- 当前work-tree(作为另一个提交,仅跟踪文件);和
- 未跟踪的文件(作为第三次提交)。请注意,第三次提交省略了 untracked-and-ignored 的文件(不存在既被跟踪又被忽略的文件;"ignored" 仅适用于已经未跟踪的文件)。如果您还想要忽略的文件,则必须在此处使用
-a
而不是 -u
。
... but there are two problems: [1. git stash -u
removes the untracked files after committing them, and 2. git stash -u
makes it rather hard to un-stash again later]
请注意 git stash -u
只需 运行s git clean
即可删除未跟踪的文件。您必须执行 git reset --hard && git clean -df
才能返回到 unstash 状态(但请参阅我在下面提到的问题案例)。
现在,进行提交(任何 提交)的主要问题是您通过将文件复制到索引中来完成。但我们刚刚注意到,可能存在各种文件的索引版本,例如 :0:README
和 :0:path/to/important.data
,它们不同于 HEAD:
对应的 和 work-tree同行。如果您要保存 work-tree 个副本,则必须通过覆盖索引副本来完成!
如果这没问题,您可能有一条比使用 git stash
或等效方法更简单的前进道路。但是您仍然遇到未跟踪文件的问题!如果不行,你必须先保存索引,就像 git stash
一样,在这种情况下你可能只想使用 git stash
.
我们在上面已经注意到,未跟踪的文件是 work-tree 中的文件——有一些路径 U——但不在索引中:没有 :0:<em>U</em>
。这会产生一些问题:要保存这些文件,我们必须将它们复制到索引中。当然,这会破坏(覆盖)我们精心准备的与 HEAD
和 work-tree 版本不同的任何内容。这就是所有并发症的来源。
如果您确实出于任何原因想要保留索引状态,和 索引状态会记录以后应该跟踪和取消跟踪哪些文件,那么我们有我们的解决方案(即jthill 的解决方案也是如此),这很像 git stash
,但略有修改:
- 写出当前索引状态:
git write-tree
.
- 使用结果进行提交(永久直到它没有名字,当它可以被垃圾收集时):
git commit-tree
。此提交可以将当前提交(HEAD
或 @
)作为其父项,尽管实际上并不需要。
- 将所有未跟踪的文件(可能包括被忽略的文件)添加到索引:
git add -A
或 git add -f -A
等,具体取决于您在第二次提交中想要的内容。
- 写入这个更新的索引,然后使用结果进行第二次提交,其父项是保存的索引状态,并为第二次提交命名以使其永久化。 (在 jthill 的回答中,就像
git stash
所做的那样,他将第二个提交存储为父项,索引提交作为第二个提交的第二个父项。这迫使我们使用后缀 ^2
符号稍后,它的优点是它可以与 git stash
脚本一起使用。)
写完这些后,我们必须立即将索引恢复到原来的状态——我们在第一步中保存的那个。否则所有 previously-untracked 文件现在都是跟踪文件!
要恢复这些东西之一,我们遇到了与 git stash save -u
相同的问题:我们进行的第二次提交中的文件(用于保存未跟踪的文件)将至少暂时变为, 跟踪 个文件。如果现在 work-tree 中有同名文件,Git 将非常不愿意覆盖它们——所以我们需要 git clean -df
或 git clean -dfx
来销毁它们。这里有一点问题,因为这将 也 删除第二次提交中 没有 的文件:例如,假设当你保存所有内容时,有一个名为 important-1
的未跟踪文件,但没有任何名为 important-2
的文件。 现在有一个important-2
。
如果您现在天真地 运行 git clean -df
或 git clean -dfx
,Git 将删除 both 这些未跟踪的 important-*
文件。然后我们将指示 Git 从第二次提交中提取所有文件,包括 previously-untracked important-1
。 Git 会将文件复制到索引和 work-tree 中。由于没有保存 important-2
,Git 不会 复制该文件。
这是使用比较大的缺陷:
git clean -dfx
git checkout snap-2018-02-18T14.19.15
这就是为什么:
git read-tree -um @ snap-2018-02-18T14.19.15
git read-tree @ snap-2018-02-18T14.19.15^2
更好。第一步,git read-tree -um @ snap-...
,执行 merge-and-update 将我们所做的第二次提交(保持所有 work-tree 状态)引入索引并更新 work-tree。这样important-2
就不会被破坏
之后需要第二步来修复索引,因为从第二次提交中读取所有那些未跟踪的文件会导致它们成为 已跟踪 文件。我们希望将索引恢复到制作快照时的状态,或者至少从索引中拉出 out 现在不应该在其中的所有文件它。
我们有确切的索引状态:它在我们所做的第一个提交中,即snap-...^2
(快照或存储的第二个父级)。我们可以直接将其读入索引:
git read-tree snap-2018-02-18T14.19.15^2
(注意此处缺少 @
/ HEAD
),或者执行 two-tree 合并以尽可能保留我们对索引所做的修改:
git read-tree @ snap-2018-02-18T14.19.15^2
请注意,我们可以只重置索引以匹配您当前所在的提交:
git reset HEAD
或者可能将其重置为保存的第一个提交的父级:
git read-tree snap-2018-02-18T14.19.15^1
如果您真的不想保存索引状态。无论哪种方式,未跟踪的文件再次未跟踪,因为它们不再在索引中。
另一种简洁明了的方法。即使没有分支更改也不需要 stash
:
git commit
git branch name-of-my-snapshot-branch
git reset HEAD^
使用 commit
只需提交您想要快照的所有内容,以您喜欢的任何方式执行此操作。
reset
将您的分支指向之前的提交,因此您回到了起点。
我有时需要为当前(可能是脏的)工作目录做一个快照。 git stash save -u
和我需要的非常相似,但是有两个问题:
- 我希望我的工作目录保持相同状态(保持未跟踪文件未跟踪)
- 万一我需要回到保存的状态(可能一个月后),
git stash apply
就不容易了,因为我需要先找到git stash
之前的状态。
我目前有以下适合我的命令序列,但我想知道是否有更优雅的方法。
# on branch master
git add .
git commit -m "Temporary commit on the original branch"
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss HEAD~
git merge master
git checkout master
git reset HEAD^
谢谢大家的解答和解释!我将主要根据@XavierGuihot 的回答做类似的事情
git stash -u # -u to include untracked files
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss
git stash apply
git add --all
git commit -m "Snapshot"
git checkout master
git stash pop --index # --index to recover the state of indexed files
您可以存储更改、创建新分支、应用更改、仅在新分支上执行提交,然后在最终再次应用更改之前再次检出 master。
git stash
git checkout -b snapshot_of_master_yyyy-mm-dd-hh-mm-ss
git stash apply
git add --all
git commit -m "Snapshot"
git checkout master
git stash pop
没有工作树变动的最有效方法是直接使用核心命令执行此操作,假设您没有任何飞行中的合并冲突或索引中的 intent-to-add 标记它是
statenow=`git write-tree`
stashedindex=`git commit-tree -p @ -m "index snap" $statenow`
git add -f .
git tag snap-`date +%Y-%m-%dT%H.%M.%S` \
$(git commit-tree -p @ -p $stashedindex \
-m "worktree snap" \
$(git write-tree)
)
git read-tree $statenow
但如果您不关心被忽略的文件或 no-effect 工作树变动,最简单的方法是
git stash -a -u
git tag snap-`date +%Y-%m-%dT%H.%M.%S stash
git stash pop
然后对于任何一个,恢复你所做的状态,例如
git clean -dfx
git checkout snap-2018-02-18T14.19.15 # to move HEAD + worktree there
# or
git read-tree -um @ snap-2018-02-18T14.19.15 # just the worktree
git read-tree @ snap-2018-02-18T14.19.15^2 # then restore the index
TL;DR
使用
长
在你决定这个问题的任何一个答案之前(参见 HEAD
",每个文件有 三个 个版本需要担心。
也就是说,当你第一次 运行:
git clone <url>
或者,从一个完全干净的(没有未跟踪的文件,没有修改的文件等)状态做:
git checkout <somebranch>
你从每个文件的三个副本开始,例如README
和Makefile
等等,现在可用:
HEAD
中的一个(您签出的任何分支的提示提交):这个是read-only,当然与HEAD
中的匹配,因为它 是HEAD
中的那个。此HEAD:README
文件以 Git 使用的特殊 Git-only 格式存储。 (使用git show HEAD:README
可以看到。)索引中的一个。索引是您将构建下一次提交的地方,但现在,它只包含 current 提交中所有内容的副本。所以
:0:README
——你可以使用git show :0:README
查看这个副本——完全匹配HEAD:README
。这个额外的副本也以特殊的 Git-only 格式存储,这意味着它基本上不需要 space。:0:README
和HEAD:README
的区别是你可以 覆盖 这个:git add README
复制 work-treeREADME
到:0:README
,例如。 (你在这里制作的副本将占用一些 space,但随后将进入下一个git commit
,之后他们将共享冻结/read-only 版本,直到你复制另一个。)每个文件的最后一个副本,如
README
,在work-tree中。该文件采用其正常的日常格式,因此所有程序都可以读取和写入它。不需要用git show
查看,因为它只是一个普通文件!
最初,所有三个版本都匹配,并且没有未跟踪的文件。
所以:
I sometimes need to make a snapshot of the current (possibly dirty) working directory.
对于路径为 P
的每个文件,除了 untracked 文件,我们有:
- HEAD 提交中
P
的版本:您永远不需要保存这个版本,因为它已经通过提交永久保存; - 索引中
P
的版本:要保存这个吗?;和 P
的版本在 work-tree 中:你无疑想要保存这个。
这也留下了如何处理 未跟踪 文件的问题。
让我们在这里注意,未跟踪的文件只是索引中不存在的 work-tree 中的文件。 (这包括 HEAD
中但您小心地 从 索引中删除的文件——它们当前未被跟踪,只要该文件的 work-tree 版本存在。)
git stash save -u
is very similar to what I need ...
这是一个很好的线索,因为 git stash save -u
节省了:
- 当前索引(作为一次提交);
- 当前work-tree(作为另一个提交,仅跟踪文件);和
- 未跟踪的文件(作为第三次提交)。请注意,第三次提交省略了 untracked-and-ignored 的文件(不存在既被跟踪又被忽略的文件;"ignored" 仅适用于已经未跟踪的文件)。如果您还想要忽略的文件,则必须在此处使用
-a
而不是-u
。
... but there are two problems: [1.
git stash -u
removes the untracked files after committing them, and 2.git stash -u
makes it rather hard to un-stash again later]
请注意 git stash -u
只需 运行s git clean
即可删除未跟踪的文件。您必须执行 git reset --hard && git clean -df
才能返回到 unstash 状态(但请参阅我在下面提到的问题案例)。
现在,进行提交(任何 提交)的主要问题是您通过将文件复制到索引中来完成。但我们刚刚注意到,可能存在各种文件的索引版本,例如 :0:README
和 :0:path/to/important.data
,它们不同于 HEAD:
对应的 和 work-tree同行。如果您要保存 work-tree 个副本,则必须通过覆盖索引副本来完成!
如果这没问题,您可能有一条比使用 git stash
或等效方法更简单的前进道路。但是您仍然遇到未跟踪文件的问题!如果不行,你必须先保存索引,就像 git stash
一样,在这种情况下你可能只想使用 git stash
.
我们在上面已经注意到,未跟踪的文件是 work-tree 中的文件——有一些路径 U——但不在索引中:没有 :0:<em>U</em>
。这会产生一些问题:要保存这些文件,我们必须将它们复制到索引中。当然,这会破坏(覆盖)我们精心准备的与 HEAD
和 work-tree 版本不同的任何内容。这就是所有并发症的来源。
如果您确实出于任何原因想要保留索引状态,和 索引状态会记录以后应该跟踪和取消跟踪哪些文件,那么我们有我们的解决方案(即jthill 的解决方案也是如此),这很像 git stash
,但略有修改:
- 写出当前索引状态:
git write-tree
. - 使用结果进行提交(永久直到它没有名字,当它可以被垃圾收集时):
git commit-tree
。此提交可以将当前提交(HEAD
或@
)作为其父项,尽管实际上并不需要。 - 将所有未跟踪的文件(可能包括被忽略的文件)添加到索引:
git add -A
或git add -f -A
等,具体取决于您在第二次提交中想要的内容。 - 写入这个更新的索引,然后使用结果进行第二次提交,其父项是保存的索引状态,并为第二次提交命名以使其永久化。 (在 jthill 的回答中,就像
git stash
所做的那样,他将第二个提交存储为父项,索引提交作为第二个提交的第二个父项。这迫使我们使用后缀^2
符号稍后,它的优点是它可以与git stash
脚本一起使用。)
写完这些后,我们必须立即将索引恢复到原来的状态——我们在第一步中保存的那个。否则所有 previously-untracked 文件现在都是跟踪文件!
要恢复这些东西之一,我们遇到了与 git stash save -u
相同的问题:我们进行的第二次提交中的文件(用于保存未跟踪的文件)将至少暂时变为, 跟踪 个文件。如果现在 work-tree 中有同名文件,Git 将非常不愿意覆盖它们——所以我们需要 git clean -df
或 git clean -dfx
来销毁它们。这里有一点问题,因为这将 也 删除第二次提交中 没有 的文件:例如,假设当你保存所有内容时,有一个名为 important-1
的未跟踪文件,但没有任何名为 important-2
的文件。 现在有一个important-2
。
如果您现在天真地 运行 git clean -df
或 git clean -dfx
,Git 将删除 both 这些未跟踪的 important-*
文件。然后我们将指示 Git 从第二次提交中提取所有文件,包括 previously-untracked important-1
。 Git 会将文件复制到索引和 work-tree 中。由于没有保存 important-2
,Git 不会 复制该文件。
这是使用比较大的缺陷:
git clean -dfx
git checkout snap-2018-02-18T14.19.15
这就是为什么:
git read-tree -um @ snap-2018-02-18T14.19.15
git read-tree @ snap-2018-02-18T14.19.15^2
更好。第一步,git read-tree -um @ snap-...
,执行 merge-and-update 将我们所做的第二次提交(保持所有 work-tree 状态)引入索引并更新 work-tree。这样important-2
就不会被破坏
之后需要第二步来修复索引,因为从第二次提交中读取所有那些未跟踪的文件会导致它们成为 已跟踪 文件。我们希望将索引恢复到制作快照时的状态,或者至少从索引中拉出 out 现在不应该在其中的所有文件它。
我们有确切的索引状态:它在我们所做的第一个提交中,即snap-...^2
(快照或存储的第二个父级)。我们可以直接将其读入索引:
git read-tree snap-2018-02-18T14.19.15^2
(注意此处缺少 @
/ HEAD
),或者执行 two-tree 合并以尽可能保留我们对索引所做的修改:
git read-tree @ snap-2018-02-18T14.19.15^2
请注意,我们可以只重置索引以匹配您当前所在的提交:
git reset HEAD
或者可能将其重置为保存的第一个提交的父级:
git read-tree snap-2018-02-18T14.19.15^1
如果您真的不想保存索引状态。无论哪种方式,未跟踪的文件再次未跟踪,因为它们不再在索引中。
另一种简洁明了的方法。即使没有分支更改也不需要 stash
:
git commit
git branch name-of-my-snapshot-branch
git reset HEAD^
使用 commit
只需提交您想要快照的所有内容,以您喜欢的任何方式执行此操作。
reset
将您的分支指向之前的提交,因此您回到了起点。