简体   繁体   中英

git reset all commits in diff of two branches

My usual workflow, when working with others, is to make a feature branch, and have both parties commit changes to this branch. When working together, it's typical to have meaningless commits, as in 'hey I made this small change, what to you think?' However, before we PR and merge this back into master, I'd do a git reset back to the first commit before we started working, and split up the history into reasonable and logical chunks.

However, this time a certain coworker merged in master, making our history unreadable. Or at least I'm now unable to git reset because our commits are no longer all in a row.

Our branch is a superset of master, there are only additional commits. I'd like to remove all those commits but leave the work, allowing me to commit the changes into logical chunks.

Thoughts?

For future folks, I found the best way to approach my problem was to just make a patch of the differences between master and my own, then apply the changes to a new branch, and PR that. This way I could get all my changes but clean up the history a bit.

git diff --binary master my-branch > ../thing.patch

then

git apply ../thing.patch

I know my problem was a bit wonky, and so was my solution. However, hopefully this'll help someone in the future.

I find your description more confusing than illuminating. In particular, the presence (or lack thereof) of a merge is no barrier to git reset . What git reset does is change the commit-ID to which the current branch points.

Any time you make any commit (even a merge), git simply adds a new commit to your repository. Each commit has its own SHA-1 ID, which is sort of its "true name" (that commit has that ID, that ID means that commit, and no one else will ever use that ID). Each commit also contains the ID(s) of it parent(s). A merge simply contains at least two parent-IDs, while regular commits have just one parent-ID. We can say that these parent-IDs "point back" to the parent commits, and therefore we can draw a graph of commits:

A <- B <- C          <-- master
           \
            D <- E   <-- feature

The names on the right are the branch labels, which git generally 1 implements by having files containing the SHA-1 IDs of the tip of each branch. That is, the file for master has the SHA-1 for C , and the file for feature has the SHA-1 for E , here.

To this picture, we should add HEAD , which is basically just a file git uses to keep the name of the "current branch":

A <- B <- C          <-- master
           \
            D <- E   <-- HEAD=feature

What git reset does can now be described very simply 2 as: "Read HEAD to see what branch to change, then change the SHA-1 ID stored in that branch's file to the commit-ID given on the git reset command line." So if HEAD says feature , and master says "commit C ", then:

git reset [options] master

just tells git: "write C 's ID into feature ". (The [options] part makes reset do a bit more or a bit less, like updating index and/or work-tree, or skipping those updates.)

(When you name a commit by branch-name, or HEAD~2 , or any of the names allowed by gitrevisions , git just turns that into a commit-ID. To see this in action, use git rev-parse , which turns a name into a commit-ID:

git rev-parse HEAD
git rev-parse master
git rev-parse feature^

and so on.)

To make a new commit, git writes the new commit into the repository, setting its parent-ID to the ID of the current ( HEAD ) commit (and using the staging area for "what goes into this commit"). Then it writes the ID of the new commit into the current branch file, and it's all done! The branch has been extended, simply by writing that new commit ID into the branch-file.

For illustration, let's add a merge commit to feature . First we'll need to add some more commits to master , unless we're merging yet another branch. Then we'll add a merge commit M . I'm going to stop drawing the arrows, just remember they point left-ward-ish (maybe left and up, etc).

A - B - C - F - G       <-- master
          \       \
            D - E - M   <-- HEAD=feature

Here, commit M is a merge commit because it has two parents: E and G . The feature branch-file contains the ID of M . But git reset works the same way as always: if we tell feature to point to, say, commit D , commits E and M become invisible (though they're still in there) and all we see is this:

A - B - C - F - G       <-- master
          \
            D           <-- HEAD=feature

The ghostly versions of E and M stick around for at least a month by default, held in git's "ref-logs" (and you can actually see them if you use git reflog or git log -g ). But they're no longer "on" the feature branch, which now ends at commit D , because of the git reset we did.

Summary

  • Each commit points to its parent(s).
  • A merge commit points to multiple parents.
  • A git branch name simply points at the tip of the branch, ie, the last commit that should be considered to be "on the branch". That ID is simply stored in the appropriate branch file.
  • HEAD tells git which branch you're on.
  • New commits do git rev-parse HEAD to get their parent-ID, then write their new commit-ID into the branch file. New merge commits do the same thing, except that they record the HEAD ID and the merged-in branch's ID.
  • git reset writes an old (existing) commit-ID into the current branch. With --hard it also updates the index/staging-area, and the work-tree. (With --mixed it only does branch and index, and with --soft it only does branch.)

Note that if you use git reset to rewind feature back to C, and then start doing new git commit s, you simply add new commits, just as before. They have different IDs from your previously-added commits, so the graph looks like this:

A - B - C           <-- master
        | \
         \  D - E   <-- [old feature, via reflog]
          \
            X - Y   <-- feature

(I jumped to X so as to make it clear that these are different from the F , G , etc., above.)


1 More specifically, labels can get "packed", so that a bunch of labels all share one file.

2 A bit too simply, perhaps, since git reset also optionally updates the index/staging-area and/or work-tree.

your workflow is awfull :) rather than resetting and redoing the job (I don't know HOW you are doing the "clean up" of the history) you should use the rebase command.

A part from this, to revert the merge you just have to reset your head to the last commit of your feature branch. So get the sha1 of the last commit (not the id of the merge commit but the previos one) and do

git reset --hard shaidofthecommit

At this point the head of your branch will reset to that commit and you'll discard the merge commit.

I changed a little bit the solution given by @adammenges — because didn't worked for me —.

  • first, run git diff --binary master my-branch > ./changes.patch ## this will create a file called changes.patch with the diff between branches
  • after, run git apply --reverse ./changes.patch ## this will make the reverse changes of the diff

This worked for me :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM