How do you deal with a public repository that has already been rebased?

前端 未结 2 2087
無奈伤痛
無奈伤痛 2021-01-01 05:15

This question has likely been asked before but I\'m having a heck of a time searching for it.

Very frequently, experienced git users will say \"do not <

相关标签:
2条回答
  • 2021-01-01 05:32

    You should do (as in "How do I recover/resynchronise after someone pushes a rebase or a reset to a published branch?"):

    git checkout yourBranch
    git branch old-upstreamBranch D # BEFORE fetching!
    
               E--F (origin/upstreamBranch)
              /
    A--B--C--D (old-upstreamBranch)
              \
               X--Y--Z (yourBranch)
    
    
    git fetch
    
    
         L--M--N--O (origin/upstreamBranch)
        /
    A--B--C--D (old-upstreamBranch)
              \
               X--Y--Z (yourBranch)
    

    At this point, you cannot simply rebase your branch on top of the rewritten upstreamBranch: that would introduced C and D which were explicitly left asside when upstreamBranch was rewritten.

    You need to rebase just yourBranch, and not the commits before it.
    That is why you put an old-upstreamBranch tag before fetching:

    git rebase --onto origin/upstreamBranch old-upstreamBranch yourBranch
    
                    X--Y--Z (yourBranch)
                   /
         L--M--N--O (origin/upstreamBranch)
        /
    A--B--C--D (old-upstreamBranch)
    
    
    
    git branch -D old-upstreamBranch 
    
                    X--Y--Z (yourBranch)
                   /
         L--M--N--O (origin/upstreamBranch)
        /
    A--B
    

    But: With Git 2.0, all you will need to do is:

    fork_point=$(git merge-base --fork-point origin/upstreamBranch yourBranch)
    # return D
    git rebase --onto origin/upstreamBranch $fork_point yourBranch
    

    No more "making a branch before fetching!

    If you realize the issue after git fetch, you can still rebase your branch nicely on top of the rewritten upstream branch.


    Since commit ad8261d from John Keeping (johnkeeping), git rebase can use that same new --fork-point option.

    0 讨论(0)
  • 2021-01-01 05:39

    There's a section in the git-rebase manpage called "RECOVERING FROM UPSTREAM REBASE" that describes this quite well.

    Fetching

    For your specific case, let's walk through what happens. If you simply fetch, all that's doing is updating your remote tracking branch. That still works fine - it will give you a warning, though:

    From <url>:
     + abc123...def456 master    -> origin/master  (forced update)
    

    Pulling

    A pull, whether normal (merging) or rebasing, calls fetch internally. So if you haven't already fetched, you will see the "forced update" warning. If you have fetched already, the forced update warning was given before, and there's no forced update to warn about now. You could also miss the forced update warning if there's a large diff report as part of the merge after it. After fetching, git-pull then calls merge or rebase as requested. This is where it gets really scary. As far as Git is concerned now, commits C and D are just local commits now. It doesn't matter that at one point they were published. So Git will merge/rebase just like it normally would.

    The merge:

    - A - B - L - M - N - O (origin/master)
       \                   \
        C - D - X - Y - Z - Q (master)
    

    The rebase:

     - A - B - L - M - N - O (origin/master) - C' - D' - X' - Y' - Z' (master)
    

    Now, either one could very well run into conflicts, if the removal of commits C and D makes the later commits different. Merge conflicts are the only other errors you'd notice. But if they were isolated changes, then everything goes "fine". If the user doesn't notice, then the might subsequently push this history, and thus reintroduce the removed commits! Sometimes this could be relatively benign. Maybe L is actually C with a typo fixed in the commit message, and MNO are identical to DEF, just with different parents. So reintroducing C and D would do nothing more than clutter the history. But maybe it's horribly dangerous: maybe removing C fixed some terrible bug, and it's just been reintroduced.

    Avoiding the problem

    And this is why it's a really bad idea to rewrite published history. The best case is ending up with some duplicated commits, and the worst case is a user unknowingly breaking everything. So if it does happen, the very first thing you have to do is tell everyone and direct them how to recover. The next thing is to vigilantly monitor everything subsequently published to make sure it doesn't reintroduce the old commits. If you're on the other side of this, fetching from a public repo, hopefully you trust the maintainers enough to never rebase the stable branch, or to tell you if they do. If you don't trust them, then it's good practice to fetch then merge/rebase, rather than pulling, so that you can notice the "forced update" message and proceed very carefully.

    Recovering locally

    The details of how to recover vary; I recommend the aforementioned manpage. It's hard to tell exactly what you need to do - it depends on whether C was rewritten into L (and maybe D into M) or they're entirely new commits, and on whether you want to rebase or merge. If you just want to rebase, rebasing XYZ onto O would be sufficient. If you want to merge, you'd rebase XYZ onto either B or M, then merge O.

    0 讨论(0)
提交回复
热议问题