Skip to content

Commit 6b2a524

Browse files
committed
Merge branch 'pw/am-rebase-read-author-script'
Unify code to read the author-script used in "git am" and the commands that use the sequencer machinery, e.g. "git rebase -i". * pw/am-rebase-read-author-script: sequencer: use read_author_script() add read_author_script() to libgit am: rename read_author_script() am: improve author-script error reporting am: don't die in read_author_script()
2 parents fd4bb38 + 4d010a7 commit 6b2a524

File tree

3 files changed

+128
-127
lines changed

3 files changed

+128
-127
lines changed

builtin/am.c

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -260,32 +260,6 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state,
260260
die_errno(_("could not read '%s'"), am_path(state, file));
261261
}
262262

263-
/**
264-
* Take a series of KEY='VALUE' lines where VALUE part is
265-
* sq-quoted, and append <KEY, VALUE> at the end of the string list
266-
*/
267-
static int parse_key_value_squoted(char *buf, struct string_list *list)
268-
{
269-
while (*buf) {
270-
struct string_list_item *item;
271-
char *np;
272-
char *cp = strchr(buf, '=');
273-
if (!cp)
274-
return -1;
275-
np = strchrnul(cp, '\n');
276-
*cp++ = '\0';
277-
item = string_list_append(list, buf);
278-
279-
buf = np + (*np == '\n');
280-
*np = '\0';
281-
cp = sq_dequote(cp);
282-
if (!cp)
283-
return -1;
284-
item->util = xstrdup(cp);
285-
}
286-
return 0;
287-
}
288-
289263
/**
290264
* Reads and parses the state directory's "author-script" file, and sets
291265
* state->author_name, state->author_email and state->author_date accordingly.
@@ -302,42 +276,16 @@ static int parse_key_value_squoted(char *buf, struct string_list *list)
302276
* script, and thus if the file differs from what this function expects, it is
303277
* better to bail out than to do something that the user does not expect.
304278
*/
305-
static int read_author_script(struct am_state *state)
279+
static int read_am_author_script(struct am_state *state)
306280
{
307281
const char *filename = am_path(state, "author-script");
308-
struct strbuf buf = STRBUF_INIT;
309-
struct string_list kv = STRING_LIST_INIT_DUP;
310-
int retval = -1; /* assume failure */
311-
int fd;
312282

313283
assert(!state->author_name);
314284
assert(!state->author_email);
315285
assert(!state->author_date);
316286

317-
fd = open(filename, O_RDONLY);
318-
if (fd < 0) {
319-
if (errno == ENOENT)
320-
return 0;
321-
die_errno(_("could not open '%s' for reading"), filename);
322-
}
323-
strbuf_read(&buf, fd, 0);
324-
close(fd);
325-
if (parse_key_value_squoted(buf.buf, &kv))
326-
goto finish;
327-
328-
if (kv.nr != 3 ||
329-
strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") ||
330-
strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") ||
331-
strcmp(kv.items[2].string, "GIT_AUTHOR_DATE"))
332-
goto finish;
333-
state->author_name = kv.items[0].util;
334-
state->author_email = kv.items[1].util;
335-
state->author_date = kv.items[2].util;
336-
retval = 0;
337-
finish:
338-
string_list_clear(&kv, !!retval);
339-
strbuf_release(&buf);
340-
return retval;
287+
return read_author_script(filename, &state->author_name,
288+
&state->author_email, &state->author_date, 1);
341289
}
342290

343291
/**
@@ -411,7 +359,7 @@ static void am_load(struct am_state *state)
411359
BUG("state file 'last' does not exist");
412360
state->last = strtol(sb.buf, NULL, 10);
413361

414-
if (read_author_script(state) < 0)
362+
if (read_am_author_script(state) < 0)
415363
die(_("could not parse author script"));
416364

417365
read_commit_msg(state);

sequencer.c

Lines changed: 121 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -669,55 +669,131 @@ static int write_author_script(const char *message)
669669
return res;
670670
}
671671

672+
/**
673+
* Take a series of KEY='VALUE' lines where VALUE part is
674+
* sq-quoted, and append <KEY, VALUE> at the end of the string list
675+
*/
676+
static int parse_key_value_squoted(char *buf, struct string_list *list)
677+
{
678+
while (*buf) {
679+
struct string_list_item *item;
680+
char *np;
681+
char *cp = strchr(buf, '=');
682+
if (!cp) {
683+
np = strchrnul(buf, '\n');
684+
return error(_("no key present in '%.*s'"),
685+
(int) (np - buf), buf);
686+
}
687+
np = strchrnul(cp, '\n');
688+
*cp++ = '\0';
689+
item = string_list_append(list, buf);
690+
691+
buf = np + (*np == '\n');
692+
*np = '\0';
693+
cp = sq_dequote(cp);
694+
if (!cp)
695+
return error(_("unable to dequote value of '%s'"),
696+
item->string);
697+
item->util = xstrdup(cp);
698+
}
699+
return 0;
700+
}
672701

