简体   繁体   中英

GIT: Branched off feature branch instead of development branch

So normally when we have feature1, feature2, and feature3, they branch off of devel. I accidentally just continued branching rather than switching back to devel though so feature2 branches off of feature1 and feature3 branches off of feature 2.

How can I fix this?

EDIT for pic:

I would like this:
---------------
  \
    A - B - C - D - E

to become this:
---------------
  \   \     \
   A   B-C   D-E

As there is only one or two commits per branch, using cherry-pick will be enough (no need to rebase for one or two commits).

First backup your local repository (do no trust the internet!)

Write down the hash of A, B, C, D and E. Then:

Just do:

# Switch to devel
git checkout devel
# Rename branches into temporary branch names
git branch -m feature1 tmp1 && git branch -m feature2 tmp2
                            && git branch -m feature3 tmp3
# Create feature1 branch and cherry-pick the required commit
git checkout -b feature1 && git cherry-pick A
# Switch to devel
git checkout devel
# Create feature2 branch and cherry-pick the required commits
git checkout -b feature2 && git cherry-pick B C
# Switch to devel
git checkout devel
# Create feature3 branch and cherry-pick the required commits
git checkout -b feature3 && git cherry-pick D E
# Switch to devel
git checkout devel
# Delete temporary branches
git branch -D tmp1 tmp2 tmp3

For completeness, here's how to use git rebase (with optional --onto ) to move these commits. First, let's update the "current" drawing to add some specific labels and commits. For ease of rebasing and yet generality, I'll assume that feature1 connects behind the tip of devel , and that you'd like to move the new branches to the tip (the commit marked with * ).

If there are no extra commits (so that * connects directly to A , etc) this all still works.

If there are extra commits but you want to keep the feature s back from the tip, the --onto destination should be devel~2 in this example.

Now:

...-o--o--*         <-- devel
     \
      A             <-- feature1
       \
        B--C        <-- feature2
            \
             D--E   <-- feature3

Step 1, rebase feature3 interactively onto devel . What this will do is create a text file with a bunch of pick commands, each of which will do a git cherry-pick . You want to pick commits D and E . Tell rebase that the "upstream" (poor name) is devel , and that you want it to start the whole operation by doing git checkout feature3 :

$ git rebase -i devel feature3

Here, rebase will find all commits selected by devel..feature3 (this is A through E inclusive, but not either of the two extra commits already on devel ), and will choose as its --onto target, devel . Then you end up in the editor, where you can delete all but the last two commits ( D and E ). Rebase will then:

  • move to the tip of branch devel , on a new unnamed branch (detached HEAD mode)
  • cherry-pick commit D , adding it to the unnamed branch
  • cherry-pick commit E , adding it to the unnamed branch
  • and last, use the plumbing equivalent of git reset to make feature3 point to the latest commit.

Now the tree looks like this:

            D'-E'   <-- feature3
           /
...-o--o--*         <-- devel
     \
      A             <-- feature1
       \
        B--C        <-- feature2
            \
             D--E   [abandoned, except for reflogs]

(If you add --onto devel~2 to the original git rebase -i , it will grow the D'-E' chain from the commit at devel~2 , which is the leftmost o node. You still need to specify the upstream, which you can list as either devel or devel~2 : it won't matter in this case.)

Now you can git rebase -i devel feature2 . This works in the same way: check out branch feature2 , find commits in devel..feature2 (which is A through C this time), and open an editor session to let you modify the set of cherry-pick commands that git will run. This time you need only delete one line (for commit A ). Git then starts a new detached-HEAD anonymous branch at commit * again, grows it with cherry picks of B and C , and then moves the feature2 branch:

            D'-E'   <-- feature3
           /
...-o--o--*         <-- devel
    |      \
     \      B'-C'   <-- feature2
      |
      A             <-- feature1
       \
        B--C        [abandoned]

Last, you can rebase feature1 . Here you don't need to bother with interaction, as commit A is the only one you want to copy and the only one git rebase will choose.

The result is the same as with git cherry-pick . You need somewhat fewer git commands, but a bit more editing for the interactive "pick" sequences.

Which way is "better"? Neither, really. Renaming the old branches and then deleting them will toss their reflogs, which might be a feature since it enables the original A through E commits to get garbage-collected sooner, or might be a drawback in case you want to look at them again.

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