1

So I'm working on a project with some teammates and I have rebased on their changes, but is there a way to remove their commit (just locally - not planning to push it this way) and add it back at will?

** Edit: Clarification, I don't want to combine the commit with my commit, I just want to make it as if that commit has never happened (remove any changes from that commit, etc)

Commit A: Commit that both of us got out
Commit B: His commit <- want to locally remove just this commit
Commit C: My commit

I had thought about making a branch and just sticking my commit on the branch and not pulling in his change onto that branch, but I was wondering if there was a cleaner way that wouldn't involve a separate branch?

Thanks in advance!

CustardBun
  • 3,457
  • 8
  • 39
  • 65
  • What does "remove a commit" _mean_? Do you mean you want to back out the _changes_ made in that commit? Do you mean you want to conflate his commit with yours? What? – matt Dec 21 '16 at 01:04
  • perhaps OP means [reset/revert single file](http://stackoverflow.com/questions/215718/reset-or-revert-a-specific-file-to-a-specific-revision-using-git) – Bagus Tesa Dec 21 '16 at 01:08
  • I meant to back out any of the changes made in the commit (several assorted changes) – CustardBun Dec 21 '16 at 01:48

3 Answers3

6

Don't be afraid of branches! Branches—branch names in particular, because the word "branch" is ambiguous in Git—are so cheap as to be nearly free. For Git, creating a new branch consists of writing one commit hash ID into one file,1 and removing a branch consists of removing that one file.

What's less cheap, though still not that expensive, are the commits themselves. Every commit has exactly one "true name", which is that big ugly hash ID, face0ff... or cafedad... or whatever. Each of those commits records its parent commit ID, so you (and Git) can work backwards, starting from the last commit on a branch, to find all the commits on the branch. These commits are permanent and unchanging. What can change is not the commits, but the branch names.

What you drew has three commits, but you didn't draw their explicit relationship to each other. That relationship, which is a sort of parent/child thing, is recorded in each commit by recording just the parents.2 So assuming (reasonably, I think) that commit A came first, then B, then C, what you have is this:

A <- B <- C   <-- branch

That is, the branch name, branch, points to your commit C, and your commit C points back to B ("B is my parent", says C) and B points back to A. None of these can parent pointers can change, but branch names can change, and making new branch names is so cheap it might as well be free.

So let's make a new name and point it to C:

$ git checkout -b temp

Now we have:

A--B--C   <-- branch, HEAD -> temp

I put in the HEAD -> here to denote that the current branch is temp, even though there are two branch names both pointing to commit C.

Now what you can do is copy C to a new commit that's mostly like C, but not quite the same. We'll call the copy C'. In C', we want these two key differences:

  • Our new parent should be A, not B
  • We should figure out what we changed going from B to C, and apply those changes as a patch onto A, instead of onto B.

The command that copies commits like this, then moves the branch name, is git rebase:

$ git rebase --onto HEAD~2 HEAD~1  # we'll explain these later

The rebase command copies each of the "rebased" commits, which in this case is just commit C. It then moves the current branch name to point to the copies:

A--B--C   <-- branch
 \
  `---C'  <-- HEAD -> temp

Note that the original commit C is still in there, and it's still at the tip of branch branch. We abandoned it from temp when we made the copy, but branch still remembers it.

Now you can test your change in isolation: does C' work correctly without B? If so, any problems you get in C are probably introduced by B, since C' vs A is pretty much "the same" as C vs B.


1This is an implementation detail, and branch names can also be stored with many-in-one-file instead of one-per-file, but it should give you an idea of just how cheap branches really are.

2This is necessary because at the time you create a commit, it has no children. Once you've created the commit, it is permanent and unchanging, so it can't record its children. Its children don't exist yet! As soon as one child comes into existence, though, that child has its parent, and that child's parent is also permanent and unchanging, so the child can and does record its parent.


Explanation of HEAD~2 and HEAD~1

Whenever you want to identify a particular commit in Git, you can give its raw SHA-1 hash. You can cut-and-paste these from git log output, for instance. But you can also use a symbolic name, such as a branch or tag name, or the special name HEAD. These symbolic names simply resolve to a commit ID:

$ git rev-parse HEAD
de2efebf7ce2b308ea77d8b06f971e935238cd2f

You can abbreviate these things, so that face0ff or cafebabe works if the actual ID starts with that.

You can also use a relative name, which is made by taking any suitable name and adding ~number. So both HEAD~2 and de2efeb~2 mean the same thing here. The ~ suffix means "go back some number of parents", and the number of parents to go back is whatever comes after the ~.

Since HEAD is always the current commit, HEAD~1 means "go back one parent", and HEAD~2 means "go back two parents". From C, going back one parent gets you B, and going back two gets you A.

Thus:

--onto HEAD~2

means "copy so that we add after A", while:

HEAD~1

in this case means "don't copy commit B or earlier"—which leaves just commit C.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
1

First, try to get all commit within repository Try--> git log which displays all commit to the repository. there will be all different commit and reference number your team made: Example: -->commit 4406fbc67a665a1e78c9ac2698164986b81bed13

let you want to change on the first commit just refer that no. like this way

git checkout 4406fb(this is the commit reference no)  SplashActivity.java(this will be particular file name) 
K P
  • 182
  • 9
1

You can rebase your commit onto the commit you want as the parent.

While on Commit C:

git rebase --onto <a_hash> <b_hash>

This doesn't "remove" B but it is no longer a part of C's history, which seems like it is your goal.

You can 'add it back' by rebasing back onto B.

git rebase --onto <b_hash> <a_hash>
Kyle
  • 893
  • 1
  • 9
  • 20