How do I run git rebase --interactive in non-interactive manner?
-
01-07-2021 - |
Question
Is it possible to do following?
- Make
git rebase --interactive
to just output standard boilerplate to a file, instead to outputting to a file and opening it in editor. - Let the user edit the file.
- Let user re-run
git rebase
with the name of edited file. - Go on with the usual rebase process.
Usecase: scripted rebasing of course. See how to re-order commits in Git non-interactively for example.
La solution
After some thinking and research, the answer turned out to be trivial: git rebase -i
takes the editor name from the well-known EDITOR/VISUAL environment variables, so overriding that to point to a non-interactive script does the job.
However, EDITOR/VISUAL applies indifferently to the list of commits, commit messages when rewording and anything else. So, since http://git.kernel.org/?p=git/git.git;a=commit;h=821881d88d3012a64a52ece9a8c2571ca00c35cd , there's a special environment variable GIT_SEQUENCE_EDITOR which applies only to the commit list.
So, the recipe to re-order or flatten commits is:
Run: GIT_SEQUENCE_EDITOR=<script> git rebase -i <params>
.
Your <script>
should accept a single argument: the path to the file containing the standard rebase commit list. It should rewrite it in-place and exit. Usual rebase processing happens after that.
Autres conseils
Adding on to @pfalcon's answer, you can use sed as your GIT_SEQUENCE_EDITOR. For example, I wanted to edit each commit, so I did this:
GIT_SEQUENCE_EDITOR="sed -i -re 's/^pick /e /'" git rebase -i
The variable GIT_SEQUENCE_EDITOR
was initially used to change the editor. It is possible to pass a script to this variable to use git rebase -i
in a non-interactive manner. So it is possible to use:
GIT_SEQUENCE_EDITOR="sed -i -re 's/^pick 134567/e 1234567/'" git rebase -i 1234567^
This command will run sed
on file provided by git rebase -i
. It will change the line pick 134567
into e 1234567
(and so, edit the commit 1234567). You can change e
with r
(rework), f
(fixup), s
(squash) or d
(drop) (the latter is not supported by old versions of git
).
Based on that, I wrote a script that automatizes this task:
#!/bin/bash
ACTION=$1
COMMIT=$(git rev-parse --short $2)
[[ "$COMMIT" ]] || exit 1
CORRECT=
for A in p pick r reword e edit s squash f fixup d drop t split; do
[[ $ACTION == $A ]] && CORRECT=1
done
[[ "$CORRECT" ]] || exit 1
git merge-base --is-ancestor $COMMIT HEAD || exit 1
if [[ $ACTION == "drop" || $ACTION == "d" ]]; then
GIT_SEQUENCE_EDITOR="sed -i -e '/^pick $COMMIT/d'" git rebase -i $COMMIT^^
elif [[ $ACTION == "split" || $ACTION == "t" ]]; then
GIT_SEQUENCE_EDITOR="sed -i -e 's/^pick $COMMIT/edit $COMMIT/'" git rebase -i $COMMIT^^ || exit 1
git reset --soft HEAD^
echo "Hints:"
echo " Select files to be commited using 'git reset', 'git add' or 'git add -p'"
echo " Commit using 'git commit -c $COMMIT'"
echo " Finish with 'git rebase --continue'"
else
GIT_SEQUENCE_EDITOR="sed -i -e 's/^pick $COMMIT/$1 $COMMIT/'" git rebase -i $COMMIT^^
fi
The first argument should be one action. The script uses the same action names than git-rebase. It also add 'split' action (and allow to use drop
with old versions of git).
It also checks that commit you ask for is an ancestor of HEAD. It a common (and really annoying) mistake with rebase -i
.
The second argument is the commit to want to edit/delete/split/reword.
Then you add an alias to your .gitconfig:
[alias]
autorebase = ! path_to_your_script
Expanding on pfalcon's answer:
Run
GIT_SEQUENCE_EDITOR=<script> git rebase -i <params>
.<script>
should accept single argument - path to file containing standard rebase commit list. The script should rewrite it in-place and exit. Usual rebase processing happens after that.
If you have an environment variable that contains the contents you want:
GIT_SEQUENCE_EDITOR='echo "$REBASE_DATA" >' git rebase -i [<additional params>]
Catting a file would work too:
GIT_SEQUENCE_EDITOR='cat rebase_data_file >' git rebase -i [<additional params>]
You can use touch
as the editor which will touch the file so it will appear modified. For example
GIT_SEQUENCE_EDITOR=touch git rebase -i [commit]
To alias it, given baseline
as a tag I want to rebase against
git config alias.baseline '!GIT_SEQUENCE_EDITOR=touch git rebase -i baseline'
The alias works under Windows because the shell it is executing is bash
not cmd
.
interactive modes brings up the set editor to work with.
the editor in use can be retrieved with:
git config --get core.editor
So, if you set a non-interactive editor - that is an editor that accepts commands on stdin, you can work with --interactive
in a non-interactive way :)
I know for sure vim
accepts commands, and so does the standard editor ed
, ofcourse.
so, hold the interactive editor (if wanted)
$ ied="$(git config --get core.editor)"
set the non-interactive editor
$ git config --unset-all core.editor
$ git config --add core.editor ed
and do work with it..
$ printf '%s\n' "some-ed-cmd" "another-ed-cmd" "wq" | git rebase -i HEAD~5
and restore the editor (if wanted)
$ git config --unset-all core.editor
$ git config --add core.editor "$ied"
I found the solution. You can use:
$ GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash $COMMIT_HASH~1
As others have mentioned, you need to provide a custom GIT_SEQUENCE_EDITOR
value that will modify the interactive rebase file. If you just want to perform an action on a single commit, you can do it as follows:
# Runs "edit" on HEAD~3
GIT_SEQUENCE_EDITOR="sed -i -ze 's/^pick/edit/'" git rebase -i HEAD~3
Alternatively, here's a function to generalize this:
# Usage: git-rebase-custom edit HEAD~3
git-rebase-custom() {
local action=$1
shift
GIT_SEQUENCE_EDITOR="sed -i -ze 's/^pick/$action/'" git rebase -i "$@"
}
Based on Jezz's answer,
I made a shell-agnostic script (GitReb)
which works with multiple-argument revisions,
:/<text>
syntax, root commit and also does some sanity checks.
I also made it simpler and removed the t
/split
action
and delete
->drop
conversion
which IMO are out of this script's scope.