如何在重写特定的本地冲突提交上游代码时重新绑定rebase [英] How to git rebase while overriding specific local conflicting commits with upstream code

查看:152
本文介绍了如何在重写特定的本地冲突提交上游代码时重新绑定rebase的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们分叉和开源项目,并在其上开发功能。这些功能特别是分支



现在我试图用新的上游分支重新设置我们的代码。重新发布失败了错误消息中的许多冲突。我希望覆盖所有与传入提交的错误消息,但我无法找到如何做到这一点。



从我发现的,以下命令将覆盖与上游代码

  git rebase -Xours upstream / branch 

但我希望非常特别地覆盖那些不在代码附近但仅限于文档的提交



通过任何方式实现此目的?

解决方案

注意:这是一个有点修改的问题的答案(请参阅long评论主题)。这是修改过的问题:

lockquote

我们在本地存储库中创建了一些分支 B ,它基于上游存储库及其分支 upstream / U ,用于标识提交 1234567



上游存储库已由其所有者更新,现在 upstream / U 标识提交 89abcde 。在 1234567 89abcde 之间有很多提交和许多更改。



我们选择重新分配我们的分支 B ,它有很长的提交列表,所以我们所有的提交都会在 89abcde之后应用。也就是说,我们现在有:

  ...-- o  -  1234567  -  o  -  o-- .. - ○ -  89abcde<  - 上游/ U 
\
B1 - B2 - B3 - ... - B1000< -B
< code>

但最终会有:

  B1  -  B2  -  B3 --...  -  B1000 [弃用] 
/
...-- o - 1234567 - o - o - ... - ○ - 89abcde< - 上游/ U
\
B1'-B2'-B3'-...- B1000'< - B

因此,我们通过这样做开始了这个操作:

  git checkout B 
git rebase upstream / U

一路上,我们遇到了很多情况,在复制commit B1 B1 时, B2 B2 等等,我们会发生冲突。

解决这些冲突有时候意味着带走你文件的pstream版本。例如,所有文档文件都是如此。在某些情况下,如果唯一的文件冲突被命名为 .gitreview



如果我们运行 git checkout --ours .gitreview 取出他们的文件版本,并试图继续这样做,Git说:



<$ p $ git checkout --ours`.gitreview`
#git add .gitreview
#git rebase --continue
应用:Update .gitreview for stable / mitaka
没有变化 - 你忘了使用'git add'吗?

这意味着什么?我们应该如何继续?


现在,如问题中所示, git rebase 所做的是复制每个提交, B1 B2 ,..., B1000 ,对于不在分支 upstream / U 上的分支 B 上的每个提交。要复制一个提交,Git本质上运行 git cherry-pick (某些变体 git rebase 使用此)或 git format-patch 后跟 git am



一个将每个提交变成一个差异(版本控制系统术语中的变更集),然后可以将其应用于其他提交。当使用 git cherry-pick 时,此变更集将通过Git的三向合并机制应用,合并基础是樱桃选择提交的父项。这两个分支提交提交是当前提交,它是左侧或本地或 - 我们提交,我称之为 L ,并且该提交被选为右侧或远程或 - 他们的提交我称为。



请注意,这些变更集一次应用一次,之后Git会提交新的提交。 Git首先检查commit 89abcde ,并使用Gi​​t所称的分离HEAD模式,以便提交 89abcde 为索引和工作树中有什么,并且根本没有当前分支。然后Git会挑选提交 B1 。由于 current 提交是 89abcde ,所以我们将上游代码称为 - 我们的本地 L 版本。由于现在应用的提交是 B1 ,我们将从分支 B 引用我们自己的代码为 - 他们的远程 R 版本。 B1变更集已成功应用于 89abcde ,Git使用来自commit <$ c的日志消息进行新的提交$ C> B1 。这个新的提交现在是 B1',我们分离的HEAD现在指向这个新的 B1'

  B1  -  B2  -  B3 --...  -  B1000 < -  B 
/
。 ... - o - 1234567 - o - o - ... - o - 89abcde< - upstream / U
\
B1'< - HEAD(分离)

此过程现在重复 B2 ,并且一遍又一遍地重复多次提交(在这个例子中为1000),直到我们精疲力尽并且彻底厌恶Git。 : - )



使用 git am 时,变更集的应用方式稍有不同,但通常结果是一样。关键的区别是,Git 不立即采用三路合并;相反,它首先尝试直接将diff应用于 git format-patch 输出中指定的文件。如果diff无法应用,那么然后 Git提取diff未能应用的合并版本的文件。 diff将正确应用于merge-base版本,生成该文件的 R 版本。 ( L 版本只是现有工作树中的版本。)



一样git cherry-pick merge base 版本是该文件的任何版本存储在我们现在正在复制的提交的 parent 提交中。因此,当回退时,Git将合并基本版本与HEAD版本区分开来。

