Skip to content

Commit 424f89f

Browse files
committed
Merge branch 'pt/am-builtin-options'
After "git am --opt1" stops, running "git am --opt2" pays attention to "--opt2" only for the patch that caused the original invocation to stop. * pt/am-builtin-options: am: let --signoff override --no-signoff am: let command-line options override saved options test_terminal: redirect child process' stdin to a pty
2 parents 080cc64 + b5e8235 commit 424f89f

File tree

3 files changed

+166
-10
lines changed

3 files changed

+166
-10
lines changed

builtin/am.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ enum scissors_type {
9898
SCISSORS_TRUE /* pass --scissors to git-mailinfo */
9999
};
100100

101+
enum signoff_type {
102+
SIGNOFF_FALSE = 0,
103+
SIGNOFF_TRUE = 1,
104+
SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
105+
};
106+
101107
struct am_state {
102108
/* state directory path */
103109
char *dir;
@@ -123,7 +129,7 @@ struct am_state {
123129
int interactive;
124130
int threeway;
125131
int quiet;
126-
int signoff;
132+
int signoff; /* enum signoff_type */
127133
int utf8;
128134
int keep; /* enum keep_type */
129135
int message_id;
@@ -1185,6 +1191,18 @@ static void NORETURN die_user_resolve(const struct am_state *state)
11851191
exit(128);
11861192
}
11871193

1194+
/**
1195+
* Appends signoff to the "msg" field of the am_state.
1196+
*/
1197+
static void am_append_signoff(struct am_state *state)
1198+
{
1199+
struct strbuf sb = STRBUF_INIT;
1200+
1201+
strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
1202+
append_signoff(&sb, 0, 0);
1203+
state->msg = strbuf_detach(&sb, &state->msg_len);
1204+
}
1205+
11881206
/**
11891207
* Parses `mail` using git-mailinfo, extracting its patch and authorship info.
11901208
* state->msg will be set to the patch message. state->author_name,
@@ -1779,7 +1797,6 @@ static void am_run(struct am_state *state, int resume)
17791797

17801798
if (resume) {
17811799
validate_resume_state(state);
1782-
resume = 0;
17831800
} else {
17841801
int skip;
17851802

@@ -1841,6 +1858,10 @@ static void am_run(struct am_state *state, int resume)
18411858

18421859
next:
18431860
am_next(state);
1861+
1862+
if (resume)
1863+
am_load(state);
1864+
resume = 0;
18441865
}
18451866

18461867
if (!is_empty_file(am_path(state, "rewritten"))) {
@@ -1895,6 +1916,7 @@ static void am_resolve(struct am_state *state)
18951916

18961917
next:
18971918
am_next(state);
1919+
am_load(state);
18981920
am_run(state, 0);
18991921
}
19001922

@@ -2022,6 +2044,7 @@ static void am_skip(struct am_state *state)
20222044
die(_("failed to clean index"));
20232045

20242046
am_next(state);
2047+
am_load(state);
20252048
am_run(state, 0);
20262049
}
20272050

@@ -2132,6 +2155,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
21322155
int keep_cr = -1;
21332156
int patch_format = PATCH_FORMAT_UNKNOWN;
21342157
enum resume_mode resume = RESUME_FALSE;
2158+
int in_progress;
21352159

21362160
const char * const usage[] = {
21372161
N_("git am [options] [(<mbox>|<Maildir>)...]"),
@@ -2147,8 +2171,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
21472171
OPT_BOOL('3', "3way", &state.threeway,
21482172
N_("allow fall back on 3way merging if needed")),
21492173
OPT__QUIET(&state.quiet, N_("be quiet")),
2150-
OPT_BOOL('s', "signoff", &state.signoff,
2151-
N_("add a Signed-off-by line to the commit message")),
2174+
OPT_SET_INT('s', "signoff", &state.signoff,
2175+
N_("add a Signed-off-by line to the commit message"),
2176+
SIGNOFF_EXPLICIT),
21522177
OPT_BOOL('u', "utf8", &state.utf8,
21532178
N_("recode into utf8 (default)")),
21542179
OPT_SET_INT('k', "keep", &state.keep,
@@ -2227,6 +2252,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
22272252

22282253
am_state_init(&state, git_path("rebase-apply"));
22292254

2255+
in_progress = am_in_progress(&state);
2256+
if (in_progress)
2257+
am_load(&state);
2258+
22302259
argc = parse_options(argc, argv, prefix, options, usage, 0);
22312260

22322261
if (binary >= 0)
@@ -2239,7 +2268,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
22392268
if (read_index_preload(&the_index, NULL) < 0)
22402269
die(_("failed to read the index"));
22412270

2242-
if (am_in_progress(&state)) {
2271+
if (in_progress) {
22432272
/*
22442273
* Catch user error to feed us patches when there is a session
22452274
* in progress:
@@ -2258,7 +2287,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
22582287
if (resume == RESUME_FALSE)
22592288
resume = RESUME_APPLY;
22602289

2261-
am_load(&state);
2290+
if (state.signoff == SIGNOFF_EXPLICIT)
2291+
am_append_signoff(&state);
22622292
} else {
22632293
struct argv_array paths = ARGV_ARRAY_INIT;
22642294
int i;

t/t4153-am-resume-override-opts.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/bin/sh
2+
3+
test_description='git-am command-line options override saved options'
4+
5+
. ./test-lib.sh
6+
. "$TEST_DIRECTORY"/lib-terminal.sh
7+
8+
format_patch () {
9+
git format-patch --stdout -1 "$1" >"$1".eml
10+
}
11+
12+
test_expect_success 'setup' '
13+
test_commit initial file &&
14+
test_commit first file &&
15+
16+
git checkout initial &&
17+
git mv file file2 &&
18+
test_tick &&
19+
git commit -m renamed-file &&
20+
git tag renamed-file &&
21+
22+
git checkout -b side initial &&
23+
test_commit side1 file &&
24+
test_commit side2 file &&
25+
26+
format_patch side1 &&
27+
format_patch side2
28+
'
29+
30+
test_expect_success TTY '--3way overrides --no-3way' '
31+
rm -fr .git/rebase-apply &&
32+
git reset --hard &&
33+
git checkout renamed-file &&
34+
35+
# Applying side1 will fail as the file has been renamed.
36+
test_must_fail git am --no-3way side[12].eml &&
37+
test_path_is_dir .git/rebase-apply &&
38+
test_cmp_rev renamed-file HEAD &&
39+
test -z "$(git ls-files -u)" &&
40+
41+
# Applying side1 with am --3way will succeed due to the threeway-merge.
42+
# Applying side2 will fail as --3way does not apply to it.
43+
test_must_fail test_terminal git am --3way </dev/zero &&
44+
test_path_is_dir .git/rebase-apply &&
45+
test side1 = "$(cat file2)"
46+
'
47+
48+
test_expect_success '--no-quiet overrides --quiet' '
49+
rm -fr .git/rebase-apply &&
50+
git reset --hard &&
51+
git checkout first &&
52+
53+
# Applying side1 will be quiet.
54+
test_must_fail git am --quiet side[123].eml >out &&
55+
test_path_is_dir .git/rebase-apply &&
56+
! test_i18ngrep "^Applying: " out &&
57+
echo side1 >file &&
58+
git add file &&
59+
60+
# Applying side1 will not be quiet.
61+
# Applying side2 will be quiet.
62+
git am --no-quiet --continue >out &&
63+
echo "Applying: side1" >expected &&
64+
test_i18ncmp expected out
65+
'
66+
67+
test_expect_success '--signoff overrides --no-signoff' '
68+
rm -fr .git/rebase-apply &&
69+
git reset --hard &&
70+
git checkout first &&
71+
72+
test_must_fail git am --no-signoff side[12].eml &&
73+
test_path_is_dir .git/rebase-apply &&
74+
echo side1 >file &&
75+
git add file &&
76+
git am --signoff --continue &&
77+
78+
# Applied side1 will be signed off
79+
echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
80+
git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
81+
test_cmp expected actual &&
82+
83+
# Applied side2 will not be signed off
84+
test $(git cat-file commit HEAD | grep -c "Signed-off-by:") -eq 0
85+
'
86+
87+
test_expect_success TTY '--reject overrides --no-reject' '
88+
rm -fr .git/rebase-apply &&
89+
git reset --hard &&
90+
git checkout first &&
91+
rm -f file.rej &&
92+
93+
test_must_fail git am --no-reject side1.eml &&
94+
test_path_is_dir .git/rebase-apply &&
95+
test_path_is_missing file.rej &&
96+
97+
test_must_fail test_terminal git am --reject </dev/zero &&
98+
test_path_is_dir .git/rebase-apply &&
99+
test_path_is_file file.rej
100+
'
101+
102+
test_done

t/test-terminal.perl

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
use IO::Pty;
66
use File::Copy;
77

8-
# Run @$argv in the background with stdio redirected to $out and $err.
8+
# Run @$argv in the background with stdio redirected to $in, $out and $err.
99
sub start_child {
10-
my ($argv, $out, $err) = @_;
10+
my ($argv, $in, $out, $err) = @_;
1111
my $pid = fork;
1212
if (not defined $pid) {
1313
die "fork failed: $!"
1414
} elsif ($pid == 0) {
15+
open STDIN, "<&", $in;
1516
open STDOUT, ">&", $out;
1617
open STDERR, ">&", $err;
18+
close $in;
1719
close $out;
1820
exec(@$argv) or die "cannot exec '$argv->[0]': $!"
1921
}
@@ -49,6 +51,17 @@ sub xsendfile {
4951
copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!";
5052
}
5153

54+
sub copy_stdin {
55+
my ($in) = @_;
56+
my $pid = fork;
57+
if (!$pid) {
58+
xsendfile($in, \*STDIN);
59+
exit 0;
60+
}
61+
close($in);
62+
return $pid;
63+
}
64+
5265
sub copy_stdio {
5366
my ($out, $err) = @_;
5467
my $pid = fork;
@@ -67,14 +80,25 @@ sub copy_stdio {
6780
if ($#ARGV < 1) {
6881
die "usage: test-terminal program args";
6982
}
83+
my $master_in = new IO::Pty;
7084
my $master_out = new IO::Pty;
7185
my $master_err = new IO::Pty;
86+
$master_in->set_raw();
7287
$master_out->set_raw();
7388
$master_err->set_raw();
89+
$master_in->slave->set_raw();
7490
$master_out->slave->set_raw();
7591
$master_err->slave->set_raw();
76-
my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave);
92+
my $pid = start_child(\@ARGV, $master_in->slave, $master_out->slave, $master_err->slave);
93+
close $master_in->slave;
7794
close $master_out->slave;
7895
close $master_err->slave;
96+
my $in_pid = copy_stdin($master_in);
7997
copy_stdio($master_out, $master_err);
80-
exit(finish_child($pid));
98+
my $ret = finish_child($pid);
99+
# If the child process terminates before our copy_stdin() process is able to
100+
# write all of its data to $master_in, the copy_stdin() process could stall.
101+
# Send SIGTERM to it to ensure it terminates.
102+
kill 'TERM', $in_pid;
103+
finish_child($in_pid);
104+
exit($ret);

0 commit comments

Comments
 (0)