应用隐藏时如何强制git使用快进?

时间:2018-06-23 19:26:13

标签: git

  • 我正在编写一个预提交/预推送脚本
  • 但是脚本只应对分阶段的更改起作用
  • 要实现这一目标,我使用git stash push --keep-index删除了未进行的更改
  • 然后git stash pop在脚本之后重新应用

但是,如果同一行上同时存在已暂存和未暂存的更改,git stash pop将始终创建合并冲突。例如,

$ echo "print('a')" >> main.py  # main.py already exists
print('a')
$ git add main.py
$ sed -i 's/a/b/g' main.py  # now it's print('b')
$ git status --short
## master
MM main.py
$ git stash push --keep-index
Saved working directory...
$ git stash pop
Auto-mergin main.py
CONFLICT (content): Merge conflict in main.py

我如何让git优先应用存储中的更改而不是分阶段的更改?


我有一个想法,认为这种行为可能是由于具有两个父项的隐式更改-第一个是HEAD,第二个是索引。然后,Git尝试执行三向合并。

但是在我的用例中,这没有任何意义。该脚本无论如何都不会改变文件,所以实际上我只是在寻求“快进”隐藏的应用。或者,我需要“隐藏”存储库,以便它的唯一父级是索引。

1 个答案:

答案 0 :(得分:1)

TL; DR

您需要先git reset --hard HEAD(或任何其他等效方式),然后再使用--index进行申请。所有有关硬重置的常见警告都适用。

我在评论中链接到How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?,其中显示了如何进行最终的弹出(或等效操作),以及有关此方面的一些注意事项。但是,所提出问题的答案(特别是如何在应用隐藏时强制git使用快进)是您无法做到的,实际上,这个问题甚至没有含义:快进与隐藏和不隐藏是不同的概念。 1

Git存放只是一组特殊安排的提交(两次提交,除非您使用--all--include-untracked选项,否则您得到三个提交)。提交保存:

  • git stash时的索引(使用git write-tree);
  • git stash时的工作树内容(使用相当复杂的代码);
  • ,并且在此列表中排在最后,但实际上更早完成,如果您确实使用了--all--include-untracked,则未跟踪的文件包括忽略的文件(--all)或未跟踪,不包括忽略的文件(--include-untracked)。

Git然后重置工作树,通常与HEAD提交匹配,并且如果使用了--all--include-untracked,则也会删除存储在第三次提交中的文件。但是,当您使用--keep-index时,Git会重置工作树以匹配索引内容。

