Reputation: 6994
So, I made some changes and committed them in the second commit - "wslanguagelabel - color changes
". These changes are now on master. I then started a new branch - replies
.
However, I want to get rid of the changes in the second commit shown - "wslanguagelabel - color changes
" - so that master now points to "blocking - works
", and then keep working on the replies
branch.
Here's my question: If I hard reset
master to "blocking - works
", will this cause problems when I eventually merge replies
? This is where I don't understand git well enough, and what exactly goes on under the hood. I'm worried that replies
will still have changes from the second commit, which I want to get rid of, but I'm not sure if my thinking there is correct.
BLAZ's answer below is correct for the simple case of removing the commit from the branch.
MU gives a more complete answer in the responses to my comments.
Upvotes: 0
Views: 397
Reputation: 487755
I think this will help: think of the green labels, replies
, master
, and trishtext
, as just that: labels, on green sticky-notes or similar.
These green sticky-note labels are pasted onto the circles, which are the actual commits.
You can peel off a label and stick it on any other commit circle, at any time.
If a commit circle has no label, and is not connected directly to another labeled commit "above" it, it becomes "abandoned". Eventually abandoned commits are garbage-collected.1 This gives you a chance to re-label them if you goof up, but it's best to think of them as gone, as they're kind of hard to find (git reflog
helps find them, also git log -g
, but it gets messy).
In all cases, the commits themselves—not the labels—make up the "commit graph". Each commit permanently records its parent commits (two or more if a merge, but usually just one parent commit). To see a commit sequence, git starts with a label like master
or replies
. That names one specific commit. Git looks at that commit and tells you something about it. Then, it moves back to that commit's parent(s), moving "down the line" from c1173c4
to af211ba
, and tells you something about that commit, and so on.
Doing a rebase (as some have suggested) copies a series of commits. Here's a similar sequence shown in text form (not as pretty, no colors, etc., but otherwise the same idea as the snippet you showed):
* 1f33fa6 (master) D
* 6cb155d C
* 170af7a B
| * 4396360 (branch) checkpoint: got to state X
| * ba1bdf2 checkpoint more work
| * ace0be9 begin work on feature
|/
* c55985d A: first commit
I can now git checkout branch
—this gets me commit 4396360
as my working tree—and then git rebase master
. What this does is copy the changes I made in the three "side branch" commits (basically, run git diff
or git show
on each one to compare it with its parent, to see what I changed each time) and attempt to make the same changes/commits on top of 1f33fa6
.
If this succeeds, the result is:
* xxxxxxx (branch) checkpoint: got to state X
* wwwwwww checkpoint more work
* vvvvvvv begin work on feature
/
* 1f33fa6 (master) D
* 6cb155d C
* 170af7a B
| * 4396360 [abandoned] checkpoint: got to state X
| * ba1bdf2 checkpoint more work
| * ace0be9 begin work on feature
|/
* c55985d A: first commit
I drew the new ones in shifted-over like this to make them mirror the old ones; "git log" wouldn't shift them over, and they'd have real actual SHA-1 values as well, but this is to give you the idea. I also kept the old commits, but marked them "abandoned", because they would be: after copying all the commits, git moves the label, branch
, to mark the newest of the new ones, which means the old ones no longer have a label.
If I use git rebase -i
, I can drop one or more of the original commits, and git will do its best to copy only the changes I keep. The three old commits will still be there, but with no label, they are quite hard to find, and the new commits will omit the one(s) I dropped.
If you simply move labels about—this is (half of) what git reset
does2—then you get no new copies of old commits, and no commits get changed. It's just that the labels move.
You were asking about how this affect a future git merge
; the answer to that is: "it depends".
What git merge
does is sort of simple to describe, although the details get quite difficult sometimes. A regular merge has two possibilities:
Git starts by looking at the current branch, and the commit that you give it. Let's look at your current situation:
* c1173ca (replies) working
* af211ba (master, trishtext) wslanguagelabel - color changes
* d38eaad blocking - works
Let's say you git checkout master
. This puts you on branch master
, which is also commit af211ba
. The contents of that commit go into your working tree. Now say you use the commit git merge replies
. Git must resolve replies
to a commit ID (c1173ca
, in this case). It then looks at the history of your current branch, master
, and the history of the named commit, c1173ca
.
The parent of c1173ca
is af211ba
. That's your current commit! "Wow, this is an easy merge," says git; "all I have to do is slide the label forward one commit, to c1173ca
, and check out that version and I'll be all done!" The result is this:
* c1173ca (master, replies) working
* af211ba (trishtext) wslanguagelabel - color changes
* d38eaad blocking - works
That's a "fast forward" merge. Git says: the history of the thing you're merging-in connects straight back to the history of the current branch, so I can just slide the branch label forward across all those commits—in this case, the one commit—and I'm done.
Suppose you use git reset
to "move the label back one step", though, so that you start here:
* c1173ca (replies) working
* af211ba (trishtext) wslanguagelabel - color changes
* d38eaad (master) blocking - works
If you now git checkout master
and git merge replies
, git has to go through the same process. The history starting from c1173ca
goes to af211ba
, and then af211ba
has d38eaad
as its parent, and wow, hey, it's another easy case! Git can once again do a fast-forward merge, sliding the label master
forward (or upward) two nodes.
A "real merge" occurs when you have a situation where sliding the labels around won't do the trick. For instance, in the graph I showed, git merge branch
can't slide master
around to point to commit 4396360
. If it did, commits 170af7a
, 6cb155d
, and 1f33fa6
would be abandoned. Instead, it has to do a real merge, producing a common working tree and making a two-parent commit (I drew this by hand, actual git log --oneline --graph
etc output may differ slightly):
* (master) Merge branch 'branch'
|\
* | 1f33fa6 D
* | 6cb155d C
* | 170af7a B
| * 4396360 (branch) checkpoint: got to state X
| * ba1bdf2 checkpoint more work
| * ace0be9 begin work on feature
|/
* c55985d A: first commit
Whether you get into such a situation depends on what commits you make. (You can force git to make a real merge, a two-parent merge commit, even when fast-forward is possible, with git merge --no-ff
, but if you need that you probably know why, already. :-) )
1Whenever you peel off a label, it leaves a bit of "residue" as it were (in the "reflog"), a sort of ghost-image saying "I was once labeled replies
" (or whatever the label was). It's after the reflog entries expire that the commit node really goes away.
2The other half is manipulation of the "index" or staging-area, and the third half—git reset
has three halves :-)—is how git reset --hard
throws away the current work tree and replaces it based on the commit you're resetting-to. Only --hard
does that; the other git reset
modes mean "move the current branch label and/or fuss with the index/staging-area."
Upvotes: 1
Reputation: 76837
Instead of checking out a branch on the latest commit, and then resetting HEAD
, you should checkout the branch replies
at that specific commit itself, like below
git checkout -b replies <SHA_of_commit>
This way, your new branch will be at exactly the commit you need to be, and you won't have to do any hard resets on your master
branch. The merge should also work seemlessly fine, unless you change the same set of code in the replies
branch as you do in master
branch in subsequent commits.
Upvotes: 1
Reputation: 2279
Branch replies
will retain the changes from the second commit. You could
selectively remove the second commit from its history with:
git checkout replies
git rebase -i HEAD~2
where you delete the line with the second commit.
Upvotes: 1