0

enter image description here

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.

Community
  • 1
  • 1
OdieO
  • 6,836
  • 7
  • 56
  • 88
  • If you hard reset master to "blocking - works" it will remove the commit from master, but it does not change anything in the replies branch. The replies branch will still contain the SAME history. To remove "blocking - works" from both branches will require some combination of rebase (like Blaz suggests) or recreating the branch (as mu suggests). – user3033893 Mar 23 '14 at 19:20
  • I wanted to select all three, but had to go with the most descriptive. – OdieO Mar 25 '14 at 18:46

3 Answers3

1

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.

Anshul Goyal
  • 73,278
  • 37
  • 149
  • 186
  • Clarification needed: The problem is, I have some changes in `replies` already that I want to keep, unrelated to recent changes in my `master` branch. If I perform what you suggest, that will just create a whole new `replies` branch, correct? – OdieO Mar 23 '14 at 19:39
  • What are the changes, are those already committed? Also, have you made any further changes on your `master` branch? – Anshul Goyal Mar 23 '14 at 19:40
  • The changes are already committed in `replies`. No further changes on `master`. – OdieO Mar 23 '14 at 19:41
  • Yeah in that case the changes will be lost. Check out rebasing in git interactive mode [here](http://stackoverflow.com/q/37219/1860929) for that case. – Anshul Goyal Mar 23 '14 at 19:44
  • Wait a minute - You want to delete that commit from your `master` branch, or from your `replies` branch? – Anshul Goyal Mar 23 '14 at 19:45
  • I want to delete it from both branches, but also keep the branches separate for a bit and not merge `replies` just yet. So it appears I have two options - a rebase on both branches or a rebase on `replies` and a hard reset on `master`. Is that correct? – OdieO Mar 23 '14 at 19:50
  • 1
    Well if you delete it from both branches, the changes will be lost forever. Do you want to delete it completely from history, or do you just want to delete it from `master`? – Anshul Goyal Mar 23 '14 at 19:51
  • Well, I'm pretty sure I want to delete them completely, but if I did want to keep them but not have them included in neither `master` nor `replies` at the moment, I guess that involves: 1. Rebase `replies` -- 2. Get `head` of `master` to `blocking - works` -- 3. Spin off `wslanguagelabel - color changes` into a separate branch? ... Thanks, BTW. – OdieO Mar 23 '14 at 19:58
  • 1
    @Ramsel Do those steps in order `3, 2, 1` or `3, 1, 2` instead of `1, 2, 3` and you should be fine. Step 3 is the backup step, it preserves the state of master branch as is right now, and hence it needs to be performed before the master branch is altered. In fact, if you are not confident enough, create another dummy branch from your `replies` branch so that you know that the state of that is also preserved. You can always delete the local dummy branches later. – Anshul Goyal Mar 23 '14 at 20:00
  • @Ramsel Don't forget to accept an answer. If need be, you can post your own answer and accept it. – Anshul Goyal Mar 24 '14 at 18:42
1

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.

Blaz Bratanic
  • 2,279
  • 12
  • 17
1

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:

  • "fast forward", or
  • "true merge".

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."

torek
  • 448,244
  • 59
  • 642
  • 775