Home > Blockchain >  Git branch is torn in 2 parts having individual names. How to sew them together in 1 branch?
Git branch is torn in 2 parts having individual names. How to sew them together in 1 branch?

Time:12-29

Here is the structure of my local Git repository. As one can see, there is a discontinuity, gap between branches master and remotes/tpt3/master. But there must be only 1 continious branch so that the commits of master should go after the last commit of remotes/tpt3/master and only 1 branch must contain all these commits. enter image description here When I try to use the command:

$git rebase --onto remotes/tpt3/master master

it leads to the following wrong result. enter image description here First, master should go after remotes/tpt3/master. Second, the commits of master disapperead somewhere.

Could You be so kind to tell which Git command I should use to make 1 continious branch from master and remotes/tpt3/master?

CodePudding user response:

Top note: the commands you will use here are:

git add              # insert arguments as needed here
git commit
git replace --graft master~2 tpt3/master

(inspect the result here to make sure it all worked)

git filter-branch -- master

(inspect the result again)

git for-each-ref --format="%(refname)" refs/original | xargs git update-ref -d

(inspect yet again), and:

git reset HEAD~

to remove the F' commit from master (see below), then git push tpt3 master.

Read the below carefully before doing anything, and consider making a backup of your repository before starting!


Side note: gitk is a serviceable (although not great) commit viewer but gitk images are terrible for questions on StackOverflow. I've retyped some crucial bits from that first image here, although I may have inserted any number of typographic errors:

- Local uncommitted changes, not checked in to index
* (master) Commit 22.10.21. Just verified Fe-Fe nuc...
* Initial commit.t Np=100100000 TITAN YEILD COUNT=9...

* (remotes/tpt3/master) After quarantine. STEP #29
* After Quarantine. STEP #28
* After Quarantine. STEP #27

This represents a total of five commits, in two separate (disjoint) DAGs. Each of the two graphs contains two or three commits arranged in a linear fashion. (NB: the second image shows that the first image is a lie, so caveat emptor: I'm working from the lie. You'll need to apply your own brain to the following.)

If we assign letter codes to each commit, and if we make the uncommitted work into a third commit on the current branch, we get:

F: Local uncommitted changes, not checked in to index
E: (master) Commit 22.10.21. Just verified Fe-Fe nuc...
D: Initial commit.t Np=100100000 TITAN YEIDL COUNT=9...

C: (remotes/tpt3/master) After quarantine. STEP #29
B: After Quarantine. STEP #28
A: After Quarantine. STEP #27

The two commit graphs are then:

A <-B <-C   <-- remotes/tpt3/master

D <-E <-F   <-- master

It seems like you'd like to have:

A <-B <-C   <-- remotes/tpt3/master
         \
          D <-E <-F   <-- master

You can't quite get this because commits D and E already exist and therefore cannot be changed, but they can be copied to new improved commits that are otherwise very much like D and E respectively. (Commit F doesn't actually exist yet, but you'll need to make it, to save the working tree contents, unless you do all your work in a separate clone or added working tree, neither of which is much benefit: just make commit F and then we'll copy it the same way we do with D and E. You can then boot commit F'—the copy—off the end of the chain while keeping the work in your working tree, using git reset.)

You can almost, but not quite, make the new improved commits using git rebase. You tried this already but used the wrong command:

$git rebase --onto remotes/tpt3/master master

What git rebase does can be thought of as (and depending on your Git version, now is, or else approximates) repeated cherry-picking. Each cherry-pick operation copies one commit. The crucial parts of this process are:

  1. git rebase must list out all the commits to copy. This is where your incorrect arguments are biting you now, as Leonardo Alves Machado commented. Running git rebase --onto remotes/tpt3/master requires that you then list which commits not to copy from the current branch; you say do not copy any commits up to and including the latest one on master, which results in copying no commits. Fixing this—by using git rebase remotes/tpt3/master instead—would attempt to cherry-pick (copy) all master-branch commits, which is much closer to what you want. It still won't quite work—probably—because of step 3 below.

  2. The rebase code now does a detached HEAD style checkout or switch to the target (--onto or otherwise specified) commit. In this case you want to use the remotes/tpt3/master commit, After quarantine. STEP #29.

  3. The rebase code now repeatedly runs git cherry-pick or equivalent. Each cherry-pick turns a commit into a change-set, and then copies that change-set atop the current commit and commits the result. For two of the three to-be-copied commits, E and F, this is the correct action. However, commit D has no parent, so cherry-pick will simply consider each file in commit D as added-from-scratch. This is likely to cause a big mess of add/add conflicts: each "copy" step is actually a three-way merge and this merge is likely to go wrong here.

  4. Last, having copied all the commits, git rebase wrests the branch name of the branch you were on before step 2 so that it now points to the current commit, which is the last-copied commit or—in your case where you copied no commits—the commit Git checked out in step 2.

The cure for this particular problem—where step 3 thinks of every commit as an add/add conflict-is probably to use git replace to make a graft that makes Git pretend that commit D has commit C as a parent. Once the graft is in place, you have:

A--B--C   <-- remotes/tpt3/master
       :
        D--E--F   <-- master

This sort of vague pretend link won't quite do the job, but using git filter-branch or the new git filter-repo can then "cement the graph into place" by replacing commits D, E, and F with new commits D', E', and F' that—except for having different hash IDs themselves1 and referring back to C, D', and E' respectively—are otherwise identical to D, E, and F: they have the same snapshot, and mostly the same metadata. That is, post-filter-branch / filter-repo, you now have:

A--B--C   <-- remotes/tpt3/master
       \
        D'-E'-F'  <-- master

which is what you want. You can now run git push tpt3 master safely; this will add on the three commits.

Since you probably just want two commits plus uncommitted changes, we can now use git reset to kick commit F' off master. Note that commit F' continues to exist in your repository (as do commits D-E-F) for some time, but with no easy way to find the commits, you won't see them. It will be as if they had never existed. Eventually—after 30 days or more—Git will notice that they're not being used and discard them for real.

So, at this point you just run:

git reset HEAD~

to tell Git to:

  1. move the current branch name back one step;
  2. reset its index as well, but leave your working tree unchanged.

You'll now have:

A--B--C   <-- remotes/tpt3/master
       \
        D'-E'  <-- master

with uncommitted work, as you wanted.


1Since the commit hash ID is the true name of the commit, this means that D' is completely different from D, for instance. The fact that they have the same snapshot and mostly the same metadata is not of interest to Git—at least not at this level—because Git cares only and entirely about the hash ID at this level. So it's a little like the old joke, "Other than that, Mrs Lincoln, how was the play?"—except in this scenario Abe has been replaced by a clone that no one (except maybe Mrs Lincoln) can detect.

  • Related