673-
/*
674-
* write_author_script() used to fail to terminate the last line with a "'" and
675-
* also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". We check for
676-
* the terminating "'" on the last line to see how "'" has been escaped in case
677-
* git was upgraded while rebase was stopped.
702+
/**
703+
* Reads and parses the state directory's "author-script" file, and sets name,
704+
* email and date accordingly.
705+
* Returns 0 on success, -1 if the file could not be parsed.
706+
*
707+
* The author script is of the format:
708+
*
709+
* GIT_AUTHOR_NAME='$author_name'
710+
* GIT_AUTHOR_EMAIL='$author_email'
711+
* GIT_AUTHOR_DATE='$author_date'
712+
*
713+
* where $author_name, $author_email and $author_date are quoted. We are strict
714+
* with our parsing, as the file was meant to be eval'd in the old
715+
* git-am.sh/git-rebase--interactive.sh scripts, and thus if the file differs
716+
* from what this function expects, it is better to bail out than to do
717+
* something that the user does not expect.
678718
*/
679-
static int quoting_is_broken(const char *s, size_t n)
719+
int read_author_script(const char *path, char **name, char **email, char **date,
720+
int allow_missing)
680721
{
681-
/* Skip any empty lines in case the file was hand edited */
682-
while (n > 0 && s[--n] == '\n')
683-
; /* empty */
684-
if (n > 0 && s[n] != '\'')
685-
return 1;
722+
struct strbuf buf = STRBUF_INIT;
723+
struct string_list kv = STRING_LIST_INIT_DUP;
724+
int retval = -1; /* assume failure */
725+
int i, name_i = -2, email_i = -2, date_i = -2, err = 0;
686726

687-
return 0;
727+
if (strbuf_read_file(&buf, path, 256) <= 0) {
728+
strbuf_release(&buf);
729+
if (errno == ENOENT && allow_missing)
730+
return 0;
731+
else
732+
return error_errno(_("could not open '%s' for reading"),
733+
path);
734+
}
735+
736+
if (parse_key_value_squoted(buf.buf, &kv))
737+
goto finish;
738+
739+
for (i = 0; i < kv.nr; i++) {
740+
if (!strcmp(kv.items[i].string, "GIT_AUTHOR_NAME")) {
741+
if (name_i != -2)
742+
name_i = error(_("'GIT_AUTHOR_NAME' already given"));
743+
else
744+
name_i = i;
745+
} else if (!strcmp(kv.items[i].string, "GIT_AUTHOR_EMAIL")) {
746+
if (email_i != -2)
747+
email_i = error(_("'GIT_AUTHOR_EMAIL' already given"));
748+
else
749+
email_i = i;
750+
} else if (!strcmp(kv.items[i].string, "GIT_AUTHOR_DATE")) {
751+
if (date_i != -2)
752+
date_i = error(_("'GIT_AUTHOR_DATE' already given"));
753+
else
754+
date_i = i;
755+
} else {
756+
err = error(_("unknown variable '%s'"),
757+
kv.items[i].string);
758+
}
759+
}
760+
if (name_i == -2)
761+
error(_("missing 'GIT_AUTHOR_NAME'"));
762+
if (email_i == -2)
763+
error(_("missing 'GIT_AUTHOR_EMAIL'"));
764+
if (date_i == -2)
765+
error(_("missing 'GIT_AUTHOR_DATE'"));
766+
if (date_i < 0 || email_i < 0 || date_i < 0 || err)
767+
goto finish;
768+
*name = kv.items[name_i].util;
769+
*email = kv.items[email_i].util;
770+
*date = kv.items[date_i].util;
771+
retval = 0;
772+
finish:
773+
string_list_clear(&kv, !!retval);
774+
strbuf_release(&buf);
775+
return retval;
688776
}
689777