名为refs/stash的引用被修改为指向工作树提交。此提交具有HEAD提交(父级#1),索引提交(父级#2)和未跟踪的文件提交(父级#3)作为其父级。索引将HEAD提交作为其父项。未跟踪的文件提交没有父提交(是根提交):

...--o--o--o   <-- refs/heads/somebranch (HEAD)
           |\
           i-w   <-- refs/stash
            /
           u

或更常见的是,没有u的情况。

git stash重置为HEAD时(即没有--keep-index),您要做的全部操作就是撤消git stash的运行git stash pop --index(注意:不是--keep-index!)。这会使用相同的选项和参数{sup> 2 运行git stash apply,并且如果在没有合并冲突的情况下成功运行,则git stash drop将在同一存储库中运行。

应用程序可以使用索引提交和工作树提交来恢复您正在处理的内容,但是默认情况下,它忽略索引提交。添加--index告诉Git使用git apply --index将索引提交(对当前索引内容进行比较)应用于当前索引内容。如果失败,git stash将停止并且不执行任何操作。在这种情况下,我建议使用git stash branch将存储库转换为新分支,尽管git stash仅建议不使用--index进行应用。 3

无论如何,Git然后尝试将工作树提交应用于当前工作树。 4 如果您藏匿了而没有 --keep-index,并且不对当前工作树进行任何更改,这将始终成功:当前索引和工作树将与HEAD提交匹配,因此这将使当前索引保持不变,并应用工作树中的所有差异提交到工作树本身,从而恢复了隐藏的工作树。

此时的问题是您没有使用--keep-index,所以 current 工作树与 index 匹配您进行了设置,而不是匹配HEAD提交。因此,在应用存储(带有或不带有--index)之前,必须首先重置工作树以匹配HEAD提交,即git reset --hard。所需的索引和工作树状态位于您将要应用的存储中,因此只要当前索引和工作树没有被您拥有的任何预提交/预推送代码修改,这是安全的。

完成此操作后,git apply --index的隐藏提交将同时还原索引和工作树(对链接的问题中的错误进行模化!)。


脚语

由于脚注1太长,因此故意使它们混乱。

2 git stash apply的参数默认为refs/stash。如果给它提供任何参数,则该行为有点幻想:在最新版本的Git中,如果给它一个全数字的参数 n ,它将检查stash@{n},否则将使用您使用的任何参数给它。它将此字符串传递给git rev-parse,以确保将其转换为有效的哈希ID,并在后缀:^1^1:^2^2:,它们也将转换为有效的哈希ID。如果字符串产生的有效哈希ID同时为^3^3:,则这些哈希ID也将被记住。它们共同构成w_commitw_treeb_commitb_treei_commiti_tree,以及u_commit和{ {1}}(如果存在)。有关详细信息,请参见the gitrevisions documentation

这归结为,您传递给u_tree的任何参数必须具有合并提交的 form ,并且至少有两个父级。 Git不会检查预期三者之外是否还有其他父母,也不会检查合并提交是否确实是一个秘密:它只是假设如果它具有正确的父母年龄,您打算将其用作父母。

3 对于不尝试单独存储索引并在git stash apply--index上使用git stash apply而不理解的Git新手来说,这可能足够明智。但是,一旦您了解了索引,这显然是错误的:您想将相对于当前索引,当前索引的隐藏索引的更改还原,而不是完全忽略它们!适当时提交当前索引,适当时提交当前工作树,然后将存储区转换为分支并提交其工作树,将为您提供构建正确的最终结果所需的一切。

4 技术细节:应用程序使用git stash pop(这是实现git merge-recursive的方式)和一些秘密环境变量来设置冲突标记上的名称(如果有)冲突。合并基础是进行存储时的git merge -s recursive提交,当前树是写入当前(在非存储时)索引的结果,而要合并的项目是工作树提交,或更确切地说,是它的树。这利用了一些合并 可以在未提交更改的情况下运行的事实。前端HEAD命令禁止进行未提交的更改的合并尝试,因为在出​​现问题时结果可能会很混乱。

1 快进概念也比起初通常看到的要复杂一些。也就是说,我们在合并时会看到它(请参阅What is the difference between `git merge` and `git merge --no-ff`?),但实际上是指更新引用,例如分支名称。仅当新的提交哈希将旧的提交哈希作为祖先,即git merge返回零退出状态时,分支名称更新才是快进。

git merge-base --is-ancester $old_hash $new_hash执行这些快进操作之一时,这意味着Git更改了git merge提交以指向新的哈希,并根据需要更新了索引和工作树。如果要在存储库中快进到工作树提交,那将在Git的其余部分暴露出奇怪的技术上合并的工作树提交,至少这会非常混乱。 / p>

请注意,HEADgit fetch也执行快进操作,或者与git push一起使用,可以对分支和(用于获取)远程跟踪名称进行非快进更改。推送的接收者通常需要快进,因为这意味着更新的分支名称包含它曾经使用过的所有提交以及一些其他提交。强制的,非快进的更新 discards 从分支提交(无论是否添加了新的)。有点神秘的--force输出记录了远程跟踪名称是以三种(!)方式快速转发还是强制的:

git fetch

请注意,在记录更新$ git fetch remote: Counting objects: 1701, done. remote: Compressing objects: 100% (711/711), done. remote: Total 1701 (delta 1363), reused 1318 (delta 989) Receiving objects: 100% (1701/1701), 975.29 KiB | 3.65 MiB/s, done. Resolving deltas: 100% (1363/1363), completed with 284 local objects. From [url] 3e5524907..53f9a3e15 master -> origin/master 61856ae69..ad0ab374a next -> origin/next + fc16284ea...4bc8c995a pu -> origin/pu (forced update) 9125ddae1..9db014fc5 todo -> origin/todo * [new tag] v2.18.0 -> v2.18.0 * [new tag] v2.18.0-rc2 -> v2.18.0-rc2 的行前面的+,并添加了单词origin/pu。这是三种方式中的两种。但是,请注意两个缩写的提交散列之间的点:不是强制更新的所有 other 行都显示两个点,但是此更新显示三个点。这是因为我们可以使用具有相同的三点语法的(forced updated)git rev-list来查看添加和删除的提交:

git log

$ git log --oneline --decorate --graph --left-right fc16284ea...4bc8c995a > 4bc8c995a (origin/pu) Merge branch 'sb/diff-color-move-more' into pu |\ | > 76db2b132 SQUASH????? Documentation breakage emergency fix | > f2d78d2c6 diff.c: add white space mode to move detection that allows indent changes | > a58e68b88 diff.c: factor advance_or_nullify out of mark_color_as_move [massive snippage] < fc16284ea Merge branch 'mk/http-backend-content-length' into pu |\ | < 202e4a2ff SQUASH??? | < cb6d3213e http-backend: respect CONTENT_LENGTH for receive-pack < | 4486a82e5 Merge branch 'ag/rebase-p' into pu < | a84cc85f3 Merge branch 'nd/completion-negation' into pu [much more snippage] 选项以及三点语法告诉Git标记提交来自哪一面。在这种情况下,--left-right个提交现在位于拾取分支上,并且>个提交已被删除。现在,这些已删除的特定提交完全未被引用,并将很快被垃圾回收。