1. 13
  1.  

  2. 12

    This is a very useful thing to do, although magit calls it “instant fixup” (cF) - that’s how I do it.

    1. 11

      Similarly I’m a big fan of git-revise for this and similar tasks.

      1. 9

        git-absorb too.

        1. 2

          git-revise with Zsh completions is awesome. You just press tab and choose the commit to revise from a nice menu. Things that would take three different git invocations and probably some head scratching are now just a couple of keypresses and really easy to get right every time.

      2. 7

        The nice thing about git is that no matter how much you screw up the history, if you haven’t run git gc and you haven’t generated a lot of intermediate history, git reflog will let you find the old tree and undo whatever mistake you made. You can avoid even that kind of problem if you just create a new branch from your current working tree before you do anything, then you have a quick undo:

         $ git branch checkpoint
         $ # This command is may be implemented in terms of git rebase
         $ git break-everything --hard 
         $ git reset --hard checkpoint
        

        I’ve done this kind of splicing before preparing a PR, but it’s never been something I’ve felt the need to automate. Looking at the script, it’s invoking exactly two commands (well, 3, but the git add . is a bit scary and definitely not something I’d want to be the default behaviour). My workflow is normally commit the fix on the top of the tree, git rebase -i, move that commit to immediately after the one that introduced the bug, and mark it as squash. Now the bug was never introduced. The sequence in the script looks slightly less error prone but only very slightly - the only reason I’ve ever had problems with this workflow is if I incorrectly identify the commit that introduced the bug and that problem still exists with this script.

        1. 1

          if I incorrectly identify the commit that introduced the bug

          Yes, it would make sense to add a prompt to the script that says something like:

          Do you really want to amend to “Refactor the flux compensator”?

          Where “Refactor the flux compensator” is the short commit message of the commit identified by the hash the script was given.

          1. 1

            It would be really nice if the script could give you the diff for the files in the fix commit that were in the commit that broke it and the diff for the combined version and say ‘is this really what you meant?’ For the common case, where the fix goes in the same place as the breakage, it would be really nice if it would go and find the commits that touched that code and ask you which one you want.

            The more annoying case is where you’ve modified a function and need to change its callers or similar. There’s very little that tooling that doesn’t look at an AST can do here. Some years ago, I had a student work on a project using libclang (for C/C++) and the Python AST that would try to infer refactorings from commits by generating the tree transforms required to get from the AST in one version to the AST in another. It could do fun things like say ‘function X renamed to Y and all callers updated except the one in oops.c on line 27’. His code was very rough around the edges but I’d really love to see someone implement this properly and incorporate it into code review tooling. Something like that would make it easy to write a really nice script for this: Your fix is in function X, go and look for callers of function X if there are no recent changes to function X.

            1. 1

              Showing the diff of how the old commit will change is a nice idea. Here is a manual way to see it:

              git checkout -b tmp
              git-retroamend.sh <hash_of_the_old_commit>
              git log # to see the new hash of the old commit
              git diff <hash_of_the_old_commit>..<new_hash_of_the_old_commit>
              
        2. 7

          I do this all the time with rebase -i which I know the article discounts, but the workflow is just:

          git rebase -i master
          <edit the line with tmp commit to below the one to amend, change lead to f>
          <save quit>
          
          1. 3

            The nice thing about doing git commit --fixup=<SHA> first is that it will automatically do both the moving it to the right place and changing it to fixup in the interactive rebase.

            1. 5

              Only if you use rebase --autosquash (or set rebase.autoSquash true). There’s a lot that isn’t on by default that should be…

              1. 3

                This is true, and I too am surprised that --autosquash isn’t a normal default. It must be a default in my company’s config. Either way, I couldn’t imagine operating without it being set to true. Why bother doing --fixup otherwise?

                1. 2

                  You imagine git designed!

                  1. 3

                    git: 'designed' is not a git command. See 'git --help'.

                    ;)

          2. 1

            This is cool! I’ve been wanting something like this for a while. I usually commit and rebase, but sometimes I find that a little too slow.

            One thing that I’ve found helpful, I use git log --oneline to get the line number of the commit I want to modify, then use HEAD~~~ where the number of tildes is equal to that line number. I usually type each ~ as I count.

            https://github.com/zachahn/dotfiles/blob/7af6d6d75f354ad5c2b75e156ffe9c9fe03a75ca/_bin/git-fu (fu stands for “fix up”! I have a separate script git fua that runs git add . and then calls this script.)