Skip to content

Directly Changing Code Stored In Commits

Jesse Duffield edited this page Nov 5, 2019 · 1 revision

I still do not know how best to name this feature. The idea is that often you find snippets of code in commits that shouldn't be there.

3 Common Predicaments:

  1. You accidentally left a console.log or a binding.pry line three commits back
  2. You think that a chunk of code makes more sense in commit abc123 than it does in commit def456 where you've found it
  3. You realise a chunk of code is both incomplete and doesn't belong in the commit you've found it in, and you want to work on it as a whole some more and then put in a fresh commit when completed.

How might you address these situations on the git command line?

Command-line Solutions

1. Assuming the offending lines are on commit abc123:
  1. git rebase --interactive abc123^
  2. edit the todo file to change the 'pick' into an 'edit' for commit abc123
  3. in your editor, delete the offending lines
  4. stage the changes with git add -A
  5. git commit --amend
  6. git rebase --continue

It's really that easy!

2.

When the source commit (source123) is earlier than the destination commit (dest123):

  1. construct a patch (somehow) with the changes you want to copy from your source commit and save it to a file
  2. git rebase --interactive dest123^
  3. edit the todo file to change the 'pick' into an 'edit' for commit dest123
  4. git apply <patch> --index
  5. git commit --amend
  6. git rebase --continue

Here the fact that the changes appear in the destination commit means that when the rebase continues and reaches the source commit, the patch that you created will be omitted from the commit because it's already there in its parent commit.

When the source commit (source123) is later than the destination commit (dest123):

  1. construct a patch (somehow) with the changes you want to copy from your source commit and save it to a file
  2. git rebase --interactive source123^
  3. edit the todo file to change the 'pick' into an 'edit' for both commit source123 and commit dest123
  4. apply the patch in reverse with git apply <patch> --index --reverse
  5. git commit --amend
  6. git rebase --continue
  7. once the rebase reaches the destination commit and halts, apply the patch forwards with git apply <patch> --index
  8. git commit --amend
  9. git rebase --continue

Instead of dealing with patches you could have deleted the code from the source commit manually and then added it back in to the destination commit manually, but that can be hairy when you're dealing with multiple bits of code (admittedly that makes the patch approach hairy as well)

3.
  1. construct a patch (somehow) with the changes you want to copy from your source commit and save it to a file
  2. git rebase --interactive source123^
  3. edit the todo file to change the 'pick' into an 'edit' for commit source123
  4. remove the code from the source commit with git apply <patch> --index --reverse
  5. git commit --amend
  6. git rebase --continue
  7. Then apply the patch forwards so you can access it from your working tree/index with git apply <patch> --index

If you've managed to read through the command line solutions to the 3 use cases you've probably (correctly) come away with the impression that doing this is really hairy and really tedious. I've experienced those emotions myself which is why I've made this feature!

Lazygit Solution

How do we do these things in lazygit? Use case 2 is the most all-encompassing so we'll use that as our example.

Let's say our repo contains a grocery list with some random items, and we've added two additional commits, one for adding junk food, and one for adding dairy. But in the junk food commit we've got the 'milk' item appearing in the list. We should move that to the dairy commit.

(screenshots/video coming soon)

  1. Open lazygit and go to the commits panel by tapping right-arrow twice
  2. press 'enter' on the commit that has the code snippet you want to move to another commit
  3. press 'enter' on the file that has the code snippet
  4. use the arrow keys to move your cursor around the patch in the main left panel and use 'space' to select lines to be added to our custom patch. You can toggle selecting a range of lines with 'v' or select hunks with 'a'. You can also remove lines from the custom patch with 'd'
  5. Once you've selected the lines you want, hit escape until you're back at the commits panel, and then navigate to the commit you want to move the changes to
  6. press ctrl+p to bring up the patch options menu and select the second option

And you're done! If you encounter any merge conflicts, resolve them as you would any other merge conflict, and then press 'm' to bring up the merge/rebase options menu and select 'continue'. Repeat as required.

And that's it. Equal or fewer steps than the command line approaches, and far fewer keystrokes!

Color Indicators

You may not need to 'stage' individual lines within a file's patch, in which case you can stage the whole file's patch by pressing 'space' on the file in the commit files panel, as you would in the regular files panel. Green filenames represent files where the whole patch has been added to our custom patch, and yellow represents only partial patches being added.

Considerations

Under the hood lazygit is basically doing the same thing that you would be doing on the command line, but with a pinch of ingenuity sprinkled in to make patches more likely to apply.

Sometimes patches will not apply because the operation makes no sense, for example trying to cut and paste part of a delete-file patch into a later commit. And sometimes the patch won't apply because something else is awry. When this happens, the rebase will be aborted and you will not lose your changes. In case something does go wrong, the patches that were applied (or that were attempted to be applied) will be stored in the same directory as your lazygit config, under a directory matching the current repo name. That way you should never lose any code.

Restrictions

Currently you can only build up a custom patch for one commit at any time. If you want to add patches from another commit, you will need to discard the patch you had been building up until this point.