根据Git,谁是“我们”和谁是“他们”?

248
在Git rebase之后,以及其他情况下,您可以在git status报告中找到一些文件被标记为deleted by us。根据Git,us是谁,为什么会这样呢?
它是指我坐在这个分支上并且它适用于我吗?还是指Git本身和我正在进行rebase的分支上的人?

8
很奇怪,似乎“被他们删除”表示你在正在变基的分支上删除了文件,而“被我们删除”则表示其他人删除了它。与此相反,git merge 给出了相反的信息。 - Fred Foo
可能是git rebase, keeping track of 'local' and 'remote'的重复问题。 - user82216
2个回答

237

当你进行 合并 操作时,us 指的是你要合并的分支,而不是要被合并的分支 them

当你进行 变基 操作时,us 指的是上游分支,而them 指的是你正在移动的分支。这在变基操作中有点反直觉。

原因在于 Git 在变基操作中使用相同的合并引擎,实际上是将你的内容 cherry-pick 到上游分支中。us = into(进入),them = from(来自)。


68
实现 的角度来看,使用 rebase 可以利用合并机制,其中 “ours” 分支是上游分支,“theirs” 分支则是正在被 rebase 的分支。虽然我同意这种实现方式,但“极其不友好”似乎是一个委婉的说法。我更愿意将分支标记为其他单词而不是“us/ours”和“them/theirs”,例如可以根据分支名称进行标记:“在主分支中删除,在特性分支中修改”。 - torek
4
在rebase时,我经常会把--theirs--ours搞混,这太不直觉了。希望他们有一天能够修复这个问题。 - iosdude
1
如果只涉及一个分支,例如在rebase期间重新排序或压缩提交记录时,该怎么办? - Justin Johnson
25
可以将us简单地理解为助记符,代表着“[u]p[s]tream”,而不是正常的英文单词“我们”。 - kojiro
1
你能否也为 git revert 的情况进行澄清吗?我在这里添加了这个问题:https://dev59.com/Gb3pa4cB1Zd3GeqPakL0。 - Gabriel Staples
显示剩余3条评论

87

根据Git,"我们(us)"和"他们(them)"是谁?

(这也回答了问题:"git rebase 是如何工作的,它具体做了什么?")

当在 git 的 mergecherry-pickrebaserevert 中间发生冲突时,您可能需要选择一方来保留冲突内容,可以选择从 --ours--theirs 一侧保留有冲突的内容(同时保留双方的非冲突内容),这就引出了以下问题:在每种情况下,“我们”和“他们”分别代表谁?

解决冲突可能涉及以下步骤:

git checkout master
git merge feature_branch
git checkout --theirs -- path/to/some/dir
git add path/to/some/dir 
git status 
git merge --continue 

但是,git checkout --theirs 这行代码是做什么的?我们还有哪些其他选项呢?以下是一些选项:

# ------------------------------------------------------------------------------
# Do this:
# - Generally, one of these might be useful during conflict resolution:
# ------------------------------------------------------------------------------

# Integrate changes from both sides, but in the lines which conflict, keep the
# `--theirs` changes for all conflicts within files inside this directory.
git checkout --theirs -- path/to/some/dir

# OR: Integrate changes from both sides, but in the lines which conflict, keep
# the `--ours` changes for all conflicts within files inside this directory.
git checkout --ours -- path/to/some/dir

# ------------------------------------------------------------------------------
# Do *not* do this:
# - Generally, do *not* do this during conflict resolution:
# ------------------------------------------------------------------------------

# Discard all changes from both sides by hard resetting all the contents inside of
# this directory back to their state at the `some_branch` commit. 
# - But, oddly enough, this does *not* do deletions of files inside this directory
#   which don't exist at this `some_branch` commit but are there now. So, this
#   isn't even a proper hard reset of this directory. It's a mishmash. 
git checkout some_branch -- path/to/some/dir

请参考下方的“最佳冲突解决示例”以及“示例用例”部分中的所有示例,了解详细信息。然而,请注意“警告!警告!警告!”部分。在大多数情况下,您不应尝试使用git checkout some_branch -- path/to/some/dir来解决冲突,因为这会丢弃整个mergecherry-pickrebaserevert,从而撤消一方的所有更改,而不是保留两方之间的更改并偏向其中一方的行。因此,通常情况下,使用如上所示的--ours--theirs是正确的操作方式,而不是使用some_branch

请仔细研究我回答底部的那些部分以获取详细信息。

现在进入主要回答:

在所有情况下:

