是否可以在git中分别进行多组更改?

时间:2017-11-24 12:54:25

标签: git

这是一个关于使用git工作流的频率的问题(请求建议),这是我多年来使用git开发的。

如果我对一个项目进行了很多更改(多个文件中有多处更改),我经常会遍历所有的人(使用git gui)并决定我想要一起进行哪一步,然后将几个人与一个人一起提交提交和描述性评论。然后我继续通过所有剩下的帅哥再次将帅哥分成一个共同的提交。

但是经常出现的问题是在第3次或第4次迭代时,我发现了一个应该在之前的提交中的一个块。在这种情况下,我暂时将阶段性更改提交到名为TMP的内容,提交有问题的“修改”块,然后继续暂存其余的人,并使用“修改TMP”消息进行评论。在我完成所有更改后,我返回并重新定义-i并重新排序并将“修改...”提交压缩到适当的位置。

问题:有更好的方法吗?

我希望能够经历各种各样的情绪并将它们一个接一个地分成几组。当所有内容都暂存时,循环遍历分段集,并使用适当的注释逐个提交它们。我还想在构建舞台布景时积累/编辑评论。

是否存在这样的功能?它叫什么?

一种可能性是允许将单个hunk附加到现有提交,然后重新绑定后面的提交,但不管怎样,否则会使工作区和暂存区域保持不变。

这个问题类似于How to create multiple stages in GitBreak up multiple changes into separate commits with git?,但这些讨论并没有真正回答我的问题。是的,这是可能的,但似乎必须/应该是更好的方式。

1 个答案:

答案 0 :(得分:0)

简短的回答是"不,没有更好的方法"但是你可以尝试git worktree add。 (这会让你遇到一个不同的问题,但毕竟这可能不是问题。)

问题是只有一个索引 1 ,Git称之为 索引,有时候暂存区< / em>或缓存。同时,一旦完成,任何提交都无法改变。甚至git commit --amend 都没有更改提交提交(我们稍后会介绍)。

1 这不是真的。特别是,如果您使用git worktree add,则每个工作树会得到一个索引。 Git还允许各种命令使用临时索引文件;像git stash这样的东西,甚至更复杂的git commit变种,都使用它,因为git write-tree总是使用索引作为输入,但可以指向临时索引。实际上,使用临时索引会非常棘手。

当您使用git add -p或某些更高级的GUI交互式选择要添加到索引的特定更改(差异或个别行或其他)时,您将在索引中创建一个无处不在的文件

想象一个非常简单的存储库,只有一个文件README。您克隆存储库并在master上。情况如下:

HEAD      index    work-tree
------    ------    ------
README    README    README

README的所有三个副本都是相同的(虽然秘密地,Git的HEAD和索引版本被压缩,并且实际上共享底层磁盘存储,因此README只有两个磁盘上的映像,压缩的Gitty和未压缩的简单的。)

现在启动您喜欢的编辑器并修改README。让我们调用这个README;1,只是为了识别&#34;文件的不同版本&#34;而使用可怕的语法 2 。 :-)现在你有了这个:

HEAD      index    work-tree
------    ------    ------
README    README    README;1

但是,你做了一个很大的改变,所以你决定要使用git add -p或其他任何方式以交互方式添加一些变化。完成此操作后,您将结束 this:

HEAD      index    work-tree
------    ------    ------
README    README;2  README;1

也就是说,现在,您正在同时处理该文件的三个不同版本:您无法更改的已提交版本;工作树你改变了;和索引1,您创建的HEAD和工作树版本的Frankensteinian混合。

由于只有一个索引,因此只有一个可用于创建该文件中间版本的位置。所以你必须创建它,然后提交它。提交进行 new 提交,获取新的唯一哈希ID,然后使新提交成为HEAD提交,以便您现在拥有:

HEAD      index    work-tree
------    ------    ------
README;2  README;2  README;1

释放索引以生成该文件的另一个新变体:README的第二个版本在HEAD提交中被安全地保存,完全不可更改(并且大部分是永久性的)。 / p>

2 实际上,这是VMS中版本化文件的语法。

In a comment,mkrieger1与a question where the answers suggest using the fixup and autosquash features of git rebase, including using git commit --fixup to record a commit for autosquashing相关联。这些与git commit --amend一样,使用了Git的分支名称的非常​​有用的属性。

Git中的分支名称指向一个提交。分支中包含的提交集是通过从一个提交开始确定的,Git调用提示提交,然后向后工作:提交有父,父提交有另一个父,等等上。每个提交都存储在其丑陋的提交哈希ID下:分支名称包含提示提交的哈希ID,每个提交包含其父提交的哈希ID:

... <--E <--F <--G   <-- master

我们说master&#34;指向&#34;提示提交G,指向F,依此类推。

