Skip to content

Instantly share code, notes, and snippets.

@geraldlai
Last active May 2, 2024 21:55
Show Gist options
  • Save geraldlai/8aeb3240e0e7f9f11d8cdb5b7a933125 to your computer and use it in GitHub Desktop.
Save geraldlai/8aeb3240e0e7f9f11d8cdb5b7a933125 to your computer and use it in GitHub Desktop.
git commit-fix: fixup an old commit quickly
#!/usr/bin/perl
# git commit-fix: fixup an old commit quickly
# $ git commit-fix 1efa604
# ( fixup commit 1efa604 to have currently staged changes )
# ( unstaged changes will be safely stashed away )
# $ git commit-fix -s 1efa604
# ( same as above, but allow new commit message to be squashed in )
# $ git commit-fix -e 1efa604
# ( same as fixup, but stop branch on 1efa604 first for any pre-edit )
# ( can be combined with --squash/-s option )
# Author: Gerald Lai
# License: 0BSD
use warnings;
use strict;
use File::Basename qw(basename);
use File::Temp qw(tempfile);
sub run { system(@_) == 0 or exit($? >> 8) }
if ($ENV{GIT_COMMIT_FIX_TARGET}) {
my ($rebase_file) = @ARGV;
if (basename($rebase_file) ne "git-rebase-todo") {
exec(($ENV{EDITOR} || "vim"), @ARGV);
}
if (!$ENV{GIT_COMMIT_FIX_EDIT}) {
exec "touch", @ARGV;
}
open(my $REBASE, "<", $rebase_file)
or die "Cannot read $rebase_file : $!\n";
my ($OUT, $out_file) = tempfile();
while (<$REBASE>) {
if (/^pick ([a-f0-9]+) (.*)/) {
$_ = "edit $1 $2\n" if index($ENV{GIT_COMMIT_FIX_TARGET}, $1) == 0;
}
print $OUT $_;
}
close $OUT;
close $REBASE;
run( "mv", $out_file, $rebase_file );
exit 0;
}
my $is_edit = 0;
my $fixup_opt = "fixup";
my $target;
if (@ARGV) {
local $_ = $ARGV[0];
if (/^(--edit|-e)$/) {
$is_edit = 1;
shift @ARGV;
}
if (/^(--squash|-s)$/) {
$fixup_opt = "squash";
shift @ARGV;
}
}
if (@ARGV) {
$target = $ARGV[0];
$target =~ s{'}{'"'"'}g; # sh escape
shift @ARGV;
}
die "Usage: git commit-fix [ --edit | --squash ] <commit>\n" unless defined $target;
my $is_commit = do {
chomp(my $type = qx{ git cat-file -t '$target' 2> /dev/null });
( $type eq "commit" );
};
die "Not a valid commit: $target\n" unless $is_commit;
chomp($target = qx{ git rev-list -n 1 '$target' });
if (system( qw(git diff --cached --quiet) ) != 0) {
run( qw(git commit), "--$fixup_opt", $target, @ARGV );
}
run( qw(git stash push -u) );
$ENV{GIT_COMMIT_FIX_TARGET} = $target;
$ENV{GIT_COMMIT_FIX_EDIT} = $is_edit;
$ENV{GIT_EDITOR} = $0; # usurp rebase editing
exec qw(git rebase -i --autosquash), "$target~1";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment