Reputation: 9008
Suppose I have a set of BRANCHES (not commits) A, B, C and D like the following
A
|
B C
| |
\ /
D
I would like to rebase branch A
over branch C
.
Doing a normal rebase would keep branch B
in the same place. Like this:
A
|
B C
| |
\ /
D
But I'd like to also move branch B
keeping its relative position to branch A
in the rebase.
The end (desired) result would be the following:
A
|
B
|
C
|
D
at the moment I'm doing this manually, rebasing branch A
over branch C
and then moving branch B
. Is there an option for the git rebase
command which allows me to do this with one command?
Using a simple git rebase C
while being in A
(or a git rebase C A
) is not enough because it moves only the A
branch, while the B
branch remains where it is (its commits are copied, but the branch is not moved)
Upvotes: 4
Views: 1239
Reputation: 36987
Git v2.38 (released Oct 3 2022) added a new feature --update-refs
that does exactly what you are asking for. So in your case:
git rebase --update-refs C A
The option can also be enabled by default:
git config --global rebase.updateRefs true
Upvotes: 5
Reputation: 488003
If I render your four commits in the style I prefer for StackOverflow postings, it looks like this, with newer commits towards the right:
C <-- branch1
/
D
\
B--A <-- branch2
The two names—you have not told us what they are, so I just used branch1
and branch2
here—contain the raw hash ID of actual commits C
and A
, which are the tip commits of the two branches. (C
and A
stand in for real hash IDs, which are big and ugly and random-looking.)
The parent of commit A
is commit B
. The parent of commit B
is commit D
. The parent of commit C
is commit D
as well. (If there are commits before commit D
, we cannot see them.)
You say that you would like to have:
D--C--B--A
as your final result. You cannot: commit B
's parent is commit D
, now and forever. You can have:
D--C--B'-A'
where B'
is a copy of B
but with a different hash ID because it also has a different parent (and probably a different snapshot as well). Similarly, A'
must be the result of taking commits B'
and A
out and recommitting something slightly different: the parent if A'
is B'
, and A'
probably holds a different snapshot than A
, just like B'
probably holds a different snapshot than B
.
Commits B
and A
will continue to exist, i.e., your repository will have:
D--C--B'-A'
\
B--A
in it. If you write down the actual hash IDs of B
and A
somewhere, and type them back in later, you will see that the commits continue to exist. (They will stick around for at least 30 days by default, in case you decide you want them back.)
What's missing from your final-result, however, is which names you want to have pointing to which commits. You can, if you like, have both names point to commit A'
, like this:
D--C--B'-A' <-- branch1, branch2
or you can have branch2
point to A'
and leave branch1
pointing to existing commit C
, like this:
B'-A' <-- branch2
/
D--C <-- branch1
The set of Git commands that you can to use to achieve the final result depends on which of these final results you want.
Note that Saurabh P Bhandari's answer is equivalent to:
git checkout --detach branch2 # or git checkout <hash-of-A>
git rebase branch1
which produces:
B'-A' <-- HEAD
/
D--C <-- branch1
\
B--A <-- branch1
i.e., you are detached HEAD mode in the end, with neither branch name moved.
Since there are two branch names involved—branch1
and branch2
are the two names—it takes, at a minimum, two Git commands to achieve:
D--C--B'-A' <-- branch1, branch2
\
B--A [abandoned]
as your final result, if that's what you want. A minimal set of commands—there are many command sequences that will achieve this result—is:
git checkout branch2
git rebase branch1
git push . branch2:branch1
which leaves you with your HEAD
attached to the name branch2
:
D--C--B'-A' <-- branch1, branch2 (HEAD)
\
B--A [abandoned]
symlink's answer, with name substitutions, amounts to:
git checkout branch2
git rebase branch1
git checkout branch1
git merge --ff-only branch2 # the --ff-only is not required here
which does the same thing but leaves HEAD
attached to the name branch1
:
D--C--B'-A' <-- branch1 (HEAD), branch2
\
B--A [abandoned]
The --ff-only
flag ensures that if something went wrong and the commit identified by the name branch2
is not now strictly ahead of that identified by branch1
, Git will produce an error message, rather than a new merge commit.
The git push . src:dst
command is particularly tricky / sneaky and I don't really recommend it in general, but it illustrates a way to ask Git to fast-forward one of your names—dst
—to point to any given commit, in this case src
. Here we fast-forward the name branch1
to make it point to the same commit as branch2
. Again, the push command has the source on the left and the destination on the right, which is why we write this as branch2:branch1
.
Git is all about commits. Each commit has a unique hash ID, and once a commit is made, no part of it can ever be changed. So if you want different commits you have to make new ones. That's usually not a big deal. That's what git rebase
does: it copies some existing commits to some new-and-improved ones.
You—and Git for that matter—find commits, however, by starting with the last one in a chain, as identified by a branch name, then working backwards. (That's why commit A
is the last one, instead of the first one, in each of our drawings: you put the last one at the top, and I put the last one at the right.)
Having copied a series of commits as found by a particular name, git rebase
now drags the name over to point to the last of the new commits. For a single name, this is just what you want.
But, after you have copied some series of commits, if there is more than one name that you would like moved, you must move the remaining names. That's really all there is to it.
Upvotes: 0
Reputation: 12209
If A is the tip of branch feature
and C is the tip of branch master
:
A //feature
|
B C //master
| |
\ /
D
git checkout feature //if not on feature
git rebase master //rebase feature onto master
You can also do this on either branch and get the same result:
git rebase master feature //rebase feature onto master
The result:
A //feature
|
B
|
C //master
|
D
To fast-forward master to point to A:
git checkout master //if not on master
git merge feature //merge master with feature
Result:
A //feature, master
|
B
|
C
|
D
Upvotes: -1