现在让我们看看 .gitreview 。假设我们正在复制commit B11 。文件 .gitreview 包含提交 B11 foo $ c>,但它在我们作为 B10'的提交中包含不同的分支名称 bar 。 Git将 B11 B10 区别开来,标识为 .gitreview 已更改,因此它处于变更集中。事实上,它是变更集中的 only 文件: B11 完全包含对 .gitreview

我们现在选择不接受我们的修改,所以我们运行: 1

 #git checkout --ours .gitreview 
#git add .gitreview
#git rebase --continue

并获得投诉。



我们得到此投诉的原因是, .gitreview 文件仅用于 更改。通过使用 git checkout --ours .gitreview 来提取随提交的 .gitreview 版本c> B10',而不是与 B11 一起使用的版本,我们使当前的索引和工作树匹配commit B10'准确。



Git注意到索引和工作树没有任何变化,与<$ c相比$ c> HEAD (它是 B10'),并且抱怨。

现在解决方案是运行:

 #git rebase --skip 

它告诉Git实际上,我们不再需要提交B11:只需从提交列表中删除它即可复制。



Git将继续提交 B12 。这可能会改变为 .gitreview ,或者它可能不会。如果没有,我们不会在那里看到任何问题。



如果 B12 / em>改变为 .gitreview ,我们可能会发生冲突,或者我们可能不会,具体取决于:


  1. 我们正在做 git am git cherry-pick

  2. 如果我们做的是 git am ,那么这个补丁是否适用于或不适用?

  3. 由于 git cherry-pick ,或者因为修补程序没有在(2)中清楚地应用,是否存在合并冲突 我们现在有:

      B1  -  B2  -  B3 --...-- B1000 < -  B 
    /
    ...-- o - 1234567 - o - o --...-- o - 89abcde< - upstream / U
    \
    B1'-...-- B10'-B12'< - 头(分离)

    请注意 B11 是如何跳过的。 Git会继续复制 B13 ,依此类推。



    请记住,在所有情况下,所有Git都在做的是逐行应用diffs。它不知道这些差异是什么意思。它只知道它们可以被应用,或者不可以;或者它们相互冲突(通过三路合并合并两个差异时),或者不相互冲突。



    每个文件,每次提交可能会发生一次合并冲突。因此,如果有1000次提交复制,并且每次提交中有1000个文件,那么您的可能会有高达1百万次合并冲突。然而,大多数提交最多可以更改几个文件,而且大多数更改都适用于干净或没有合并冲突;所以很可能每个合并最多只有几个冲突,最多只能解决几千个冲突。



    然而,您可能想要使用一些策略,而不是复制所有1000个(或者很多)提交。






    1 这些提示稍微关注我:他们建议您以用户 root 的方式执行所有操作。对于非root用户,通常的sh / bash提示符以 $ 结尾。 除了root之外的其他人,尽可能地活着是最明智的。

    We forked and opensource project and developed features on top of that. These features are in particular branch

    Now I am trying to rebase our code with new upstream branch. The rebase fails many conflicts in error messages. I wish to override all the error messages with incoming commits but I could not find how to do that.

    From what I found, the following command would override all conflicts with upstream code

    git rebase -Xours upstream/branch
    

    But I wish to very specifically override those commits which are not around code but documentation only

    Any way to achieve this ?

    解决方案

    Note: this is an answer to a somewhat modified question (see the long comment thread). Here's the modified question:

    We have some branch B we made in a local repository, based on an upstream repository and its branch upstream/U, which used to identify commit 1234567.

    The upstream repository has been updated by its owner, and now upstream/U identifies commit 89abcde. There are many commits and many changes between 1234567 and 89abcde.

    We have chosen to rebase our branch B, which has a long list of commits, so that all of our commits will be applied after 89abcde. That is, we now have:

      ...--o--1234567--o--o--...--o--89abcde   <-- upstream/U
                   \
                    B1--B2--B3--...--B1000   <-- B
    

    but will, in the end, have:

                    B1--B2--B3--...--B1000   [abandoned]
                   /
      ...--o--1234567--o--o--...--o--89abcde   <-- upstream/U
                                        \
                                         B1'-B2'-B3'-...--B1000'  <-- B
    

    Therefore, we started this operation by doing:

    git checkout B
    git rebase upstream/U
    

    Along the way, though, we hit a lot of cases where, while copying commit B1 to B1, B2 to B2, and so on, we get a conflict.

    Resolving these conflicts sometimes means "take the upstream version of the file". This is true for all the documentation files, for instance. It's also true for some cases where the only file conflicting is named .gitreview.

    If we run git checkout --ours .gitreview to take their version of the file, and attempt to continue like this, Git says:

    # git checkout --ours `.gitreview`
    # git add .gitreview
    # git rebase --continue
    Applying: Update .gitreview for stable/mitaka
    No changes - did you forget to use 'git add'?
    #
    

    What does this mean and how should we proceed?

    Now, as shown in the illustration within the question, what git rebase is doing is copying each commit, B1, B2, ..., B1000, for every commit that is on branch B that is not on branch upstream/U. To copy a commit, Git essentially runs either git cherry-pick (some variants of git rebase use this) or git format-patch followed by git am.

    Either one turns each commit into a diff (a changeset, in Version Control System terms) that can then be applied to some other commit. When using git cherry-pick this changeset will be applied through Git's three-way merge machinery, with the merge base being the parent of the cherry-picked commit. The two branch tip commits are the current commit, which is the left side or "local" or --ours commit that I call L, and the commit being cherry-picked as the right side or "remote" or --theirs commit that I call R.

    Note that these changesets are applied one at a time, after which Git makes a new commit. Git starts by checking out commit 89abcde, using what Git calls its "detached HEAD" mode, so that commit 89abcde is what is in the index and work-tree and there is no current branch at all. Git will then cherry-pick commit B1. Since the current commit is 89abcde, we will refer to the upstream code as --ours or the local or L version. Since the commit being applied now is B1, we will refer to our own code from branch B as --theirs or the remote or R version.

    Assuming the changeset from B1 is successfully applied to 89abcde, Git makes a new commit, using the log message from commit B1. This new commit is now B1', and our detached HEAD now points to this new B1':

                    B1--B2--B3--...--B1000   <-- B
                   /
      ...--o--1234567--o--o--...--o--89abcde   <-- upstream/U
                                        \
                                         B1'  <-- HEAD (detached)
    

    The process now repeats with B2, and repeats over and over again for as many commits as there are to copy (1000, in this example), until we are exhausted and thoroughly sick of Git. :-)

    When using git am, the change-set is applied slightly differently, but usually the result is the same. The key difference is that Git doesn't immediately resort to the three-way merge; instead, it first attempts to apply the diff directly, to the file named in the git format-patch output. If the diff fails to apply, then Git extracts a merge-base version of the file to which the diff failed to apply. The diff will apply correctly to the merge-base version, producing the R version of the file. (The L version is simply the one in the existing work-tree.)

    As with git cherry-pick, the merge base version is whatever version of that file is stored in the parent commit of the commit we're copying right now. So when falling back, Git diffs the merge base version against the HEAD version.

    Let's look now at what happens with .gitreview. Suppose we're copying commit B11. The file .gitreview contains the name of a branch foo in commit B11, but it contains a different branch name bar in the commit we made as B10'. Git has, in diffing B11 against B10, identified .gitreview as changed, so it's in the changeset. In fact, it's the only file in the changeset: B11 consists entirely of a change to .gitreview.

    We now choose not to take our change, so we run:1

    # git checkout --ours .gitreview
    # git add .gitreview
    # git rebase --continue
    

    and get the complaint.

    The reason we get this complaint is that this modified .gitreview file was the only change. By using git checkout --ours .gitreview to extract the version of .gitreview that goes with commit B10', rather than the version that goes with B11, we've made the current index-and-work-tree match commit B10' exactly.

    Git notices that there's nothing changed in the index and work-tree as compared with HEAD (which is B10'), and complains.

    The solution at this point is to run:

    # git rebase --skip
    

    which tells Git, in effect, we no longer want commit B11 after all: just drop it from the list of commits to copy.

    Git will go on to commit B12. This may have a change to .gitreview, or it may not. If it doesn't, we won't see any problem there.

    If B12 does have a change to .gitreview, we may get a conflict, or we may not, depending on:

    1. Are we doing git am or git cherry-pick?
    2. If we're doing git am, did the patch apply cleanly, or not?
    3. If we're doing a three-way merge because of git cherry-pick, or because the patch didn't apply cleanly in (2), is there a merge conflict, or not?

    Assuming B12 copies without conflict, we'll now have:

                    B1--B2--B3--...--B1000   <-- B
                   /
      ...--o--1234567--o--o--...--o--89abcde   <-- upstream/U
                                        \
                                         B1'-...--B10'-B12'  <-- HEAD (detached)
    

    Note how B11 is simply skipped. Git will proceed with copying B13, and so on.

    Remember, in all cases, all Git is doing is a line-by-line application of diffs. It has no idea what these diffs mean. It just knows that they can be applied, or cannot; or that they conflict with each other (when combining two diffs via three-way merge), or do not.

    You can get one merge conflict per file, per commit. So if there are 1000 commits to copy, and 1000 files in each commit, you could have up to 1 million merge conflicts. Chances are that most commits change at most a few files, though, and most changes apply cleanly or have no merge conflicts; so it's pretty likely that there will be at most a few conflicts per merge, for at most a few thousand of these to resolve.

    Nonetheless, you may want to use some strategy other than copying all 1000 (or however many) commits.


    1These # prompts concern me slightly: they suggest you're doing all this as user root. The usual sh/bash prompt is or ends with $ for non-root users. It's generally wisest to "live" as much as possible as someone other than root.

    这篇关于如何在重写特定的本地冲突提交上游代码时重新绑定rebase的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