690778
/*
691-
* Read a list of environment variable assignments (such as the author-script
692-
* file) into an environment block. Returns -1 on error, 0 otherwise.
779+
* Read a GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL AND GIT_AUTHOR_DATE from a
780+
* file with shell quoting into struct argv_array. Returns -1 on
781+
* error, 0 otherwise.
693782
*/
694783
static int read_env_script(struct argv_array *env)
695784
{
696-
struct strbuf script = STRBUF_INIT;
697-
int i, count = 0, sq_bug;
698-
const char *p2;
699-
char *p;
785+
char *name, *email, *date;
700786

701-
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
787+
if (read_author_script(rebase_path_author_script(),
788+
&name, &email, &date, 0))
702789
return -1;
703-
/* write_author_script() used to quote incorrectly */
704-
sq_bug = quoting_is_broken(script.buf, script.len);
705-
for (p = script.buf; *p; p++)
706-
if (sq_bug && skip_prefix(p, "'\\\\''", &p2))
707-
strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
708-
else if (skip_prefix(p, "'\\''", &p2))
709-
strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
710-
else if (*p == '\'')
711-
strbuf_splice(&script, p-- - script.buf, 1, "", 0);
712-
else if (*p == '\n') {
713-
*p = '\0';
714-
count++;
715-
}
716790

717-
for (i = 0, p = script.buf; i < count; i++) {
718-
argv_array_push(env, p);
719-
p += strlen(p) + 1;
720-
}
791+
argv_array_pushf(env, "GIT_AUTHOR_NAME=%s", name);
792+
argv_array_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
793+
argv_array_pushf(env, "GIT_AUTHOR_DATE=%s", date);
794+
free(name);
795+
free(email);
796+
free(date);
721797

722798
return 0;
723799
}
@@ -737,54 +813,28 @@ static char *get_author(const char *message)
737813
/* Read author-script and return an ident line (author <email> timestamp) */
738814
static const char *read_author_ident(struct strbuf *buf)
739815
{
740-
const char *keys[] = {
741-
"GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
742-
};
743816
struct strbuf out = STRBUF_INIT;
744-
char *in, *eol;
745-
const char *val[3];
746-
int i = 0;
817+
char *name, *email, *date;
747818

748-
if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
819+
if (read_author_script(rebase_path_author_script(),
820+
&name, &email, &date, 0))
749821
return NULL;
750822

751-
/* dequote values and construct ident line in-place */
752-
for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
753-
if (!skip_prefix(in, keys[i], (const char **)&in)) {
754-
warning(_("could not parse '%s' (looking for '%s')"),
755-
rebase_path_author_script(), keys[i]);
756-
return NULL;
757-
}
758-
759-
eol = strchrnul(in, '\n');
760-
*eol = '\0';
761-
if (!sq_dequote(in)) {
762-
warning(_("bad quoting on %s value in '%s'"),
763-
keys[i], rebase_path_author_script());
764-
return NULL;
765-
}
766-
val[i] = in;
767-
in = eol + 1;
768-
}
769-
770-
if (i < 3) {
771-
warning(_("could not parse '%s' (looking for '%s')"),
772-
rebase_path_author_script(), keys[i]);
773-
return NULL;
774-
}
775-
776823
/* validate date since fmt_ident() will die() on bad value */
777-
if (parse_date(val[2], &out)){
824+
if (parse_date(date, &out)){
778825
warning(_("invalid date format '%s' in '%s'"),
779-
val[2], rebase_path_author_script());
826+
date, rebase_path_author_script());
780827
strbuf_release(&out);
781828
return NULL;
782829
}
783830

784831
strbuf_reset(&out);
785-
strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0));
832+
strbuf_addstr(&out, fmt_ident(name, email, date, 0));
786833
strbuf_swap(buf, &out);
787834
strbuf_release(&out);
835+
free(name);
836+
free(email);
837+
free(date);
788838
return buf->buf;
789839
}
790840

sequencer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
130130
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
131131
void print_commit_summary(const char *prefix, const struct object_id *oid,
132132
unsigned int flags);
133+
134+
int read_author_script(const char *path, char **name, char **email, char **date,
135+
int allow_missing);
133136
#endif
134137

135138
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);

0 commit comments

Comments
 (0)