根据 Git,谁是“我们”,谁是“他们”?
Posted
技术标签:
【中文标题】根据 Git,谁是“我们”,谁是“他们”?【英文标题】:Who is "us" and who is "them" according to Git? 【发布时间】:2014-01-28 07:18:29 【问题描述】:在 Git rebase 之后,以及在其他情况下,您可以在 git status
报告中找到一些标记为 我们已删除 的文件。根据 Git,谁是 我们,为什么?
它是指我坐在这个分支上并且它为我工作吗?或者它是指它自己和在我所针对的分支上工作的人?
【问题讨论】:
奇怪的是,似乎“被他们删除”意味着你删除了你正在变基的分支上的文件,而“被我们删除”意味着其他人删除了它。git merge
给出相反的信息。
git rebase, keeping track of 'local' and 'remote'的可能重复
相关:What is the precise meaning of “ours” and “theirs” in git?
【参考方案1】:
当您合并时,us
指的是您要合并到的分支,而不是 them
,要合并的分支。
当您rebase 时,us
指的是上游分支,them
是您正在移动的分支。在变基的情况下,这有点违反直觉。
原因是 Git 使用相同的合并引擎进行 rebase,它实际上是在将你的东西挑选到上游分支中。 us
= 进入,them
= 来自。
【讨论】:
从 实现 的角度来看是有道理的,因为 rebase 使用合并机制,“我们的”分支是上游分支,“他们的”分支是正在重新设置基础的那个。但我同意,嗯,“非常没有帮助”似乎是一种礼貌的说法。我宁愿用其他词来标记分支,而不是“我们/我们的”和“他们/他们的”,也许用分支名称,例如:“在主服务器中删除,在功能中修改”。 在 rebase 期间进行结帐时,我总是倾向于将--theirs
与 --ours
混淆,这太违反直觉了。我希望他们有一天能解决这个问题。
当只涉及一个分支时,例如在rebase
期间重新排序或压缩提交时怎么办?
将us
视为“[u]p[s]tream”而不是普通英文单词“us”的助记符可能会有所帮助。
你能澄清一下git revert
的情况吗?我在这里添加了这个问题:***.com/q/63908012/4561887.【参考方案2】:
(这也回答了这个问题:“git rebase 是如何工作的,它到底发生了什么?”)
在所有情况下:
-
"us"(或 "ours")= 当前签出的提交 (
HEAD
) 此时 git 执行操作导致冲突(稍后会详细介绍),并且:
"them" (或 "theirs") = 另一个提交,未由 git 签出 此时 git 执行操作冲突(稍后会详细介绍)。
重要提示:HEAD
在执行导致冲突的操作时不一定是在您键入 git 命令时的HEAD
。这是必须理解的。在运行导致冲突的操作之前,Git 可能会进行一些检查并更改 HEAD
(已检查提交),从而导致“我们”和“他们”在未经训练的人眼中出现交换或倒退。
4 种情况,详细:merge、cherry-pick、rebase、revert:
git merge
(直觉):
-
示例命令:
git checkout master
git merge feature_branch # merge feature_branch into master
"us"/"ours" = HEAD
,即master
,因为您在运行git merge feature_branch
时位于分支master
。
"them"/"theirs" = feature_branch
,这是您要合并到 master
的分支。
git cherry-pick
(直观):
-
示例命令:
git checkout feature_branch
git cherry-pick some_commit # apply some_commit to feature_branch
"us"/"ours" = HEAD
,即feature_branch
,因为您在运行git cherry-pick some_commit
时位于分支feature_branch
。
"them"/"theirs" = some_commit
,这是您在 feature_branch
上挑选的提交。
git rebase
(违反直觉,但一旦你了解了它的工作原理,就完全有意义了):
-
示例命令:
git checkout feature_branch
git rebase master # rebase feature_branch onto latest master
此图(绘制于https://asciiflow.com),latest 或 newest 提交位于顶部和/或右侧:
# Prior to rebase: feature_branch
# received new commits while
# master did too
#
# master
# x
# | feature_branch
# x y
# | |
# x y
# | /
# git merge-base ────► x--y--y--y
# master feature_branch |
# x
#
#
# After rebase: feature_branch has
# been completely cherry-picked onto
# the end of master
#
# feature_branch
# y'
# |
# y'
# /
# y'--y'--y'
# |
# master x
# |
# x
# |
# x
# |
# x
# |
# x
"us"/"ours" = HEAD
,这是 上游分支:最初是在 master
上的最后一个 x
提交,然后是一些新的提交,@987654369 @, cherry-picked
最重要的是(这个很棘手!)。这是因为当你输入 git rebase master
时,git 首先检查 master
作为开始挑选你的 feature_branch
提交的起点,然后它会确定从 feature_branch
到哪些提交樱桃挑选(即:您的哪些feature_branch
提交尚未在master
上)。它通过找到merge-base
(feature_branch
和master
共同的提交,可以通过git merge-base master feature_branch
找到)来做到这一点,然后它开始从第一个提交 在merge-base
之后 之后,一次一个地工作,朝着feature_branch
的最后一次提交,到master
的尖端,从而“重新定位”所有“新”y
提交您将feature_branch
添加到最新的master
,作为新的y'
提交。因此,"us"/"ours" = HEAD
,但由于 git 在幕后进行了新的检查以执行此变基,HEAD
不是您在使用时所在的分支输入git rebase master
。相反,us 或 HEAD
,如果在第一个 cherry-pick
期间发生冲突,则要么是 master
上的最后一个 x
提交,要么是任何新提交 y'
,如果在以后的任何樱桃挑选期间发生冲突,则最后一次成功挑选到master
。 Them 因此是另一个提交,它是来自feature_branch
的一些y
提交,它通过一个樱桃选择应用于这个新的HEAD
,依次从第一个y
在feature_branch
上提交,它紧跟在git merge-base master feature_branch
之后,一直到最后一个y
在feature_branch
上提交。 请参阅下面对“他们”的解释。
"them"/"theirs" = some y
commit from feature_branch
应用于新签出的HEAD
,其中HEAD
是最后一个x
在master
上提交用于rebase 期间的第一个cherry-pick 操作,或者在master
之上新创建的y'
提交之一,因为feature_branch
是“rebase”,或者cherry-picked one-commit-一次(沿着从git merge-base master feature_branch
到feature_branch
的最后一次提交的新提交字符串)到master
。 参见上面对“我们”的解释。
git revert
(有点直观):
-
示例命令:
git checkout feature_branch
# create a new commit to undo the changes from some_previous_commit
# within feature_branch
git revert some_previous_commit
关于这个的一些详细的、低级的机制,请参阅my other answer here 和this very long and detailed answer by @torek here 底部的“结果和结论”部分。
"us"/"ours" = HEAD
,即feature_branch
,因为您在运行git revert some_previous_commit
时位于分支feature_branch
。更具体地说,"us"/"ours" 包含由git diff some_previous_commit..HEAD
表达的更改,即从被还原的提交到我们现在正在进行的提交的更改。
"them"/"theirs" =(与some_previous_commit
相反或相反),这是您要恢复其更改的提交(撤消,通过在feature_branch
之上创建一个新提交)。换句话说,"them"/"theirs" 包含git diff some_previous_commit..some_previous_commit~
表达的更改,其中some_previous_commit~
是some_previous_commit
的父提交。这意味着 "them"/"theirs" 是 some_previous_commit
的 对立面,因为它包含与其更改相反的内容,以便撤消 some_previous_commit
的变化。
示例用例:
合并冲突解决示例:
# 1. Merge `feature_branch` into `master`, accepting ALL of
# `master`'s (`ours`) changes in the event of
# any merge conflicts!
git checkout master
git merge -X ours feature_branch
# 2. Merge `feature_branch` into `master`, accepting ALL of
# `feature_branch`'s (`theirs`) changes in the event of
# any merge conflicts!
git checkout master
git merge -X theirs feature_branch
这里还有一些。 这些是我最常用的技术,而不是上面的 2 个示例。
# 3. Assuming this merge attempt results in merge conflicts in
# these 3 files, but we know all of the changes on the `master`
# branch side are the ones we wish to keep, check out these 3
# files from `master` (`--ours`) to overwrite the conflicted
# files. Then, add these now-fixed files to stage them for
# committing, and continue (finish) the merge.
git checkout master
git merge feature_branch
git checkout --ours -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git add path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git status
git merge --continue
# 4. Assuming this merge attempt results in merge conflicts in
# these 3 files, but we know all of the changes on the `feature_branch`
# side are the ones we wish to keep, check out these 3
# files from `feature_branch` (`--theirs`) to overwrite the conflicted
# files. Then, add these now-fixed files to stage them for
# committing, and continue (finish) the merge.
git checkout master
git merge feature_branch
git checkout --theirs -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git add path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
git status
git merge --continue
非常有用:如果存在整个文件夹的冲突,您还可以指定一次接受来自--ours
或--theirs
分支的所有冲突更改整个文件夹,像这样!:
**最佳合并冲突解决示例:**
# 5. [BEST EXAMPLE] Assuming this merge attempt results in merge conflicts in
# a bunch of files, some of which are inside `path/to/some/dir`, I can
# choose to accept the changes from one side or the other **for
# all conflicts within files inside this directory**, like this!:
git checkout master
git merge feature_branch
# Keep `--theirs` for all conflicts within files inside this dir
git checkout --theirs -- path/to/some/dir
# OR: keep `--ours` for all conflicts within files inside this dir
git checkout --ours -- path/to/some/dir
# Add (stage for committing) all changes within files inside this dir
# all at once
git add path/to/some/dir
git status
git merge --continue
处理path does not have our version
或path does not have their version
错误:
如果你曾经运行过这样的事情:
git checkout --ours -- path/to/some/dir
...它没有工作!它什么也没做。相反,它会输出以下错误:
error: path 'path/to/some/dir/file1.cpp' does not have our version error: path 'path/to/some/dir/file2.cpp' does not have our version error: path 'path/to/some/dir/file3.cpp' does not have our version
问题是这些错误文件在our
端已删除文件,因此我们必须在运行git checkout --ours -- path/to/some/dir
之前手动git rm
每个文件。
git rm path/to/some/dir/file1.cpp path/to/some/dir/file2.cpp \
path/to/some/dir/file3.cpp
# then try again
git checkout --ours -- path/to/some/dir
您也可以这样做来自动化流程:
git checkout --ours -- path/to/some/dir \
|& gawk ' print $3 ' | xargs git rm
git checkout --ours -- path/to/some/dir
有关上述命令的详细说明,请参见我的回答:git checkout --ours when file spec includes deleted file。
警告警告警告!
如需下文所述问题的更详细示例,请see my other answer here。
请勿在冲突解决过程中使用git checkout -- path/to/some/dir
或git checkout some_branch -- path/to/some/dir
(例如在上述示例中的merge
冲突期间),除非您打算检查所有 来自HEAD
或some_branch
的文件分别位于path/to/some/dir
目录中,并用这些文件覆盖本地文件,因此不只是从一侧接受冲突的更改或其他。
换句话说,在解决冲突的过程中,这样:
好:
# GOOD :)
# Accept all conflicts from one side or the other (while still
# merging changes from both sides into one, in the event of being
# in the middle of a `git merge`).
git checkout --ours -- path/to/some/dir
# OR
git checkout --ours -- path/to/some/file
将接受来自--ours
方面的只是更改,如果这是我们想要的,这总是好的和安全的,而这个:
不好:
# BAD :(
# OVERWRITE all files with these files from `some_branch` instead,
# thereby _losing_ any changes and/or files contained in the other
# side but which are not in `some_branch`.
git checkout some_branch -- path/to/some/dir
# OR
git checkout some_branch -- path/to/some/file
将完全签出并覆盖指定的整个目录或整个文件,而不是仅冲突更改本身。这意味着您可能无意中删除了更改通过使用git checkout some_branch
而不是使用git checkout --ours
或git checkout --theirs
解决冲突来进行全面检查。 因此,建议在解决冲突时使用git checkout --ours -- file_or_dir_paths
或git checkout --theirs -- file_or_dir_paths
,而不是git checkout some_branch -- file_or_dir_paths
,例如git merge
、git cherry-pick
、@ 987654462@,或git revert
。
但是,如果您确实运行 git checkout some_branch -- file_or_dir_paths
是因为您了解这种行为并且这就是您想要的,那么您也需要注意这一点:检查整个目录不会删除该目录中的本地文件在some_branch
中不存在,就像您期望的那样。相反,如果它们存在于本地但不在 some_branch
中,则它将它们单独留在本地文件系统中。 因此,您必须改为手动删除所有这些文件。 请记住这一点,否则最终会让您非常困惑。在我的其他答案here (this answer has a good solution to that too) 和here 中阅读更多相关信息。
更进一步/附加说明和提示:
-
上述文件冲突解决示例可适用于任何各种类型的操作(
git merge
、git cherry-pick
、git rebase
、git revert
等)中发生冲突的情况,除非您需要请记住theirs
和ours
对每种冲突解决方案的含义,如上所述。
您也可以只使用master
或feature_branch
等分支名称代替-X ours
/-X theirs
和--ours
和--theirs
。警告:传递分支名称可能看起来更容易和更清晰,但可能会对您的更改造成危险,因为这样做会导致完整文件或目录替换,而不是仅针对文件内冲突的冲突解决方案。请参阅上面的“警告警告”部分。但是,如果您确实想要进行完整的文件替换,而不是仅仅接受来自一侧或另一侧的冲突更改,则方法如下:
# See "WARNING WARNING WARNING" section above.
git checkout feature_branch -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
您还可以检查整个目录,而不是单独指定文件!请参阅 this answer here 和 my answer here 和 my other answer here。我刚刚测试了它。这也有效。但是,请再次注意上面的“WARNING WARNING 警告”部分。这会进行完整的目录替换,而不仅仅是接受整个文件夹的一侧或另一侧的冲突更改:
# Check out ALL files from feature_branch which are in
# directory "path/to/dir". See "WARNING WARNING WARNING"
# section above.
git checkout feature_branch -- path/to/dir
记住,如果你故意使用git checkout feature_branch -- path/to/dir
,期望/希望它会删除目录path/to/dir
中不存在于feature_branch
的本地文件,它不会。在运行checkout
命令之前或之后,您必须在本地文件系统中手动删除这些文件。在我的答案中阅读更多信息:
-
All about checking out files or directories in git
How to do a
--soft
or --hard
git reset by path
参考资料:
-
[我的答案,我一直在参考!] “关于在 git 中检出文件或目录的全部内容”:How to get just one file from another branch
[我的回答]Why git can't do hard/soft resets by path? --> 尤其请参阅末尾“背景知识”部分中的git 术语和定义!
[我自己对
git revert
期间“他们”和“我们”的含义的回答]Who is `them` and `us` in a `git revert`?
[@LeGEC 的回答指出“我们的/我们”始终是 HEAD
,而“他们/他们的”始终是另一个分支或提交]Who is `them` and `us` in a `git revert`?
[关于我对git merge
和git rebase
的理解,我交叉核对的预先存在的答案] Who is "us" and who is "them" according to Git?
来自man git checkout
:“请注意,在 git rebase 和 git pull --rebase 期间,我们的和他们的可能会出现交换”:[回答我的问题]:您也可以将目录路径传递给 git 以检查整个目录中的所有文件,而不必单独指定文件:How do I accept git merge conflicts from "their" branch for only a certain directory? [我的回答]git checkout --ours when file spec includes deleted file 用于绘制漂亮的 ASCII 图片或图表以放入代码中:https://asciiflow.com/。--ours, --theirs When checking out paths from the index, check out stage #2 (ours) or #3 (theirs) for unmerged paths. Note that during git rebase and git pull --rebase, ours and theirs may appear swapped; --ours gives the version from the branch the changes are rebased onto, while --theirs gives the version from the branch that holds your work that is being rebased. This is because rebase is used in a workflow that treats the history at the remote as the shared canonical one, and treats the work done on the branch you are rebasing as the third-party work to be integrated, and you are temporarily assuming the role of the keeper of the canonical history during the rebase. As the keeper of the canonical history, you need to view the history from the remote as ours (i.e. "our shared canonical history"), while what you did on your side branch as theirs (i.e. "one contributor’s work on top of it").
补充研究:
-
[
【讨论】:
在我看来,错误在于动词rebase
的奇怪语法。当您说git merge feature
时,您是说将功能分支合并到我(无论我是谁),但是当您说git rebase master
时,您是说将我(无论我是谁)重新定位到主分支。因此,直接宾语和间接宾语可以说是互换了。我们和他们的逆转直接由此而来。
git rebase -m
怎么样?手册页似乎暗示“我们”和“他们”在这种情况下是相反的。
@imz--IvanZakharyaschev,我正在阅读man git rebase
中的-m
描述,并查看我刚刚添加到答案中的git rebase
图表,不,git rebase upstream
和git rebase -m upstream
对us
和them
或ours
和theirs
具有相同的定义。当他们说“换句话说,双方交换了”时,我认为他们在说的是,正如我已经在回答中解释的那样,双方似乎在git rebase
方面通常被交换,而不是一般为git merge
,但git rebase
的侧面与git rebase -m
的侧面相同。以上是关于根据 Git,谁是“我们”,谁是“他们”?的主要内容,如果未能解决你的问题,请参考以下文章