-1

Assume that we have master branch, and commit a0->a1->a2->a3. And in commit a1, we make another branch called dev: a1->b2->b3, that is:

a0->a1->a2->a3 (master)
     -->b2->b3 (dev)

And a3 is quite different from b3, means that they have a lot of conflicts.

What I want is in master branch, I want to get b3 without any change, like below: a0->a1->a2->a3->b3 (master)

How to do with git command? Thanks!

More: I mean to ignore a3 entirely. In some case, you know b3 is good enough, and a3 fails. So just take b3 completely.

Tom Xue
  • 3,169
  • 7
  • 40
  • 77
  • so you want to ignore commit a3 entirely? Is that what you're saying? – MCBama Nov 02 '15 at 17:01
  • Yes, ignore a3 entirely. – Tom Xue Nov 02 '15 at 17:01
  • 1
    Possible duplicate of [How to copy commits from one branch to another?](http://stackoverflow.com/questions/2474353/how-to-copy-commits-from-one-branch-to-another) – m.aibin Nov 02 '15 at 17:02
  • Easiest solution is to just revert a3 and then merge your dev branch back into your master – MCBama Nov 02 '15 at 17:02
  • There will be conflicts to solve? I want a solution without handling any conflict, just take b3 there in master branch. – Tom Xue Nov 02 '15 at 17:06
  • Do you care about `a2` and `a3` at all? – Mat Nov 02 '15 at 17:14
  • depends if a2 has conflicts with b2. simply reverting a3 (ignoring anything in it) and then merging b3 into master would essentially be fast-forwarding the branch – MCBama Nov 02 '15 at 17:27

1 Answers1

1

You need to make a clear distinction here between these entities:

  • a commit, with author / committer name, email, and message;
  • the source tree associated with any given commit;
  • the commit graph (some or all of the DAG); and
  • a branch name like master or dev.

Remember, each commit has a complete source tree attached: a snapshot, as it were, of the work tree at the time the commit was made. In some ways this is the most important part, but it's what we will talk about last, because the commits and graph are in the way.

Each commit also has a pointer back to its parent commit(s). This is where there's a slight problem with the graph fragment you've drawn: the pointers are pointing the wrong way ("forwards" in time, from oldest to newest, instead of backwards, newest to oldest).

Let's redraw the graph fragment with the arrows going the correct way:

a0 <- a1 <- a2 <- a3   <-- master
       ^
        `-- b2 <- b3   <-- dev

None of these commits can be changed. Commit b3 points back to b2, and b2 points back to a1, and so on. (More precisely, each commit has, stored in it, the SHA-1 IDs of its parent(s). At this level, a merge is simply a commit with at least two parent IDs.)

The names, in this case master and dev, can be changed. All they have is a pointer to (more precisely, the SHA-1 ID of) a single starting-point commit: in this case, a3 and b3 respectively. These are the newest commits on the branches.

The graph itself is formed by taking these starting-points and following all the commit parent-pointers. That's how we get the picture above: master points us to a3, dev points us to b3, and we combine a3 and b3 and all their parent ID pointers.

What's missing from this drawing is the source associated with each commit. I left it out for two good reasons: (1) I don't have it, only you have it and (2) it won't fit anyway. :-) But from what you've said above, it sounds like the source tree associated with commit a3 is essentially worthless.

You have not said anything about the source trees associated with commits a2 and b2. What should we make of those?


With all those mentioned, let's look at what you can do. You can't change a commit, but you can make a copy that's "almost the same" and therefore "just as good".1 You can also change branch pointers arbitrarily. But note that if you make copies of commits, everyone who's sharing this repository (or a --bare copy of it) needs to get those copies, and if you change a branch-pointer in ways they don't expect, it makes things more difficult for them.

Let's take a look, then, at things people do expect.

They expect branches to "grow", i.e., acquire new commits that point back to existing commits. If master grew a new commit a4 that pointed back to a3, that would be entirely unsurprising, for instance.

They expect new branch names to appear: feature3 might suddenly come into existence, pointing either to some existing commit (perhaps to a1 or b2).

They occasionally even expect branch names to vanish, usually after that branch got merged into some other branch.

What they don't expect, at least not without some advance warning, is for branches to "shrink": for a branch name to point to a commit that used to be only in its history. For instance, if we were to completely discard commit a3 we'd have this drawing:

a0 <- a1 <- a2   <-- master
       ^
        `-- b2 <- b3   <-- dev

Note that you can do this! It's just that people don't expect it—and those other people who are sharing this repository might, and very easily can, put a3 back. It will happen by accident,2 without them even thinking about it, if they're not prepared for this kind of branch rewinding. (I have personal experience with this from something about 7 years ago. We wound up putting in a "reject certain commits by SHA-1 ID" hack in the bare repo pre-receive hook.)

So, now you must decide whether you want to "rewind master" to make it look like commit a3 is gone. If you do this, make sure everyone sharing the repository knows about it, lest they accidentally re-introduce it. Then simply re-set master to point to a2, i.e., change the arrangement so that a2 is the newest history for master.

It's probably better not to do this rewinding, precisely because of the problems that this sort of rewind causes with sharing. You said (I think) that you wanted to discard the changes made between a2 and a3, though, and this is where we get into the source tree attached to a commit.

Suppose that instead of removing a3, we added a new a4 that had the same source tree as a2?

If we didn't have git at all—if, for instance, we just saved each tree somewhere, as a back-up copy, with a name and date on it—there are two ways we could do that:

  1. undo everything in the work-tree: wherever a line was added, remove it; wherever a line was removed, restore it; wherever a word was changed, change it back; or

  2. just restore the work-tree from the saved back-up copy.

With git, we have the same two options, because each commit has a saved back-up copy of the entire tree.

Method (1), undoing everything, can be difficult if you don't have git, but it's really easy with git as it's a basic operation: git revert. (Method 2 is not built-in, as we'll see.)

