Here's a common workflow:
git commit -m 'add action buttons'
git rebase -i asdf123^
I'd like an alias to automate steps 4-5, e.g. git fixup add action buttons
. The rebase command is:
$ git rebase -i 'HEAD^{/Add action buttons}^'
I think this alias should work, but it doesn't:
[alias]
fixup = !sh -c 'git rebase --interactive HEAD^{/$@}^'
Curiously, it works if I do git fixup add action
, but git fixup add action buttons
throws an error: fatal: invalid upstream 'HEAD^{/action'
.
I think the problem might have to do with quoting, but I can't figure out how to escape the quotes in the alias. I tried this:
[alias]
fixup = !sh -c 'git rebase --interactive \'HEAD^{/$@}^\''
But this throws another error: fatal: bad config line 2 in file /home/dave/.gitconfig
.
This quote from the git-config(1)
man page is useful here:
Shell command aliases always receive any extra arguments provided to the Git command-line as positional arguments.
- Care should be taken if your shell alias is a "one-liner" script with multiple commands (e.g. in a pipeline), references multiple arguments, or is otherwise not able to handle positional arguments added at the end. For example: alias.cmd = "!echo $1 | grep $2" called as git cmd 1 2 will be executed as echo $1 | grep $2 1 2, which is not what you want.
- A convenient way to deal with this is to write your script operations in an inline function that is then called with any arguments from the command-line. For example `alias.cmd = "!c() { echo $1 | grep $2 ; }; c" will correctly execute the prior example.
With that in mind, we can write your alias like this:
[alias]
fixup = "!f() { git rebase -i \"HEAD^{/$*}^\" ; }; f"
If I have a commit history like this:
$ git log --oneline -8
423e981 (HEAD -> main) Reformat with gofmt
b9de0cb (origin/main) Refactor Makefile
be4ac65 Integrate options and common_options
042d122 Rename precommit workflow
d0f41ef Fully qualify package name
829bda3 Remove --exact-match from git describe command line
c8de23e Simplify pre-commit execution of golangci-lint
40b071a Update krew index manifest
Then I can write:
git fixup precommit workflow
And end up with a pick list like this:
pick 042d122 Rename precommit workflow
pick be4ac65 Integrate options and common_options
pick b9de0cb Refactor Makefile
pick 423e981 Reformat with gofmt
Which is what you want.
By trying to call out to the shell, like this...
!sh -c '...'
You are calling a shell from a shell, so you have, effectively:
sh -c "sh -c '...'"
And that makes quoting a royal PITA. You end up with something like:
[alias]
fixup = "!bash -c \"git rebase -i \\\"HEAD^{/\\$*}\\\"\" -- "
Lastly, you should really take a look at stacked git, which sits on top of git
and makes a workflow like this ("go back and make changes to an older commit") super trivial. For the above example, I might do something like:
# pop patches after the named patch so that the named patch becomes HEAD
stg goto rename-precommit-workflow
<make changes>
# update the patch
stg refresh
# push all the patches back so that I return the original HEAD of the branch
stg push -a