由于没有任何提交可以永远被更改,内部箭头总是向后指向,并且不需要被绘制,这很方便,因为它很难做好在stackoverflow上的ASCII。 :-)所以我把它画成:

...--E--F--G   <-- master

(我将箭头保留在分支名称前面,因为分支名称​​执行移动。)

现在,这意味着如果我们创建一个 new 提交,其父级不是正常的&#34;当前提交哈希ID&#34;,而是父级,然后使当前分支名称指向我们刚刚创建的新提交,我们似乎替换了当前提交:

         H   <-- master
        /
...-E--F--G

我们还没有实际更改了任何提交,但是当我们运行git log看起来像,因为没有任何指向{{ 1}},Git没有表现出来。 Git首先向我们展示新提交G,然后返回提交H,然后返回F,依此类推。

这是你使用E获得的:新提交只是作为其父级,当前(现在是当前的)当前提交的任何父级都有。

git commit --amend命令将此提升到一个新的级别:我们可以复制许多提交,而不是只提交一个提交,然后进行一些细微的更改。使用交互式rebase,我们在每个要复制的提交上使用Git git rebase。副本可以在你喜欢的任何提交之后进行,但是对于像autosquash这样的东西,你通常会就地复制这些副本:

git cherry-pick

其中...--E--F--G--H--I--J <-- branch J的修正。现在运行H,Git生成命令:

git rebase -i --autosquash <hash of G>

如果完全直接运行,将导致:

pick <hash-of-H>
pick <hash-of-I>
pick <hash-of-J>

但是,autosquash功能注意到 H'-I'-J' <-- branch / ...--E--F--G--H--I--J [abandoned] 本身作为其一行提交主题具有前缀J,而不是运行它们。此fixup! <subject>部分与<subject>的提交主题相匹配,因此autosquash代码会将说明更改为:

H

执行这些说明会给出:

pick <hash-of-H>
fixup <hash-of-J>
pick <hash-of-I>

其中 HJ--I' <-- branch / ...--E--F--G--H--I--J [abandoned] 是自动压缩的HJ + H,使用J的提交消息。

现在再一次,问题在于只有一个索引,并且您在中构建了一个索引的中间图像

如果您使用H,则可以根据需要制作任意数量的工作树。每个都有自己的索引,当然还有自己独立的工作树。但是Git强加了一个非常强大的约束:每个工作树必须位于不同的分支上。

毕竟这可能不是问题。请记住,在Git中,分支是非常便宜的:使 new 分支只需要一个磁盘块,只需要一个41字节的文件。 (未来的实施可能会改变这些细节,但分支机构将保持荒谬的廉价。)

让我们回到这张图:

git worktree add

我们现在可以创建一个新的分支,而Git所做的就是写一个指向提交...--E--F--G--H--I--J <-- branch (HEAD) 的文件。这就是为什么我们将J添加到绘图中,以便我们知道我们的工作树正在使用哪个分支:

(HEAD)

我们现在可以添加,复制或变基,或者其他任何东西,无论我们喜欢什么。新提交是完全只读的,而且大部分是永久性的,是安全的,并且总是与旧的提交分开。新名称...--E--F--G--H--I--J <-- branch (HEAD), br2 可以安全地保留原始提交,无论我们使用它们做什么。或者,我们可以将br2切换为HEAD,并让旧名称br2保持原始提交的安全:

branch

现在让我们像以前一样对...--E--F--G--H--I--J <-- branch, br2 (HEAD) 做一些事情:

H-I-J

如果你创建一个新的工作树,你可以让它有一个新的分支。新的工作树共享所有旧的提交,以及所有旧的分支。

Git禁止你使用相同的分支拥有两个工作树,因为它们都会指向同一个提交,但却有两个索引文件和两个工作树。当您在其中一个中进行新提交时,将使用该索引中的索引,并将 shared 分支更改为指向新提交。结果是这两个中的另一个仍然具有旧的(现在是陈旧的)索引和旧的(现在是陈旧的)工作树。 Git的作者认为这太混乱了,并且只是将其定为非法。 因为分支是如此便宜,这实际上是非常合理的:只需为每个新工作树创建一个新分支。

您的优势在于您不仅拥有多个索引文件,还拥有多个工作树。您可以停止尝试使用每个文件的额外版本(...--E--F--G--H--I--J <-- branch \ HJ--I' <-- br2 (HEAD) )来播放索引文件技巧:只需使工作树文件看起来像您想要的那样,然后测试它,然后提交它。所有这些都是临时工作树上的临时工作树,如果他们不能很好地解决这些问题。如果他们运作良好,请使用最好的作为最终结果。只需删除(git add -p)所有较小的工作树和(rm -rf)&#34;毕竟不能解决问题&#34;一旦你满意就临时分支。