git - 如何在应用存储时强制 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 尝试执行三向合并。
但在我的用例中,这没有意义。该脚本无论如何都不会改变文件,所以我真的只是希望“快进”存储应用。或者,我需要“重新设置”存储,以便它的唯一父级是索引。
解决方案
TL;博士
在像这样申请之前,您需要git reset --hard HEAD
(或任何等效的) 。--index
所有关于硬重置的常见警告都适用。
长
我在评论中链接到How do I proper git stash/pop in pre-commit hooks to get a clean working tree for testing?它显示了如何做最后的 pop(或等效),以及一些关于此的警告。然而,所问问题的答案——特别是如何在应用存储时强制 git 使用快进——是你不能,事实上,这个问题甚至没有意义:快进是一个与 stashing 和 unstashing 不同的概念。1
Git stash 只是一组经过特殊安排的提交(除非您使用--all
or选项,否则两个提交,然后您会得到三个)。--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
该索引将提交作为其父级。untracked-files 提交没有父级(是根提交):
...--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
!)。这git stash apply
使用相同的选项和参数2运行,如果成功且没有合并冲突,则git stash drop
在相同的存储上运行。
apply 可以同时使用索引提交和工作树提交来恢复您正在处理的内容,但默认情况下,它会忽略索引提交。添加--index
告诉 Git 使用git apply --index
. 如果失败,git stash
则停止并且不执行任何操作。在此,我建议使用 将 stash 转换为新分支git stash branch
,但git stash
仅建议不使用--index
. 3
在任何情况下,Git 都会尝试将工作树提交应用到当前工作树。4 如果您没有 stashed并且没有 --keep-index
对当前工作树进行任何更改,这将始终成功:当前索引和工作树将匹配HEAD
提交,因此这将使当前索引保持不变并应用工作中的所有差异-tree 提交到工作树本身,从而恢复隐藏的工作树。
此时的问题是您确实使用--keep-index
了,因此当前工作树与您设置的索引HEAD
匹配,而不是与提交匹配。因此,在应用存储之前(有或没有--index
),您必须首先重置工作树以匹配HEAD
提交,即git reset --hard
. 您想要的索引和工作树状态在您将要应用的存储中,因此只要当前索引和工作树没有被您拥有的任何预提交/预推送代码修改,这是安全的.
一旦你这样做了,一个git apply --index
隐藏提交将恢复索引和工作树(以链接问题中的那个错误为模!)。
脚注
这些是故意乱序的,因为脚注 1 太长了。
2参数git stash apply
默认为refs/stash
。如果你给它任何参数,它的行为会有点奇怪:在最新版本的 Git 中,如果你给它一个全数字参数n它会检查,否则它会使用你给它的任何东西。它传递此字符串以确保它转换为有效的哈希 ID,并且当以、、、和为后缀时,它们也会转换为有效的哈希 ID。如果字符串产生了一个有效的哈希 ID和,这些也会被记住。这些共同构成, , , , , and , 加上and如果存在的话。看stash@{n}
git rev-parse
:
^1
^1:
^2
^2:
^3
^3:
w_commit
w_tree
b_commit
b_tree
i_commit
i_tree
u_commit
u_tree
gitrevisions 文档更详细地了解了它的工作原理。
这归结为,您传递给的任何参数都git stash apply
必须具有合并提交的形式,并且至少具有两个父级。Git 不会检查在预期的三个之外是否还有其他父级,也不会检查此合并提交是否真的是一个隐藏:它只是假设如果它具有正确的父级集,则您打算将其用作一个。
3--index
这对于那些不想单独存储索引并在理解它git stash apply
或git stash pop
不理解它的情况下使用的 Git 新手来说可能是足够明智的。但是,一旦您确实了解了索引,那显然是错误的:您希望将隐藏的索引相对于当前索引的更改恢复到当前索引,而不是完全忽略它们!如果合适,提交当前索引,然后提交当前工作树(如果合适),然后将 stash 转换为分支并提交其工作树,为您提供构建正确最终结果所需的一切。
4技术细节:如果存在冲突,应用程序使用git merge-recursive
(这是实现的)git merge -s recursive
和一些秘密环境变量来设置冲突标记上的名称。合并基础是进行HEAD
存储时的提交,当前树是写入当前(在非存储时)索引的结果,而要合并的项目是工作树提交,或者更准确地说,它的树。这利用了一些合并可以在未提交更改的情况下运行的事实。前端git merge
命令禁止未提交更改的合并尝试,因为当出现问题时结果可能非常混乱。
1快进概念也比人们最初看到的要复杂一些。也就是说,我们在合并时看到它 - 请参阅`git merge` 和 `git merge --no-ff` 有什么区别?——但它实际上是指更新引用,例如分支名称。当且仅当新提交哈希以旧提交哈希作为祖先时,分支名称更新是快进,即,如果git merge-base --is-ancester $old_hash $new_hash
返回零退出状态。
当git merge
执行这些快进操作之一时,这意味着 Git 已将HEAD
提交更改为指向新的哈希,并根据需要更新了索引和工作树。如果您要快进到 stash 中的工作树提交,这会将奇怪的技术上合并工作树提交暴露给 Git 的其余部分,至少在那里它会非常混乱。
请注意,git fetch
andgit push
还执行快进操作,或 with--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
,以及添加的单词(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]
该--left-right
选项以及三点语法告诉 Git 标记提交来自哪个“侧”。在这种情况下,>
提交现在位于拾取分支上,并且<
提交已被移除。这些特定的已删除提交现在完全没有被引用,很快就会被垃圾收集(ish)。
推荐阅读
- windows - 使用 NSIS,卸载程序快捷方式不会显示在开始菜单中
- asp.net - 从 viewbag 下拉列表中获取 0 值返回到控制器
- laravel - 为什么我的网络登录转到 api.php 路由而不是 web.php
- web - 无标记 WebAR 库
- django - 调用者在使用 GMail API 时没有权限错误
- ruby-on-rails - 对 .erb 文件中的数据进行排序
- qt - TypeError:值为空,无法转换为对象
- java - 错误:(19、21)java:不兼容的类型:java.lang.Object 无法转换为项目
- javascript - 如何从地图外部将对象拖放到 Mapbox 地图中?
- django - 无法通过带有脆表的外键上传数据