使用git创建工作目录的快照

时间:2018-02-18 21:14:01

标签: git git-branch git-merge git-stash

我有时需要制作当前(可能是脏的)工作目录的快照。 git stash save -u与我的需求非常相似,但有两个问题:

  1. 我希望我的工作目录保持相同状态(保持未跟踪文件未跟踪)
  2. 如果我需要回到保存状态(可能一个月后),git stash apply并不容易,因为我首先需要在git stash之前找到状态。
  3. 我目前有以下命令序列对我有用,但我想知道是否有更优雅的方法。

    # 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
    

4 个答案:

答案 0 :(得分:1)

没有worktree流失效率最高的是直接使用核心命令,假设你没有任何机上合并冲突或意图添加标记在它的

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

但如果您不关心被忽略的文件或无效的工作树流失,最简单的方法是

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

答案 1 :(得分:1)

TL; DR

使用jthill's answer,这是我在写下下面的长答案的各阶段之间离开键盘时所写的。

在你解决这个问题的任何一个答案之前(参见jthill's answer我认为在某种意义上是最好的#34;),考虑一下:每当你有一个状态不是&只是"工作树中跟踪的所有内容都与HEAD"完全匹配,每个文件都有三个版本需要担心。

也就是说,当你第一次跑步时:

git clone <url>

或者,从完全干净(没有未跟踪文件,没有修改过的文件等)状态执行:

git checkout <somebranch>

开始每个文件的三个副本,例如READMEMakefile等等,现在可用:

  • HEAD中的一个(您已检出的任何分支的提示):这个是只读的,当然与HEAD中的那个匹配,因为它 HEAD中的那个。此HEAD:README文件以Git使用的特殊Git格式存储。 (使用git show HEAD:README查看。)

  • 索引中的一个。索引是您构建下一个提交的地方,但是现在,它只包含当前提交中所有内容的副本。所以:0:README - 您可以使用git show :0:README查看此副本 - 与HEAD:README完全匹配。这个额外的副本也以特殊的Git格式存储,这意味着它基本上没有空间。 :0:READMEHEAD:README之间的区别在于您可以覆盖这一个:git add README将工作树README复制到:0:README , 例如。 (你在这里制作的副本会占用一些空间,但接下来是git commit的内容,之后他们将共享该冻结/只读版本,直到你复制另一个版本。 )

  • 每个文件的最后一个副本(例如README)位于工作树中。此文件采用正常的日常格式,因此所有程序都可以读取和写入。我们无需使用git show查看它,因为它只是一个普通文件!

最初,所有三个版本都匹配,并且没有未跟踪的文件。

所以:

  

我有时需要制作当前(可能是脏的)工作目录的快照。

对于包含路径 P 的每个文件,未跟踪文件除外,我们有:

  • HEAD提交中的 P 版本:您永远不需要保存此版本,因为它已经通过提交而永久保存;
  • 索引中的 P 版本:您要保存此文件吗?和
  • 工作树中 P 的版本:毫无疑问,您希望保存此版本。

这也留下了如何处理未跟踪的文件的问题。

这里要注意,未跟踪文件只是工作树中索引中不存在的文件。 (这包括HEAD中但您已经从索引中删除的文件 - 他们当前未跟踪的文件,只要该文件的工作树版本存在。)

  

git stash save -u与我需要的非常相似......

这是一个很好的线索,因为git stash save -u会保存:

  • 当前索引(作为一次提交);
  • 当前工作树(作为另一个提交,仅跟踪文件);和
  • 未跟踪的文件(作为第三次提交)。请注意,第三个提交省略了未跟踪和忽略的文件(没有跟踪和忽略的文件;&#34;忽略&#34;仅适用于已经未跟踪的文件)。如果您还想要忽略的文件,则必须使用-a而不是-u
  

......但有两个问题:[1。 git stash -u在提交后删除未跟踪的文件,并且2. git stash -u使得以后再次取消隐藏文件变得相当困难]

请注意,git stash -u只需运行git clean即可删除未跟踪的文件。你必须做一个git reset --hard && git clean -df才能回到州内解冻(但请看下面我提到的问题)。

现在,进行commit- 任何提交的主要问题是您通过将文件复制到索引中来实现。但我们只是注意到可能存在各种文件的索引版本,例如:0:README:0:path/to/important.data,它们与工作树中的HEAD:对应不同同行。如果你做任何事情来保存工作树对应物,你必须通过覆盖索引副本来做到这一点!

如果可以的话,你可能会有一条比使用git stash或同等版本更简单的前进路径。但你仍然有未跟踪文件的问题!如果不行,您必须先保存索引,就像git stash一样,在这种情况下,您可能只想使用git stash

上面我们已经注意到,未跟踪的文件是工作树中的文件 - 有一些路径 U - 这也不在索引中:那里&#39 ; s没有:0:U。这会产生一些问题:要保存这些文件,我们必须将它们复制到索引中。当然,这会破坏(覆盖)我们精心准备的与HEAD和工作树版本不同的任何内容。这就是所有复杂情况的来源。

如果你因任何原因想要保留索引状态,索引状态记录了以后应该跟踪和跟踪哪些文件,那么我们就有了解决方案(这是jthill&#39; s解决方案),这很像git stash,但略有修改:

  • 写出当前索引状态:git write-tree
  • 使用结果进行提交(永久性,直到它没有名称,可以进行垃圾回收):git commit-tree。此提交可以将当前提交(HEAD@)作为其父级,但实际上并不需要。
  • 将所有未跟踪的文件(可能包括被忽略的文件)添加到索引中:git add -Agit add -f -A等,具体取决于您在第二次提交时的确切要求。
  • 编写此更新的索引,然后使用结果进行第二次提交,其父级是已保存的索引状态,并为第二次提交提供一个名称以使其成为永久性。 (在jthill的回答中,正如git stash所做的那样,他有第二个提交存储作为父项提交,索引提交作为第二个提交的第二个父项。这迫使我们使用后缀{ {1}}以后的表示法,其优点是可以与^2脚本一起使用。)

写完所有这些后,我们必须立即将索引恢复到原始状态 - 我们保存的第一步。否则,所有先前未跟踪的文件现在都是跟踪文件!

要恢复其中一项,我们遇到与git stash相同的问题:我们进行的第二次提交(保存未跟踪文件)中的文件将至少暂时变为< em>跟踪文件。如果现在工作树中存在具有相同名称的文件,Git将非常不愿意覆盖它们 - 因此我们需要git stash save -ugit clean -df来销毁它们。这里有一点问题,因为这将删除第二次提交中的文件:例如,假设您保存了所有内容,有一个名为git clean -dfx的未跟踪文件,但没有任何名为important-1的文件。 现在那里有important-2

如果您现在只是天真地运行important-2git clean -df,Git将删除这两个未跟踪git clean -dfx文件的。然后我们将指示Git从第二次提交中提取所有文件,包括之前未跟踪的important-*。 Git会将文件复制到索引和工作树中。由于没有保存important-1,Git 不会复制该文件。

使用时这是一个相当大的缺陷:

important-2

是原因:

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-...就不会被摧毁。

之后需要第二步修复索引,因为从第二次提交中读取所有未跟踪的文件会导致它们成为跟踪的文件。我们希望将索引恢复到我们创建快照时的方式,或者至少将索引的 out 拉出现在不应该在其中的任何文件

我们完全具有索引状态:它在我们制作的第一个提交中,即important-2(快照的第二个父或存储)。我们可以直接将其读入索引:

snap-...^2

(请注意此处缺少git read-tree snap-2018-02-18T14.19.15^2 / @),或进行双树合并,以便在可能的情况下对索引进行修改:

HEAD

请注意,我们可以只重置索引以匹配您当前的提交:

git read-tree @ snap-2018-02-18T14.19.15^2

或者可能将其重置为已保存的第一次提交的父级:

git reset HEAD

如果你真的不想保存索引状态。无论哪种方式,未跟踪的文件再次未被跟踪,现在它们不再在索引中。

答案 2 :(得分:0)

您可以存储更改,创建新分支,应用更改,仅在新分支上执行提交,然后再次签出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

答案 3 :(得分:0)

另一种不使用stash且不太复杂的命令的方法:

假设您从master开始

git checkout -b my-snapshot
git add --all
git commit
git checkout --detach
git reset master
git checkout master

重置将分离的分支指向master的提交,但不更改文件。

然后我们实际结帐。由于它已经指向master的提交,因此不会再次更改文件。