2

Situation: I have a single git commit (at HEAD), which has mixed extra logging code, and then some "real code".

Now I would like to do the following:

  1. Edit out all the logging code, basically clean up the codebase
  2. Commit this
    • I now have the desired states in the last two commits, but they are in the wrong order, first everything is added and then logging is removed, when I want other code added and then logging added
  3. Swap these two commits so, that
    • first the actual feature gets added without the logging
    • then the logging code gets added in its own commit

Now I could do this "by hand", by taking a plain copy of the code with logging included, then remove logging as desribed above and squash these commits into one (so there is no logging code in the commit history at all), then restore the copy of the code with logging included, and add that as a new commit.

But is there a way to do this elegantly and with less chance of human error using git features?

To clarify, I do not want to pick which parts to include in the first commit I desire (because there's a lot of small changes there and I don't want to review them again). I want to pick which parts to remove from the first commit, and add as a new 2nd commit.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • Possible duplicate of [How can I reorder/combine commits using Git rebase?](https://stackoverflow.com/questions/2080443/how-can-i-reorder-combine-commits-using-git-rebase) – Joe Jul 24 '19 at 09:45
  • @Joe I did not find solution in that QA. Could you be more specific of which answer, or rather which part of the answer, covers my need? – hyde Jul 24 '19 at 09:53

4 Answers4

2

I don't think there is any way to do it without any chance of human error, but by using some git-features and basically undoing your commit and then only add the parts you want in each commit, you can achieve what you describe.

I would do this by soft-resetting to HEAD~1, and then use git add --patch to only add the parts you want to commit in the first commit, commit that, then add the logging.

Here's how I would approach this:

  1. git reset --mixed HEAD~1 This resets your last commit message and unstages those changes.
  2. git add --patch This gives you an interactive mode where you can select what parts of the unstaged changes you want to stage. If a code hunk is too big (i.e. you want to only commit part of it), you can split it into smaller parts by hitting s and then only stage a smaller part. It supports splitting code hunks down into single-line changes, so hopefully that is good enough for you.
  3. git commit Commit your "real code"
  4. git add . Stage the rest of your changes (the logging code)
  5. git commit again to commit the logging code

By using this approach you should not have to copy-paste any of the code anywhere, thereby at least reducing the chance of human error.

Frost
  • 11,121
  • 3
  • 37
  • 44
  • Tip: `git reset --soft HEAD~1; git reset HEAD .` can be replaced with `git reset --mixed HEAD~1`. – Quentin Jul 24 '19 at 09:39
  • Thank you! Git really has a lot of hidden gems that it sometimes takes a while to find. – Frost Jul 24 '19 at 09:43
  • I want to specifically avoid adding changes one by one. I want to just search for all the logging statements and remove them (from the first commit) without reviewing everything else in it (which I would have to do if I wanted to do it the other way). – hyde Jul 24 '19 at 09:49
  • Aah. I don't think there's git features to do that, but some shell scripting with grep, sed, and patch should be able to achieve that, I think – Frost Jul 24 '19 at 10:10
1

Ok, I worked it out (warning: I did not test this exact command sequence, as there was some trial and error, so if you use this, do not disengage your own brain):

git checkout branch_to_split_the_head
git tag desired_result # also backup, if you haven't pushed
# edit the source to the state for 1st commit, test etc
git add -u
git status # make sure status looks ok
git diff --staged # make sure removed lines etc look ok
git commit --amend --no-edit # create correct 1st commit
git merge -X theirs desired_result # ...add correct 2nd commit
git diff desired_result # there shouldn't be differences
# git push -f # as usual -f needed after rewriting history

Every change will be merge conflict, but merge strategy option -X theirs should ensure they get resolved automatically to the desired end result version.

hyde
  • 60,639
  • 21
  • 115
  • 176
1

Here is the routine which successfully replicates your requirements.

Assuming you have the following history.

ef5e245  Remove logging 
9fffc54  Add real code and logging
bdf6bce  Add initial commit

Checkout at temporary branch.

git checkout -b temp 

Return to master branch and reset to commit before your changes.

git reset --hard bdf6bce

Then do the following (-e allows you to edit commit message while cherry-picking).

git cherry-pick ef5e245 -X theirs -e # Add real code
git cherry-pick 9fffc54 -X theirs -e # Add logging

Eventually you will have a new history with the following commits.

3c8ce38  Add logging
6afa0ab  Add real code
bdf6bce  Add initial commit

You should not have any merge problems, unless you have created new files.

Yevgen
  • 1,576
  • 1
  • 15
  • 17
  • Before accepting/upvoting, did you test this? I mean, this may be a bit n00bish question, but does the first cherry-pick really do what is expected, combining the commits (instead of trying to remove lines which haven't been added)? – hyde Jul 24 '19 at 12:19
0

You could reset to before your last commit, keeping your working tree intact:

git reset HEAD^

You should now have all of your changes in an uncommitted and unstaged state. You can interactively add the changes you want to be in your first commit (i.e. the logging) with git add -p then commit them.

Now you should be left with only your "real code" changes which you can commit separately.

Calum Halpin
  • 1,945
  • 1
  • 10
  • 20