Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GIT: Appending a patch made after a few commits

I use git to keep track of changes made by our development team and committed into our central cvs-style repository. Since its cvs, it keeps track of files and not commits, making it sometimes difficult to tell exactly what files constitute the full patch for a bug fix. I just came across one and did the following:

1) trolling along, checking CVS logs and committing them to git as full patches

A--B--C--D

2) Found another file change that was actually for ticket (B), so I reset the current branch to B with

git reset --soft <sha1 ID for commit B>

3) I copy in the change, and append it to commit (B) with

git commit --amend

4) to my surprise, the tree now reads

A--B

with commits (C) and (D) only in the working tree. Their details are gone from the logs and I don't think I can get them back. Where did I go wrong? Is my only option to make an additional commit on top of (D), and just know that its really part of (B)?

like image 890
David Dombrowsky Avatar asked Oct 09 '09 17:10

David Dombrowsky


People also ask

How do I add staged changes to a previous commit?

Changing the Last Commit: git commit --amend. The git commit --amend command is a convenient way to modify the most recent commit. It lets you combine staged changes with the previous commit instead of creating an entirely new commit.

How does git add patch work?

What is “git add -patch” git add -p is short for git add --patch and it is a git option that allows you to make more specific commits. How it works is that it will go through all new changes in your code and display hunks of them at a time for you to decide what you would like to stage or not stage.

What is git add P?

git add -p is basically "git add partial (or patch)" Patch mode allows you to stage parts of a changed file, instead of the entire file. This allows you to make concise, well-crafted commits that make for an easier to read history. This feature can improve the quality of the commits.


1 Answers

What happened

You mean amend, not append, right? I'm going to pretend this was on a branch called master, for convenience. This is what your repository looks like now:

A---B' (master)
 \
  \-B---C---D

Git commits explicitly depend on their parents - the same patch on top of a different parent is a different commit.

How to recover

You can recover the previous position of a few ways. There's a nice shorthand for previous positions, which you can use to directly check it out or create a branch:

git checkout master@{1}
git branch oldmaster master@{1}

This is assuming it's the first previous position. It might be the second (master@{2})... or if you know when it was, you can use master@{7:35} or master@{23.hours.ago}. For a summary of these forms, see the "specifying revisions" section of man git-rev-parse (online here).

If you're not sure exactly how to get to it, try

git reflog show master

This will give you a list of previous positions of master, and you should be able to tell from the descriptions which one you want (or maybe try a few). You can simply copy hashes from the list, and use git checkout or git branch as above.

What you should have done

Warning: editing history is a bad idea if it's been published already - in that case, you should simply commit the fix. Yes, it's kind of ugly having it split into two commits in the repository, but other users have to be able to trust what they've seen in the public repo not to change!

That said, to do this particular kind of history editing, you want interactive rebase:

git rebase -i master~4 master

master~4 represents the commit four commits before the tip of master. You can use any form you want here - maybe it's another branch, maybe a commit hash - whatever works.

This will open up in an editor a list of the commits you're playing with:

pick <hash-A>  <message-A>
pick <hash-B>  <message-B>
pick <hash-C>  <message-C>
pick <hash-D>  <message-D>

# Rebase <hash-A^>..<hash-D> onto <hash-A^>
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

The commented-out help text there is pretty self-explanatory. In this case, you want to change 'pick' to 'edit' on commit B's line, save, and quit. The rebase will start, and it'll pause after applying B to let you make changes. You'll do what you need to, add, use git commit --amend, and then git rebase --continue. It'll apply C and D, and you'll be done. If anything goes wrong in the middle, use git rebase --abort to get back to where you started.

Rebasing can be kind of scary - for example, don't accidentally remove lines in that list! If you're not comfortable with it yet, it's a good idea to make heavy use of gitk and backup branch names.

like image 60
Cascabel Avatar answered Oct 22 '22 07:10

Cascabel