This is where we get into the fact that git stores content. I mentioned above that, in some ways, this is the most important part of what git does. This is because everything else git does—managing the commit graph and commit messages, so as to remember who did what, when, and why—only matters once you have some content.

Git differs from many other systems, though, in that each commit stores a complete tree. In other systems, each commit stores changes; this is not the case in git.

This means, however, that in order to see changes, you must direct git to look at two commits.3 But git revert takes a single commit ID. This is where the commit graph comes in: revert can compare that commit's source tree to its parent commit's source tree.

In this case, for instance, it would make a lot of sense to revert commit a3. To do this, git simply compares a3 against its parent, a2. Whatever it took to turn a2 into a3—adding some lines, deleting some lines, maybe even adding or deleting entire files—git can simply reverse these changes. If you apply this "reverse change" to the source tree from a3 and make a new commit a4, git guarantees that the source tree for the new a4 is the same as the one for a2.

You might ask why we do this instead of the simpler "copy tree from a2". In this particular case, they would both do the same thing. But suppose we already had a commit a4 that was a good commit? If we copied the tree from a2 to make a5, we'd lose the changes in a4 (vs a3). But if, instead, we reverse a3's changes while keeping a4's, we get a new a5 that is "a2, plus a3, plus a4, minus a3". With luck, the plus and minus cancel each other out and a5 is a2 plus a4, as it were.

Let's draw that:

a0 <- a1 <- a2 <- a3 <- a4   <-- master
       ^
        `-- b2 <- b3   <-- dev

where a4 is the revert of a3. This leaves the source tree in a4 the same as that in a2: if you ran git diff <id-of-a2> master, there would be no diff shown. The two commits are different—their SHA-1 IDs are different, their messages are different, their parent SHA-1 IDs are different—but their source trees are the same.

Moreover, anyone sharing from this repository sees the kind of commit-graph change they expect: master grew a new commit a4.

You can now combine changes from the dev branch any way you like (cherry-pick and merge being the two common methods, though these two do very different things, both in terms of the source trees, and in terms of the commit graph).

Alternatively, you can rewind master back to a2, after which you can combine changes from dev any way you like. In terms of the resulting source tree, you will get exactly the same results. In terms of the commit graph, you won't have commit a3 any more, nor will you have its reversion commit a4 (which you won't need). Someone who look at your repository in the future will never see the malfunctioning a3 nor its revert, since it will be as if they never existed. But anyone sharing the repository, who already has a3, will need to do their own extra work to handle the "rewinding" of master (basically they also need to discard their a3).


1Whether it really is "just as good" depends on what you're using it for, and how good a job you did copying it.

2This is less likely once you've added a new commit, but it's still possible. It remains easy to have it happen accidentally if your users do merges, and generally gets harder if you force them to rebase.

3Two or more, really, but it gets more complicated when dealing with more than two commits. Also, git can work with just a tree (or the index) rather than a complete commit.

torek
  • 448,244
  • 59
  • 642
  • 775