简体   繁体   中英

How to recreate a merged Git branch

Let's assume a feature branch was deleted after it was merged to master.

A few days/weeks later, I have a need to generate patch files for all the commits that were in that feature branch.

If I had the feature branch around, I could use the following command to generate patches:

git format-patch $(git merge-base master my_feature_branch)..my_feature_branch

So, how could I re-create the feature branch so I can use the above command?

Well, first, branches don't really get deleted. Only branch names get deleted. But this gets us into a thorny question: What exactly do we mean by "branch"?

Let's take a fast look at the process of merging. We start with a series of commits in the commit graph (or "DAG"; see the linked question) that look like this:

...--o--*-----o   <-- master
         \
          o--o--o--o--o   <-- feature

We then run:

git checkout master
git merge feature

which somehow figures out what we've changed in both master and feature since the last time we merged them (which was actually never, but they were together at one time, at the point marked * here). Git then makes a new merge commit that points back to both of these branch tip commits:

...--o--*-----o---------o   <-- master
         \             /
          o--o--o--o--o   <-- feature

and we have "a merge": a commit, of type merge commit .

We can now erase the word feature and the arrow, ie, remove the name . The graph remains intact, retained by the name master :

...--o--*-----o---------M   <-- master
         \             /
          o--o--o--o--F

Should we wish to see what went into master via feature , all we have to do is find the commit I have labeled F (for Feature) here. Note that I have also labeled the merge commit M (for merge).

The way to find it is to start from master and work backwards until we find M . (It's right there at the tip of master right now, although later, it will be some number of steps back from the tip.) Then, we simply look at the second parent commit of M .

To find the second parent of a commit whose hash ID we know, we just tell Git: tell me the hash ID of the second parent of this other hash ID. The easy way to do this is with git rev-parse . Let's say the hash ID of M is badf00d :

git rev-parse badf00d^2

Git spits out the full hash ID of F . The hat-two suffix means "second parent" (hat-one, or just hat by itself, means "first parent").

Now we may also want to find commit * . That's the merge base of the commit that is the first parent of M , and this particular commit F that we just found. To find the merge base of two commits, we ask Git:

git merge-base badf00d^1 badf00d^2

We can then look at every commit in the range starting just after the merge base * and going up through and including commit F , using git log or git format-patch or whatever.

We can do this with the raw hashes, or we can point names (temporary or permanent, they will live exactly as long as you like) to commits M , F , and/or * , using git branch or git tag . Each name remembers the hash ID for you. The chief difference between a tag name and a branch name is that if you git checkout a tag name, you get a "detached HEAD" and are not on a branch, but if you git checkout a branch name, you get on that branch, and if you make new commits, they will cause that branch to advance:

$ git branch newname <hash-ID-of-commit-F>

...--o--*-----o---------M--o--o--o   <-- master
         \             /
          o--o--o--o--F   <-- newname

$ git checkout newname
... hack away ...
$ git commit ...

...--o--*-----o---------M--o--o--o   <-- master
         \             /
          o--o--o--o--F--o   <-- newname (HEAD)

This is all that branches are, in Git: the names just point to commits, while the branch structure , the history or DAGlet or whatever name you want, is formed by the permanent parts of the commit DAG. Branch names have the special feature that you can git checkout them and make them advance by running git commit .

Let's assume the following simplified history: The feature branch was based on commit N and contained the commits P and Q . In the meantime, commit O was added to the master branch. The feature branch was merged in commit R . After that, commits S and T were created, which is where master is now.

M -- N -- O -- R -- S -- T [master]
      \       /
       P --- Q

You want to find commit Q . I don't think there is a programmatic way to do this, so you need to use any repository browser, for example gitk .

Once you have found Q , you can easily recreate the feature branch:

$ git branch my_feature_branch <hash of commit Q>

I'm not sure if you are aware of this, but a key to understanding this is knowing that a branch is not much more than a "pointer" or "bookmark" to a commit.

If you have a base commit, the one from which the now-deleted feature branch had diverged, you can proceed like follows -

  1. Check out a branch based off the base commit.
  2. Using git cherry-pick , cherry-pick the merge commit that had merged the feature branch into master. If this doesn't work, you can cherry-pick individual commits that were part of the feature branch.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM