0

I know I can do this using rebase -i, but I'm really looking for a way to do it in one line, e.g., with a script. I took a look at How do I run git rebase --interactive in non-interactive manner? for example, but my bashfu is too weak to use it properly.

To be clear, this is what I have right now:

A1B2C3 Some commit
AAAAAA Some other commit
A3B2C1 More commit
BBBBBB Another commit

I want to squash AAAAAA and BBBBBB in one (or several) git lines, non-interactively.

Thanks

Community
  • 1
  • 1
Gal
  • 5,338
  • 5
  • 33
  • 55
  • Are you able to write, in any language/etc., a script/program that will transform the `git rebase -i` list of commits into the order you want and set the commit(s) in question to squash? – Etan Reisner Feb 19 '15 at 13:29
  • I'm okay with python and the likes. I'm just not sure how to apply the script. Should the script receive the rebase -i file (now edited in vim) as input? Or as a raw string? – Gal Feb 19 '15 at 13:43
  • Also, all of the examples in the link I've posted have use inline bash code, which I'm less comfortable with, so I'm not sure how to pass a python script to be applied to begin with... – Gal Feb 19 '15 at 13:46
  • 1
    @Gal, from that linked answer: "Run `GIT_SEQUENCE_EDITOR= – ChrisGPT was on strike Feb 19 '15 at 13:52
  • Indeed, that quote from the linked answer is the one I would focus on. If you can write the script then it just needs to operate on the file it is given. – Etan Reisner Feb 19 '15 at 14:34
  • 1
    If you use `git commit --fixup` when you create the commit then you can avoid the issue altogether. – Andrew C Feb 22 '15 at 22:27

3 Answers3

0

You could emulate rebase interactive with a series of cherry-picks and amended commits:

git checkout AAAAAA
git cherry-pick BBBBBB -n && git commit --amend -m "AAAAA squashed with BBBBB"
git cherry-pick A3B2C1
Mykola Gurov
  • 8,517
  • 4
  • 29
  • 27
0

I ended up using a bash script with the help of the linked question. I'm attaching it for completeness's sake. Note: My bash is ugly. One thing of importance though: Since the script cannot accept arguments, I've opted for a self-modifying code. If there's a better solution (without using globals/environment variables), I'd love to know. Also, I'm not entirely sure how to swap two lines in bash (and google wasn't a great help), so I ended up using a for loop.

#!/bin/bash
commit1=???
commit2=???
if [ "$#" -eq 2 ]; then #2 arguments, first phase of script
    #modify lines 2 and 3 with arguments
    currentFile=${BASH_SOURCE[0]}
    cat $currentFile | sed "2s/.*/commit1=$1/" | sed "3s/.*/commit2=$2/" >| $currentFile
    echo "$currentFile"
    exit
fi

# second phase of script
line1=$(grep -nr "pick $commit1" $1 | cut -f1 -d:)
if [ -z $line1 ]; then
    echo "Could not find a match for $commit1"
    exit
fi
line2=$(grep -nr "pick $commit2" $1 | cut -f1 -d:)
if [ -z $line2 ]; then
    echo "Could not find a match for $commit2"
    exit
fi
oldLine=$(($line1<$line2?$line1:$line2))
newLine=$(($line1>$line2?$line1:$line2))
cat $1
#move newLine to be below oldLine, by iterating over all lines in reverse and swapping one by one
for currentLineIndex in `seq $newLine -1 $(expr $oldLine + 2)`; do
    currentLine=$(sed -n "${currentLineIndex}p" "$1")
    nextLineIndex=$(expr $currentLineIndex - 1)
    nextLine=$(sed -n "${nextLineIndex}p" "$1")
    cat $1 | sed $currentLineIndex"s/.*/$nextLine/" | sed $nextLineIndex"s/.*/$currentLine/" >| $1
done

#change the pick to squash
cat $1 | sed $(expr $oldLine + 1)"s/pick/squash/" >| $1

example use: $ GIT_SEQUENCE_EDITOR=$(./rebase.sh d199 a548) git rebase -i 95e2a7c

Gal
  • 5,338
  • 5
  • 33
  • 55
  • 1
    For what's it's worth, running `cat $1 | ...anything... >| $1` seems liable to make you sad, because it entirely possible that the `>|` will truncate the file before `cat` reads it. – larsks Feb 22 '15 at 21:06
  • @larsks what is the idiomatic alternative? – Gal Feb 23 '15 at 10:43
  • You would typically use a temporary file that gets cleaned up automatically after the script exits: `tmpfile=$(mktemp rebaseXXXXXX); trap "rm -f $tmpfile" EXIT`; then later on `sed ... $1 > $tmpfile; mv $tmpfile $1`. – larsks Feb 23 '15 at 13:56
0

A much simpler alternative, if you want to just fix an existing commit from the staging area: (Thanks to @Andrew C for pointing me towards --fixup)

function git_fix {
    git commit --fixup=$1
    GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash "$1^" #empty script
}

As a git alias:

fix="!f() {\
    git commit --fixup=$1;\
    GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash "$1^";\
}; f"
Gal
  • 5,338
  • 5
  • 33
  • 55