Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 0.004008
- Allow to specify paths on the command line to limit fixup commits to these files only

# 0.004007

- Use the `--no-prefix` option for calls to `git diff` so that git-autofixup works regardless the configured values of the relatively new diff.srcPrefix and diff.dstPrefix variables, added in Git 2.45.0, as well as the older diff.noPrefix and diff.mnemonicPrefix.
Expand Down
4 changes: 3 additions & 1 deletion README.pod
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ App::Git::Autofixup - create fixup commits for topic branches

=head1 SYNOPSIS

git-autofixup [<options>] [<revision>]
git-autofixup [<options>] [<revision>] [--] [<path>...]

=head1 DESCRIPTION

F<git-autofixup> parses hunks of changes in the working directory out of C<git diff> output and uses C<git blame> to assign those hunks to commits in C<E<lt>revisionE<gt>..HEAD>, which will typically represent a topic branch, and then creates fixup commits to be used with C<git rebase --interactive --autosquash>. It is assumed that hunks near changes that were previously committed to the topic branch are related. C<E<lt>revisionE<gt>> defaults to C<git merge-base --fork-point HEAD @{upstream} || git merge-base HEAD @{upstream}>, but this will only work if the current branch has an upstream/tracking branch. See C<git help revisions> for info about how to specify revisions.

If any changes have been staged to the index using C<git add>, then F<git-autofixup> will only consider staged hunks when trying to create fixup commits. A temporary index is used to create any resulting commits.

If one or more paths are specified, only changes to those files will be considered. The C<--> separator between the revision and paths is optional.

By default a hunk will be included in a fixup commit if all the lines in the hunk's context blamed on topic branch commits refer to the same commit, so there's no ambiguity about which commit the hunk corresponds to. If there is ambiguity the assignment behaviour used under C<--strict 1> will be used to attempt to resolve it. If C<--strict 1> is given the same topic branch commit must be blamed for every removed line and at least one of the lines adjacent to each added line, and added lines must not be adjacent to lines blamed on other topic branch commits. All the same restrictions apply when C<--strict 2> is given, but each added line must be surrounded by lines blamed on the same topic branch commit.

For example, the added line in the hunk below is adjacent to lines committed by commits C<99f370af> and C<a1eadbe2>. If these are both topic branch commits then it's ambiguous which commit the added line is fixing up and the hunk will be ignored.
Expand Down
39 changes: 33 additions & 6 deletions git-autofixup
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use File::Temp;
use Getopt::Long qw(:config bundling);
use IPC::Open3;

our $VERSION = 0.004007;
our $VERSION = 0.004008;

my $VERBOSE;
my @GIT_OPTIONS;
Expand All @@ -24,7 +24,7 @@ my @STRICTNESS_LEVELS = (
);

my $usage =<<'END';
usage: git-autofixup [<options>] [<revision>]
usage: git-autofixup [<options>] [<revision>] [--] [<path>...]

