77

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.

Community
  • 1
  • 1
pfalcon
  • 6,724
  • 4
  • 35
  • 43
  • See http://stackoverflow.com/questions/12270357/really-flatten-a-git-merge for where this would be useful, too. – pfalcon Sep 12 '12 at 18:53
  • 1
    Besides git rebase below, git filter-branch is another option: http://stackoverflow.com/questions/19636750/git-filter-branch-msg-filter-to-reword-a-pushed-commit-message – MarcH Nov 08 '16 at 22:50

11 Answers11

70

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.

pfalcon
  • 6,724
  • 4
  • 35
  • 43
  • did not know of `GIT_SEQUENCE_EDITOR`, seems useful ;) – c00kiemon5ter Sep 12 '12 at 19:34
  • 2
    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`. – me_and Sep 13 '12 at 16:45
  • 5
    @me_and this only helps if you want to do a `rebase -i` without actually reordering the commits. – Paŭlo Ebermann Jul 08 '14 at 18:55
  • 13
    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~5` – MarcH Mar 23 '15 at 23:40
  • 4
    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.5 – MarcH Nov 08 '16 at 22:44
  • Also: make sure `git config --global core.editor` returns empty. – MarcH Nov 08 '16 at 23:02
  • 2
    I found `VISUAL` also didnt work in my attempt at [automated git rebase](https://stackoverflow.com/questions/30949067/git-squash-by-author-all-author-commits-into-a-single-commit/46403701#46403701) , but `GIT_EDITOR` worked. – John Vandenberg Sep 25 '17 at 12:27
  • @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. – pfalcon Oct 28 '17 at 11:40
  • Both `EDITOR` and `VISUAL` worked for me. `git-2.26.0`. – x-yuri May 13 '20 at 20:23
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
Leif Wickland
  • 3,693
  • 26
  • 43
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
Jérôme Pouiller
  • 9,249
  • 5
  • 39
  • 47
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>]
James Foucar
  • 91
  • 1
  • 2
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.

CervEd
  • 3,306
  • 28
  • 25
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.

Archimedes Trajano
  • 35,625
  • 19
  • 175
  • 265
  • 2
    This works well with `git --interactive --exec `. I set `` to a command which runs the tests introduced or modified by my branch, and `` to `master`. That causes git to run my tests against each commit in the branch. – Mike Oct 11 '17 at 09:43
  • 1
    You can use git's native `alias`es 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`. – nh2 Jun 06 '20 at 12:46
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"
c00kiemon5ter
  • 16,994
  • 7
  • 46
  • 48
  • Thanks, we probably started to write answers at similar time, I didn't see yours before I posted mine ;-) – pfalcon Sep 12 '12 at 19:51
  • I think it's simpler to re-define EDITOR, either on a per-session or per-command basis. – MarcH Mar 23 '15 at 23:34
  • 1
    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. – Matthieu Moy May 14 '16 at 18:48
  • I found setting `EDITOR` doesnt work any more, and I needed to use `GIT_EDITOR` instead. – John Vandenberg Sep 25 '17 at 11:39
2

I found the solution. You can use:

$ GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash $COMMIT_HASH~1
Wooseong Kim
  • 1,871
  • 2
  • 21
  • 19
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 "$@"
}
Matthew D. Scholefield
  • 2,977
  • 3
  • 31
  • 42
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

Jack'
  • 1,722
  • 1
  • 19
  • 27
-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.

Community
  • 1
  • 1
John Gliksberg
  • 122
  • 2
  • 6