Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I rebase all my local Git branches (and tags) when upstream has rewritten history?

Tags:

git

git-rebase

The maintainer of a specific (embedded) linux kernel repo has modified a bunch of ancient commits in order to clean up the history. As a consequence all commits have a different SHA.

When I git fetch from this rewritten history, I'm in the need of rebasing my local branch(es) to the local tracking branch entry point which corresponds to the point in the local tree where my own branches start. Standard solution:

git rebase --onto SHAxx master ownbranch

(SHAxx corresponds to c2 of remotes/origin/master in the graph below).

However, when I have several own branches that have one single ancestor in master, I have to apply a rebase for each individual branch. Instead I want to transfer all of the branches with any associated tags to the new entry point in the fetched tracking branch in a single action.

Graphically, state after fetch, before any action (simplified - beyond left is deep master history):

    c1'--c2'--c3'--c4'--c5'--c6'--c7--c8--c9 remotes/origin/master
   /
--o--c1--c2--c3--c4--c5--c6 master
           \
            o---o---o---o---o  branch1
                     \
                      o---o  branch2 (etc.)

To be precise: when my own work starts at master commit c2, I want to rebase my own subtree with all its tags (when present) in one action onto c2' of remotes/origin/master (with its distinct SHA compared to master's c2).

At that time I can remove master completely and make remotes/origin/master the new master with my own work:

    c1--c2--c3--c4--c5--c6  (old master, not referenced anymore)
   /
--o--c1'--c2'--c3'--c4'--c5'--c6'--c7--c8--c9 master = remotes/origin/master
            \
             o---o---o---o---o  branch1
                      \
                       o---o  branch2 (etc.)

Then I'd test if the build process yields the same result as before and if OK: proceed in having merged master updates incrementally (e.g. per subindex nn step in 2.6.nn) into my own (board-specific) branch.

Or is there another/better approach to realize the same result?

A possible solution is presented at Rebasing a branch including all its children, but tags aren't moved over.

like image 449
Rob Avatar asked Jul 12 '11 00:07

Rob


1 Answers

Below, a sequence of steps is presented that make use of a relatively new git replace command. Using this command instead of rebase --onto ..., no headaches due to own merges with master will occur - merges that might require conflict resolution, because rebase actually ''replays'' commits.

Remark: It seems better to clone the remote repo where history is rewritten and insert your own branches here, instead of fetching this remote repo "as usual" into your local repo and then rebase your own branches. Main advantage: any old stuff that is not your own is not present in a newly cloned repo when it is deleted or renamed in the rewritten repo. When fetching the rewritten repo into your local repo, branches that are renamed in the new history would result in further existence of old-named branches.

PROPOSED PROCEDURE

  • clone the remote repo with its rewritten history, presumably only the branch(es) that are related to your work - this helps in checking next operations.
  • specify a remote add tmp specifying the path of your current local repo.
  • on the local repo: add a tag (e.g. ownstart) at the starting point(s) of your branches (the point that is shared with master).
  • fetch your own branches from this tmp remote (some git fetch tmp <branch>:<branch>). A lot of (duplicate) commits parallel with the branch master may appear below the tag ownstart. This will disappear after the next step; alternatively you can make a new root at your ownbranch tag via grafts & git filter-branch before doing the fetch).
  • seek the new corresponding starting point where ownstart has its equivalent commit in the rewritten history and apply its SHA as follows: git replace ownstart SHA. This will create a fake connection of all your local work from this point to the appropriate entry in the new history, and let the rest of history 'below' ownstart disappear because a reference is failing.

Note 1: any logs after these operations should already show the desired commit chain (e.g. using gitk). However, the replaced commits are not melted together: they are still separate and now have a common ancestor from the rewritten history. This is inherent by design and does not affect the (correct) overall intergration of your own work. It is possible (and even recommended - see note 2) to remove these doubles by executing git filter-branch -- --master..ownstart --all (this can be time consuming). It will change the SHA's of all your own work, but the upstream commit chain is not affected. The git filter-branch operation creates backup branches. The tail of the filter-branch manpage suggests a clone operation to obtain a fresh repo without these backups (use --no-hardlinks when cloned locally). But this would turn all your own branches in tracking remotes while assigning another upstream origin.

Note 2: Upon a test sequence that was performed, any merge of a certain commit in my own branch (from an abitrary master commit) was automatically re-linked to the appropriate rewritten history commit after having executed the git filter-branch command as described. This is perfect. However, on another test this merge was 'gone' separate. It can be tied to the rewritten history by issuing git replace command(s) with SHA's where master-side parents of merges equal the appropriate commits of the master branch. Then a git-filter-branch must be repeated.

like image 161
Rob Avatar answered Sep 18 '22 14:09

Rob