Reputation: 105
Ok, so here's the deal. I created a branch (master) off of a commit (2f6ea37), and then create another branch (develop) off of master. Changes were made on develop. The problem is, master should actually have been created off of a previous commit (df7992e) instead of (2f6ea37).
How do I change where master was created without loosing the new stuff committed to develop?
Here's how it is now:
branch1 --> <2f6ea37> --> <df7992e>
\
master
\
develop --> <new_commit>
Here's how it should be:
branch1 --> <2f6ea37> --> <df7992e>
\
master
\
develop --> <new_commit>
Upvotes: 0
Views: 44
Reputation: 488183
You probably just need three git branch -f
commands to do what you want:
git branch -f master 2f6ea37
git branch -f branch1 df7992e
git branch -f develop 2f6ea37
But to do this with git branch -f
, you must be on some branch name other than any of these three, so you might want one git reset --hard
or other command. If develop
already has additional commits, you have a bigger problem. See the long explanation below.
First (and this is somewhat minor, but it really does matter), the arrows in your diagram are wrong because they're forwards. :-) Git does everything backwards. So a commit like df7992e
will point backwards to its predecessor 2f6ea37
.
Branch names like master
and develop
just contain one raw hash ID, i.e., they point directly to specific commits. So I would draw this as:
2f6ea37 <-- branch1
\
df7992e <-- master
\
C <-D <-E <-- develop
To avoid mucking about with unreadable hash IDs, I'd then just call 2f6ea37
A
, and df7992e
B
:
A <-- branch1
\
B <-- master
\
C--D--E <-- develop
Now, the thing about branch names in Git is, you can always stuff a new hash ID into any branch name. That branch immediately now just points to the hash ID you gave it.
The way branch names are expected to move, over time, is so that they accumulate new commits:
A--H <-- branch1
\
B--F--G <-- master
\
C--D--E--I <-- develop
for instance has moved all three branch names in "expected" ways: branch1
now identifies the commit with hash H
, which points back to the commit with hash A
, which branch1
identified earlier. So that's fine. Similar arguments work for the other two.
You apparently want the name master
to identify commit A
, and the name branch1
to identify commit B
. (We'll worry about develop
in the next section!) You can achieve this part by just forcefully shoving the right hash IDs into each name:
A <-- master
\
B <-- branch1
\
C--D--E <-- develop
But if we compare this to the status a moment ago, the name master
has moved in the "wrong" direction. It used to name commit B
and now names commit A
instead. The name branch1
has moved in the "right" direction (forwards, against the internal backwards-pointing arrows).
Moving a branch name backwards like this confuses other Git repositories (and/or their users) if they have seen your master
identifying commit B
before. Remember, all Gits everywhere share the raw hash IDs, so if any other Git has commit B
, they could have some of their names remembering it. They might have their origin/master
pointing to their copy of shared commit B
, and as a result, they might have their own master
pointing to B
too.
If no one else has ever seen this, you're fine. Just use git branch -f
and/or git reset --hard
to stuff the correct hash IDs into the correct branch names.
The last issue is the branch name develop
. Here, I've drawn it pointing to commit E
, with E
pointing back to D
which points back to C
which points back to B
.
I believe that, at the moment—based on the way you drew your initial diagram—what you really have looks more like this:
A <-- branch1
\
B <-- master, develop
That is, commits C-D-E
simply don't exist. If so, we just need to move develop
to point directly to A
too, with the same caveat about moving develop
"backwards" that we had about moving master
the same way:
A <-- master, develop
\
B <-- branch1
If you now git checkout develop
and make a new commit C
, the new C
will point back to A
:
C <-- develop (HEAD)
/
A <-- master
\
B <-- branch1
The attached (HEAD)
here indicates that the branch you have checked out, at this point, is develop
.
But what if you already do have existing commits C-D-E
? That is, what if your current diagram is:
A <-- branch1
\
B <-- master
\
C--D--E <-- develop (HEAD)
The problem here is that existing commit C
already points back to existing commit B
. Nothing about any commit can ever be changed once the commit is made. So you cannot change C
at all. What you will have to do in this case is copy C
, and thus also D
and E
, to new and improved commits. What's new and improved about them is that the new C
—let's call it C'
—points to A
, and uses A
as its source base. Similarly, the new D'
points back to C'
and the new E'
points back to D'
, giving us this:
C'-D'-E' <-- develop (HEAD)
/
A <-- branch1
\
B <-- master
\
C--D--E [abandoned]
This too moves the name develop
in an unexpected way: now we not only backed up over B
, we first backed up over, and abandoned, E
and then D
and then C
too. (Existing commit B
can still be found by some branch name(s) so it is not abandoned.)
The command that will achieve all of this is git rebase
. With your HEAD
firmly attached to develop
, you would run:
git rebase --onto 2f6ea37 df7992e
which uses raw hash IDs to say Excluding commits B
and earlier, copy all commits from the current one backwards, with the new commits going after commit 2f6ea37
. Then move the name develop
to point to the last such copied commit. The exclusion means that the set of commits to be copied is exactly the right set—C-D-E
—so we get just what we drew above.
It's now time to use the two git branch -f
commands to swap the other two branch names, while we're still on develop. Or, we could swap them before starting the git rebase
.
C-D-E
to copy, but are on develop
?Suppose the actual picture right now can be drawn this way:
A <-- branch1
\
B <-- master, develop (HEAD)
You can use git branch -f
to swap the two names branch1
and master
, without changing any other commits in any way. But now you're left with:
A <-- master
\
B <-- branch1, develop (HEAD)
Using git branch -f develop
will give you an error:
fatal: Cannot force update the current branch.
But you still need to move develop
. There are lots of ways to do it:
Using git rebase
: git rebase --onto master branch1
: this excludes commits starting from B
backwards, which is all commits, from the copying; copies zero commits to come after commit A
; and then moves your current branch name to point to commit A
.
Using git reset
: git reset --hard master
: this discards all in-progress work in your index and work-tree, re-setting them to match commit A
instead, and moves your current branch name develop
to point to commit A
. The advantage of this is it's quick and easy and does just what you want. The disadvantage is that it wipes out any in-progress work you forgot to save somewhere. The rebase command will warn you.
Using git checkout
: git checkout master; git branch -f develop master; git checkout develop
. This is exactly as efficient as the rebase and reset commands, but requires typing in three separate commands. It does have the ability to carry uncommitted work in your index and work-tree in some cases (for gory details, see Checkout another branch when there are uncommitted changes on the current branch).
Branch names can be moved pretty easily. To move the branch you're standing on, either get off it (git checkout
something else), or use git reset --hard
or other sneakier methods. Remember that git reset --hard
is destructive of uncommitted work.
You cannot modify any existing commits. If the adjustments you would like to make to the branch names can be done without this, you can simply adjust those branch names. Anyone else—some other Git repository you're supplying with commits, for instance—might get confused by "unusual" branch name movements, so make sure those other Git repositories and their users are prepared for that.
If you need to copy some commits, git rebase
will do the job. This may also adjust your branch names in ways that other Gits who are keeping track of your branch names (and your commits) may need to prepare for.
Upvotes: 1