How do I run git rebase --interactive in non-interactive manner?
Asked Answered
D

11

79

Is it possible to do following?

  1. Make git rebase --interactive to just output standard boilerplate to a file, instead to outputting to a file and opening it in editor.
  2. Let the user edit the file.
  3. Let user re-run git rebase with the name of edited file.
  4. Go on with the usual rebase process.

Usecase: scripted rebasing of course. See how to re-order commits in Git non-interactively for example.

Dagmar answered 12/9, 2012 at 18:22 Comment(2)
See https://mcmap.net/q/13043/-really-flatten-a-git-merge for where this would be useful, too.Dagmar
Besides git rebase below, git filter-branch is another option: stackoverflow.com/questions/19636750/…Truncated
D
73

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.

Dagmar answered 12/9, 2012 at 19:31 Comment(9)
did not know of GIT_SEQUENCE_EDITOR, seems useful ;)Weide
Or, instead of creating a script for this sole purpose, just use the existing command true, which ignores any arguments and has a fixed return code of 0.Brochu
@Brochu this only helps if you want to do a rebase -i without actually reordering the commits.Brote
Here is another example that fixes a typo in the last five commit messages: EDITOR="sed -i -e 's/borken/broken/g'" GIT_SEQUENCE_EDITOR="sed -i -e 's/pick/reword/g'" git rebase -i HEAD~5Truncated
For some unknown reason EDITOR is now ignored by git --version 2.5.5. On the other hand VISUAL still works. So here's another example re-generating the 5 last Change-Id. This assumes the Gerrit commit hook is installed. VISUAL="sed -i -e '/^[[:blank:]]*Change-Id/ d'" GIT_SEQUENCE_EDITOR="sed -i -e 's/pick/reword/g'" git rebase -i HEAD~5 . Tested successfully with git version 2.5.5Truncated
Also: make sure git config --global core.editor returns empty.Truncated
I found VISUAL also didnt work in my attempt at automated git rebase , but GIT_EDITOR worked.Pave
@MarcH: Please mention alternatives in comments to somebody else's answer (yet better in your own answer), not by editing content which has somebody else's signature. Thanks.Dagmar
Both EDITOR and VISUAL worked for me. git-2.26.0.Blackmarketeer
H
22

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
Honolulu answered 13/3, 2013 at 19:36 Comment(1)
-e is optional in this case. Actually you can even reduce it to sed -ri 's/pick/e/'.Blackmarketeer
P
10

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
Precincts answered 9/10, 2013 at 8:45 Comment(1)
Nice answer, thank you! Could you add comments to explain a little bit what each step does?Linehan
S
8

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>]
Slack answered 22/5, 2014 at 15:42 Comment(0)
S
7

To do exactly what the original question asks, use sed -i '1s/^/b\n/' as your editor

git -c sequence.editor="sed -i '1s/^/b\n/'" rebase --interactive

Explanation

  • git -c specifies to run the following git command with a specified setting
  • sequence.editor the setting that determines the "editor" which will edit the redbase-todo file
  • sed -i run sed command in place
    • 1s on the first line
    • ^ match the beginning
    • b\n insert b and a newline \n character. b or break is the rebase command to halt the interactive rebase.
  • rebase --interactive run rebase command

This stops the interactive rebase immediately. Now you can edit the .git/rebase-merge/git-rebase-todo and run git rebase --continue.

Syndic answered 10/12, 2021 at 9:36 Comment(0)
A
5

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.

Alamode answered 14/5, 2016 at 7:36 Comment(2)
This works well with git --interactive --exec <cmd> <branch>. I set <cmd> to a command which runs the tests introduced or modified by my branch, and <branch> to master. That causes git to run my tests against each commit in the branch.Reiff
You can use git's native aliases by using -c sequence.editor=touch instead of GIT_SEQUENCE_EDITOR. For example, in your .gitconfig [alias] section: rs = -c sequence.editor=touch rebase --interactive --autosquash --autostash.Southport
W
3

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"
Weide answered 12/9, 2012 at 19:33 Comment(4)
Thanks, we probably started to write answers at similar time, I didn't see yours before I posted mine ;-)Dagmar
I think it's simpler to re-define EDITOR, either on a per-session or per-command basis.Truncated
I'd strongly advise against doing git config --unset-all or whatever can modify the user's config file in a script. To set a git config variable for one command, use git -c var=val, and in this case setting EDITOR is much simpler. It's an environment variable, so applies only to the current process, it won't disturb other processes or write anything to disk.Hankhanke
I found setting EDITOR doesnt work any more, and I needed to use GIT_EDITOR instead.Pave
J
2

I found the solution. You can use:

$ GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash $COMMIT_HASH~1
Jerilynjeritah answered 8/8, 2019 at 9:46 Comment(0)
N
0

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 "$@"
}
Nerti answered 4/8, 2019 at 2:50 Comment(2)
Should quote $1 and $@Kilowatt
Quoting doesn't matter in variable assignment, but I did remove the assigning to variables because it would only pick up the first extra argument unless array assignment was used.Nerti
R
0

Based on all answers, I made a small script :

reword() {
    COMMITHASH=$1
    GIT_SEQUENCE_EDITOR="sed -i -re 's/^pick $COMMITHASH/r $COMMITHASH/'" git rebase -i $COMMITHASH~
}

For example, if you want to reword the commit 3d4e4bdc, you can just run the following command in your terminal :

reword 3d4e4bdc

You can add it to your file my_aliases.zsh or .bashrc

NOTE : This example is for a reword. If you want to edit the commit, you can replace $COMMITHASH/r by $COMMITHASH/e

Regardful answered 14/11, 2022 at 23:26 Comment(0)
M
-1

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.

Macarthur answered 23/2, 2017 at 8:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.