-h show usage
--help show manpage
Expand Down Expand Up @@ -475,10 +475,14 @@ sub blame {

sub diff_hunks {
my $num_context_lines = shift;
my @files = @_;
my @cmd = git_cmd(qw(diff --no-prefix --no-ext-diff --ignore-submodules), "-U$num_context_lines");
if (is_index_dirty()) {
push @cmd, "--cached";
}
if (@files) {
push @cmd, '--', @files;
}
open(my $fh, '-|', @cmd) or die "run git diff: $!";
my @hunks = parse_hunks($fh, keep_lines => 1);
close($fh) or die "git diff: " . child_error_desc($?) . "\n";
Expand Down Expand Up @@ -671,6 +675,22 @@ sub main {
my $dryrun;
my $use_detailed_exit_codes;

my $raw_upstream;
my @files = ();

my $found_option_terminator = 0;
my @new_argv = ();
for my $arg (@ARGV) {
if ($found_option_terminator) {
push @files, $arg;
} elsif ($arg eq '--') {
$found_option_terminator = 1;
} else {
push @new_argv, $arg;
}
}
@ARGV = @new_argv;

GetOptions(
'h' => \$help,
'help' => \$man,
Expand All @@ -682,6 +702,11 @@ sub main {
'gitopt|g=s' => \@GIT_OPTIONS,
'exit-code' => \$use_detailed_exit_codes,
) or return 1;

if (@ARGV) {
$raw_upstream = shift @ARGV;
@files = (@ARGV, @files);
}
if ($help) {
print $usage;
return 0;
Expand Down Expand Up @@ -712,8 +737,8 @@ EOF

# "upstream" revisions as 40 byte SHA1 hex hashes.
my @upstreams = ();
if (@ARGV == 1) {
my $raw_upstream = shift @ARGV;

if ($raw_upstream) {
my $upstream = rev_parse("${raw_upstream}^{commit}");
push @upstreams, $upstream;
} else {
Expand All @@ -737,7 +762,7 @@ EOF
my $toplevel = toplevel_dir();
chdir $toplevel or die "cd to toplevel: $!\n";

my $hunks = diff_hunks($num_context_lines);
my $hunks = diff_hunks($num_context_lines, @files);
my $summary_for = summary_for_commits(@upstreams);
my $alias_for = sha_aliases($summary_for);
my $grafts_file = create_grafts_file(@upstreams);
Expand Down Expand Up @@ -789,14 +814,16 @@ App::Git::Autofixup - create fixup commits for topic branches

=head1 SYNOPSIS

git-autofixup [<options>] [<revision>]
git-autofixup [<options>] [<revision>] [--] [<path>...]

=head1 DESCRIPTION

F<git-autofixup> parses hunks of changes in the working directory out of C<git diff> output and uses C<git blame> to assign those hunks to commits in C<E<lt>revisionE<gt>..HEAD>, which will typically represent a topic branch, and then creates fixup commits to be used with C<git rebase --interactive --autosquash>. It is assumed that hunks near changes that were previously committed to the topic branch are related. C<E<lt>revisionE<gt>> defaults to C<git merge-base --fork-point HEAD @{upstream} || git merge-base HEAD @{upstream}>, but this will only work if the current branch has an upstream/tracking branch. See C<git help revisions> for info about how to specify revisions.

If any changes have been staged to the index using C<git add>, then F<git-autofixup> will only consider staged hunks when trying to create fixup commits. A temporary index is used to create any resulting commits.

If one or more paths are specified, only changes to those files will be considered. The C<--> separator between the revision and paths is optional.

By default a hunk will be included in a fixup commit if all the lines in the hunk's context blamed on topic branch commits refer to the same commit, so there's no ambiguity about which commit the hunk corresponds to. If there is ambiguity the assignment behaviour used under C<--strict 1> will be used to attempt to resolve it. If C<--strict 1> is given the same topic branch commit must be blamed for every removed line and at least one of the lines adjacent to each added line, and added lines must not be adjacent to lines blamed on other topic branch commits. All the same restrictions apply when C<--strict 2> is given, but each added line must be surrounded by lines blamed on the same topic branch commit.

For example, the added line in the hunk below is adjacent to lines committed by commits C<99f370af> and C<a1eadbe2>. If these are both topic branch commits then it's ambiguous which commit the added line is fixing up and the hunk will be ignored.
Expand Down
2 changes: 1 addition & 1 deletion lib/App/Git/Autofixup.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package App::Git::Autofixup;
use strict;
use warnings FATAL => 'all';

our $VERSION = 0.004007;
our $VERSION = 0.004008;

=head1 NAME

Expand Down
113 changes: 112 additions & 1 deletion t/autofixup.t
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require './t/test.pl';
require './t/util.pl';

Util::check_test_deps();
plan tests => 44;
plan tests => 49;

Test::autofixup_strict(
name => "single-line change gets autofixed",
Expand Down Expand Up @@ -615,3 +615,114 @@ index da0f8ed..c1827f0 100644
EOF
);
}

Test::autofixup(
name => 'file filtering with -- separator (single file)',
topic_commits => [{a => "a1\n", b => "b1\n"}],
unstaged => {a => "a2\n", b => "b2\n"},
autofixup_args => ['--', 'a'],
exit_code => 0,
log_want => <<'EOF'
fixup! commit0

diff --git a a
index da0f8ed..c1827f0 100644
--- a
+++ a
@@ -1 +1 @@
-a1
+a2
EOF
);

Test::autofixup(
name => 'file filtering with -- separator (multiple files)',
topic_commits => [{a => "a1\n", b => "b1\n", c => "c1\n"}],
unstaged => {a => "a2\n", b => "b2\n", c => "c2\n"},
autofixup_args => ['--', 'a', 'b'],
exit_code => 0,
log_want => <<'EOF'
fixup! commit0

diff --git a a
index da0f8ed..c1827f0 100644
--- a
+++ a
@@ -1 +1 @@
-a1
+a2
diff --git b b
index c9c6af7..e6bfff5 100644
--- b
+++ b
@@ -1 +1 @@
-b1
+b2
EOF
);

Test::autofixup(
name => 'file filtering excludes unspecified files',
topic_commits => [{a => "a1\n", b => "b1\n"}],
unstaged => {a => "a2\n", b => "b2\n"},
autofixup_args => ['--', 'a'],
exit_code => 0,
log_want => <<'EOF',
fixup! commit0

diff --git a a
index da0f8ed..c1827f0 100644
--- a
+++ a
@@ -1 +1 @@
-a1
+a2
EOF
unstaged_want => <<'EOF'
diff --git b b
index c9c6af7..e6bfff5 100644
--- b
+++ b
@@ -1 +1 @@
-b1
+b2
EOF
);

Test::autofixup(
name => 'file filtering without the optional -- separator',
topic_commits => [{a => "a1\n", b => "b1\n"}],
unstaged => {a => "a2\n", b => "b2\n"},
autofixup_args => ['a'],
exit_code => 0,
log_want => <<'EOF'
fixup! commit0

diff --git a a
index da0f8ed..c1827f0 100644
--- a
+++ a
@@ -1 +1 @@
-a1
+a2
EOF
);

Test::autofixup(
name => 'file filtering with -- at start (no revision)',
topic_commits => [{a => "a1\n", b => "b1\n"}],
unstaged => {a => "a2\n", b => "b2\n"},
autofixup_args => ['--', 'a'],
exit_code => 0,
log_want => <<'EXPECTED'
fixup! commit0

diff --git a a
index da0f8ed..c1827f0 100644
--- a
+++ a
@@ -1 +1 @@
-a1
+a2
EXPECTED
);
Loading