Reputation: 8828
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?
Upvotes: 3
Views: 2226
Reputation: 1
I changed a little bit the solution given by @adammenges — because didn't worked for me —.
git diff --binary master my-branch > ./changes.patch ## this will create a file called changes.patch with the diff between branches
git apply --reverse ./changes.patch ## this will make the reverse changes of the diff
This worked for me :)
Upvotes: 0
Reputation: 8828
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.
Upvotes: 7
Reputation: 488183
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 generally1 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 simply2 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.
HEAD
tells git which branch you're on.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.)
1More specifically, labels can get "packed", so that a bunch of labels all share one file.
2A bit too simply, perhaps, since git reset
also optionally updates the index/staging-area and/or work-tree.
Upvotes: 4
Reputation: 835
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.
Upvotes: 0