在粗略的俗语中(谢谢,@user20358):
"我们"是已经存在于代码库中的代码,而"他们"是你试图合并进来的代码。
更详细地说:
1. "我们"(或者"ours")= 当前检出的提交(`HEAD`)在Git执行导致冲突的操作时的状态(稍后会详细解释); 2. "他们"(或者"theirs")= 另一个提交,在Git执行导致冲突的操作时,并未被检出的状态(稍后会详细解释)。
重要提示:在引起冲突的操作时,HEAD 不一定是您输入 git 命令时的 HEAD。这一点非常重要。Git 在运行引起冲突的操作之前可能会进行一些检出和更改 HEAD(检出的提交),从而使得对于未经训练的人来说,“我们”和“他们”看起来被交换或颠倒。
详细介绍四种情况:合并、挑选、变基、还原:
1. git merge(直观): - 示例命令: ``` git checkout master git merge feature_branch # 将 feature_branch 合并到 master ``` - "us"/"ours" = HEAD,即 master,因为在运行 git merge feature_branch 时你在 master 分支上。 - "them"/"theirs" = feature_branch,即你要合并到 master 的分支。
2. git cherry-pick(直观): - 示例命令: ``` git checkout feature_branch git cherry-pick some_commit # 将 some_commit 应用到 feature_branch ``` - "us"/"ours" = HEAD,即 feature_branch,因为在运行 git cherry-pick some_commit 时你在 feature_branch 分支上。 - "them"/"theirs" = some_commit,即你要将其 cherry-pick 到 feature_branch 上的提交。
3. git rebase(反直观,但一旦理解了其工作原理就很有道理): - 示例命令: ``` git checkout feature_branch git rebase master # 将 feature_branch 变基到最新的 master ``` - 这个过程的图示(在 https://asciiflow.com 绘制),最新的或最新的提交在顶部和/或右侧: ``` # 变基之前:feature_branch # 在此期间 feature_branch 接收了新的提交, # master 也接收了新的提交 # # master # x # | feature_branch # x y # | | # x y # | / # git merge-base ────► x--y--y--y # master feature_branch | # x # # # 变基之后:feature_branch 已经完全被 cherry-pick 到 # master 的末尾 # # feature_branch # y' # | # y' # / # y'--y'--y' # | # master x # | # x # | # x # | # x # | # x ``` - "us"/"ours" = HEAD,即上游分支:最初是 master 上的最后一个 x 提交,然后是此后的一些新提交 y',通过 cherry-pick 添加到其中(这个有点棘手!)。这是因为当你输入 git rebase master 时,git 首先检出 master 作为开始进行 cherry-pick 的起点,然后确定从 feature_branch 中要 cherry-pick 的提交(即你的 feature_branch 提交中哪些尚未在 master 上)。它通过找到 merge-base(即 feature_branchmaster 共有的提交,可以使用 git merge-base master feature_branch 找到),然后从此 merge-base 之后的第一个提交开始,逐个工作,直到 feature_branch 上的最后一个提交,将这些提交 cherry-pick 到 master 的末尾,从而将你添加到 feature_branch 的所有新的 y 提交“变基”到最新的 master 上,作为新的 y' 提交。因此,"us"/"ours" = HEAD示例用例:

合并冲突解决示例:

# 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

这里还有一些。这些是我最常用的技巧,而不是刚才提到的两个例子。
# 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 

处理“路径没有我们的版本”或“路径没有他们的版本”错误:
如果你遇到类似这样的情况:
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
问题在于这些错误的文件是我们这边的已删除文件,所以在运行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/dirgit checkout some_branch -- path/to/some/dir,除非您打算从HEADsome_branch检出所有文件,并将本地文件覆盖为那些文件,从而不仅接受一方或另一方的冲突更改。

换句话说,在冲突解决过程中,以下操作是不推荐的:

好的。
# 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

只接受来自我们一方的变更,这样做总是好的和安全的,如果这正是我们想要的。而这个例子则不好:
# 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”,“git rebase”或“git revert”等操作。

然而,如果您运行git checkout some_branch -- file_or_dir_paths是因为您理解这种行为并且那是您想要的,那么您也需要了解以下内容:像您期望的那样,这样检出整个目录不会删除本地文件系统中在some_branch不存在的目录下的本地文件。相反,如果它们在本地存在但在some_branch不存在,它会保留它们在您的本地文件系统中。因此,您必须手动删除所有这些文件。请记住这一点,否则最后会让您非常困惑。在我的其他回答(这个回答有一个很好的解决方案)这里中了解更多。

进一步/附加说明和提示:

上述文件冲突解决示例适用于各种操作(`git merge`、`git cherry-pick`、`git rebase`、`git revert`等)中的冲突情况,除了需要记住每种冲突解决类型中`theirs`和`ours`的含义,如上所述。
您还可以直接使用分支名称(例如`master`或`feature_branch`)代替`-X ours`/`-X theirs`和`--ours`以及`--theirs`。警告:传递分支名称可能更容易和清晰,但对于您的更改可能存在危险,因为这种方式会进行完全的文件或目录替换,而不仅仅是解决文件内的冲突。请参阅上面的“警告警告警告”部分。然而,如果您确实想要进行完整的文件替换,而不仅仅是接受一方或另一方的冲突更改,可以按照以下步骤进行:
#参见上面的“警告警告警告”部分。 git checkout feature_branch -- path/to/somefile1.c path/to/somefile2.c path/to/somefile3.c
您还可以检出整个目录,而不是逐个指定文件!参见此处的答案我的答案我的其他答案。我刚刚测试过,这也可以工作。然而,再次注意上面的“警告警告警告”部分。这将进行完整的目录替换,而不仅仅是接受一方或另一方的冲突更改以及整个文件夹中的所有内容:
#从feature_branch检出位于"path/to/dir"目录中的所有文件。请参阅上面的“警告警告警告”部分。 git checkout feature_branch -- path/to/dir 请记住,如果您有意使用`git checkout feature_branch -- path/to/dir`命令,希望删除不存在于`feature_branch`中的目录`path/to/dir`中的本地文件,那么这是行不通的。您必须在运行`checkout`命令之前或之后手动删除本地文件系统中的这些文件。在这里阅读更多信息:
1. 关于在git中检出文件或目录的全部内容 2. 如何通过路径执行`--soft`或`--hard` git重置

参考文献:

  1. [我一直参考的答案!] "关于在git中检出文件或目录的全部内容": 如何从另一个分支获取单个文件
  2. [我的答案] 为什么git无法按路径进行硬重置/软重置? --> 特别要看末尾的"背景知识"部分的git术语和定义!
  3. [关于`git revert`中"them"和"us"的解释,我的回答] `git revert`中的`them`和`us`是谁?
  4. [@LeGEC指出"ours/us"始终是HEAD,而"them/theirs"始终是另一个分支或提交] `git revert`中的`them`和`us`是谁?
  5. [我核对的现有回答,涉及我对git mergegit rebase的理解] 根据Git,“us”和“them”分别代表谁?
  6. 来自man git checkout的说明:"请注意,在git rebase和git pull --rebase期间,ours和theirs可能会互换":
    --ours,--theirs
        当从索引中检出路径时,检出未合并路径的第2阶段(ours)或第3阶段(theirs)。
    
        请注意,在git rebase和git pull --rebase期间,ours和theirs可能会互换;
        --ours提供了重叠更改所基于的分支的版本,而--theirs提供了保持你工作的分支的版本。
    
        这是因为rebase用于以远程历史记录作为共享规范并将在你正在进行rebase的分支上进行的工作视为要集成的第三方工作的工作流程,
        在rebase过程中,你暂时扮演了规范历史记录的保管者角色。作为规范历史记录的保管者,你需要将远程历史记录视为我们的(即“我们共享的规范历史记录”),
        而将你在自己分支上所做的视为他们的(即“一个贡献者在其之上的工作”)。
    
  7. [回答我的问题]:你也可以将目录路径传递给git来检出整个目录中的所有文件,而不必逐个指定文件:如何只接受“their”分支中特定目录的git合并冲突?
  8. [我的答案] 当文件规范包括已删除文件时,使用git checkout --ours
  9. 用于绘制漂亮的ASCII图片或图表以放置在代码中:https://asciiflow.com/

进一步学习:

  1. [我仍然需要自己学习这个答案!——已完成;我已经弄清楚并更新了我的答案,截至2020年1月7日] `git revert`中的`them`和`us`指的是谁?
  2. [我仍然需要学习和阅读这个] `git checkout --ours`不会从未合并文件列表中删除文件

另请参阅:

快速链接到我经常参考并认为是“git基础知识”的答案:
  1. 各种在git中从另一个分支创建分支的方法
  2. 关于在git中检出文件或目录的一切
  3. 根据Git,"我们"和"他们"是谁?

2
问题在于rebase这个动词的语法比较奇怪。当你输入git merge feature时,你是在说要将feature分支合并到我(无论我是谁)身上,但是当你输入git rebase master时,你是在说要将我(无论我是谁)变基到master分支上。因此,直接宾语和间接宾语被交换了。我们和他们的颠倒顺序也是由此而来。 - matt
git rebase -m 怎么样?手册似乎暗示在这种情况下“我们”和“他们”是相反的。 - imz -- Ivan Zakharyaschev
根据我所阅读的man git rebase中的-m描述以及我刚刚添加到答案中的git rebase图表,git rebase upstreamgit rebase -m upstream对于usthemourstheirs有相同的定义。当他们说“换句话说,双方被交换了”时,我认为他们是在说,就像我已经在我的答案中解释的那样,与一般的git merge相比,一般的git rebase似乎会交换双方,但是对于git rebase -m,双方的位置与git rebase相同。 - Gabriel Staples
2
这句话如果改成“我们”是指存储库中已经存在的代码,“他们”是指我正在尝试合并的代码,它会更简单些吗?这样说对吗? - user20358
1
@GabrielStaples 对不起,如果我让某些事情变得更加模糊了。我当时处于一种情况下,希望确保完全还原之前引入的更改,最后我选择了 git revert fed8934 --strategy-option=theirs。所以与我写的相反,我撤销了编辑。我认为你的回答在“revert”方面可能更直接,但我很难用适当的措辞表达出来。 - slhck
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接