diff options
Diffstat (limited to 'builtin/commit.c')
-rw-r--r-- | builtin/commit.c | 384 |
1 files changed, 246 insertions, 138 deletions
diff --git a/builtin/commit.c b/builtin/commit.c index 243b0c3300..cda74e9a68 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "color.h" #include "dir.h" @@ -42,7 +43,20 @@ static const char * const builtin_status_usage[] = { NULL }; -static const char implicit_ident_advice[] = +static const char implicit_ident_advice_noconfig[] = +N_("Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly. Run the\n" +"following command and follow the instructions in your editor to edit\n" +"your configuration file:\n" +"\n" +" git config --global --edit\n" +"\n" +"After doing this, you may fix the identity used for this commit with:\n" +"\n" +" git commit --amend --reset-author\n"); + +static const char implicit_ident_advice_config[] = N_("Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" "You can suppress this message by setting them explicitly:\n" @@ -302,8 +316,8 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static char *prepare_index(int argc, const char **argv, const char *prefix, - const struct commit *current_head, int is_status) +static const char *prepare_index(int argc, const char **argv, const char *prefix, + const struct commit *current_head, int is_status) { struct string_list partial; struct pathspec pathspec; @@ -328,7 +342,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -339,10 +353,17 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename); + read_cache_from(index_lock.filename.buf); + if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { + if (reopen_lock_file(&index_lock) < 0) + die(_("unable to write index file")); + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + die(_("unable to update temporary index")); + } else + warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename; + return index_lock.filename.buf; } /* @@ -365,7 +386,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return index_lock.filename; + return index_lock.filename.buf; } /* @@ -380,8 +401,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (!only && !pathspec.nr) { hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); - if (active_cache_changed) { + if (active_cache_changed + || !cache_tree_fully_valid(active_cache_tree)) { update_main_cache_tree(WRITE_TREE_SILENT); + active_cache_changed = 1; + } + if (active_cache_changed) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die(_("unable to write new_index file")); @@ -420,8 +445,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("cannot do a partial commit during a cherry-pick.")); } - memset(&partial, 0, sizeof(partial)); - partial.strdup_strings = 1; + string_list_init(&partial, 1); if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); @@ -432,6 +456,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); + update_main_cache_tree(WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); @@ -448,9 +473,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("unable to write temporary index file")); discard_cache(); - read_cache_from(false_lock.filename); + read_cache_from(false_lock.filename.buf); - return false_lock.filename; + return false_lock.filename.buf; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, @@ -521,56 +546,82 @@ static int sane_ident_split(struct ident_split *person) return 1; } +static int parse_force_date(const char *in, struct strbuf *out) +{ + strbuf_addch(out, '@'); + + if (parse_date(in, out) < 0) { + int errors = 0; + unsigned long t = approxidate_careful(in, &errors); + if (errors) + return -1; + strbuf_addf(out, "%lu", t); + } + + return 0; +} + +static void set_ident_var(char **buf, char *val) +{ + free(*buf); + *buf = val; +} + +static char *envdup(const char *var) +{ + const char *val = getenv(var); + return val ? xstrdup(val) : NULL; +} + static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; struct ident_split author; - name = getenv("GIT_AUTHOR_NAME"); - email = getenv("GIT_AUTHOR_EMAIL"); - date = getenv("GIT_AUTHOR_DATE"); + name = envdup("GIT_AUTHOR_NAME"); + email = envdup("GIT_AUTHOR_EMAIL"); + date = envdup("GIT_AUTHOR_DATE"); if (author_message) { - const char *a, *lb, *rb, *eol; + struct ident_split ident; size_t len; + const char *a; - a = strstr(author_message_buffer, "\nauthor "); + a = find_commit_header(author_message_buffer, "author", &len); if (!a) - die(_("invalid commit: %s"), author_message); - - lb = strchrnul(a + strlen("\nauthor "), '<'); - rb = strchrnul(lb, '>'); - eol = strchrnul(rb, '\n'); - if (!*lb || !*rb || !*eol) - die(_("invalid commit: %s"), author_message); - - if (lb == a + strlen("\nauthor ")) - /* \nauthor <foo@example.com> */ - name = xcalloc(1, 1); - else - name = xmemdupz(a + strlen("\nauthor "), - (lb - strlen(" ") - - (a + strlen("\nauthor ")))); - email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); - len = eol - (rb + strlen("> ")); - date = xmalloc(len + 2); - *date = '@'; - memcpy(date + 1, rb + strlen("> "), len); - date[len + 1] = '\0'; + die(_("commit '%s' lacks author header"), author_message); + if (split_ident_line(&ident, a, len) < 0) + die(_("commit '%s' has malformed author line"), author_message); + + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + + if (ident.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); + strbuf_addch(&date_buf, ' '); + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } } if (force_author) { - const char *lb = strstr(force_author, " <"); - const char *rb = strchr(force_author, '>'); + struct ident_split ident; - if (!lb || !rb) + if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) die(_("malformed --author parameter")); - name = xstrndup(force_author, lb - force_author); - email = xstrndup(lb + 2, rb - (lb + 2)); + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + } + + if (force_date) { + struct strbuf date_buf = STRBUF_INIT; + if (parse_force_date(force_date, &date_buf)) + die(_("invalid date format: %s"), force_date); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - if (force_date) - date = force_date; strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); if (!split_ident_line(&author, author_ident->buf, author_ident->len) && sane_ident_split(&author)) { @@ -578,15 +629,52 @@ static void determine_author_info(struct strbuf *author_ident) export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); } + + free(name); + free(email); + free(date); +} + +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf) +{ + if (split_ident_line(id, buf->buf, buf->len) || + !sane_ident_split(id)) + die(_("Malformed ident string: '%s'"), buf->buf); +} + +static int author_date_is_interesting(void) +{ + return author_message || force_date; } -static char *cut_ident_timestamp_part(char *string) +static void adjust_comment_line_char(const struct strbuf *sb) { - char *ket = strrchr(string, '>'); - if (!ket || ket[1] != ' ') - die(_("Malformed ident string: '%s'"), string); - *++ket = '\0'; - return ket; + char candidates[] = "#;@!$%^&|:"; + char *candidate; + const char *p; + + comment_line_char = candidates[0]; + if (!memchr(sb->buf, comment_line_char, sb->len)) + return; + + p = sb->buf; + candidate = strchr(candidates, *p); + if (candidate) + *candidate = ' '; + for (p = sb->buf; *p; p++) { + if ((p[0] == '\n' || p[0] == '\r') && p[1]) { + candidate = strchr(candidates, p[1]); + if (candidate) + *candidate = ' '; + } + } + + for (p = candidates; *p == ' '; p++) + ; + if (!*p) + die(_("unable to select a comment character that is not used\n" + "in the current commit message")); + comment_line_char = *p; } static int prepare_to_commit(const char *index_file, const char *prefix, @@ -645,9 +733,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } else if (use_message) { char *buffer; buffer = strstr(use_message_buffer, "\n\n"); - if (!use_editor && (!buffer || buffer[2] == '\0')) - die(_("commit has empty message")); - strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); + if (buffer) + strbuf_addstr(&sb, buffer + 2); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { @@ -713,36 +800,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (clean_message_contents) stripspace(&sb, 0); - if (signoff) { - /* - * See if we have a Conflicts: block at the end. If yes, count - * its size, so we can ignore it. - */ - int ignore_footer = 0; - int i, eol, previous = 0; - const char *nl; - - for (i = 0; i < sb.len; i++) { - nl = memchr(sb.buf + i, '\n', sb.len - i); - if (nl) - eol = nl - sb.buf; - else - eol = sb.len; - if (starts_with(sb.buf + previous, "\nConflicts:\n")) { - ignore_footer = sb.len - previous; - break; - } - while (i < eol) - i++; - previous = eol; - } - - append_signoff(&sb, ignore_footer, 0); - } + if (signoff) + append_signoff(&sb, ignore_non_trailer(&sb), 0); if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); + if (auto_comment_line_char) + adjust_comment_line_char(&sb); strbuf_release(&sb); /* This checks if committer ident is explicitly given */ @@ -750,7 +815,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (use_editor && include_status) { int ident_shown = 0; int saved_color_setting; - char *ai_tmp, *ci_tmp; + struct ident_split ci, ai; + if (whence != FROM_COMMIT) { if (cleanup_mode == CLEANUP_SCISSORS) wt_status_add_cut_line(s->fp); @@ -790,32 +856,39 @@ static int prepare_to_commit(const char *index_file, const char *prefix, status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); - ai_tmp = cut_ident_timestamp_part(author_ident->buf); - ci_tmp = cut_ident_timestamp_part(committer_ident.buf); - if (strcmp(author_ident->buf, committer_ident.buf)) + split_ident_or_die(&ai, author_ident); + split_ident_or_die(&ci, &committer_ident); + + if (ident_cmp(&ai, &ci)) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Author: %.*s <%.*s>"), + ident_shown++ ? "" : "\n", + (int)(ai.name_end - ai.name_begin), ai.name_begin, + (int)(ai.mail_end - ai.mail_begin), ai.mail_begin); + + if (author_date_is_interesting()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Author: %s"), + "Date: %s"), ident_shown++ ? "" : "\n", - author_ident->buf); + show_ident_date(&ai, DATE_NORMAL)); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Committer: %s"), + "Committer: %.*s <%.*s>"), ident_shown++ ? "" : "\n", - committer_ident.buf); + (int)(ci.name_end - ci.name_begin), ci.name_begin, + (int)(ci.mail_end - ci.mail_begin), ci.mail_begin); if (ident_shown) - status_printf_ln(s, GIT_COLOR_NORMAL, ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); saved_color_setting = s->use_color; s->use_color = 0; commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; - - *ai_tmp = ' '; - *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -828,8 +901,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (get_sha1(parent, sha1)) commitable = !!active_nr; - else - commitable = index_differs_from(parent, 0); + else { + /* + * Unless the user did explicitly request a submodule + * ignore mode by passing a command line option we do + * not ignore any changed submodule SHA-1s when + * comparing index and parent, no matter what is + * configured. Otherwise we won't commit any + * submodules which were manually staged, which would + * be really confusing. + */ + int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG; + if (ignore_submodule_arg && + !strcmp(ignore_submodule_arg, "all")) + diff_flags |= DIFF_OPT_IGNORE_SUBMODULES; + commitable = index_differs_from(parent, diff_flags); + } } strbuf_release(&committer_ident); @@ -936,7 +1023,7 @@ static int message_is_empty(struct strbuf *sb) static int template_untouched(struct strbuf *sb) { struct strbuf tmpl = STRBUF_INIT; - char *start; + const char *start; if (cleanup_mode == CLEANUP_NONE && sb->len) return 0; @@ -945,8 +1032,7 @@ static int template_untouched(struct strbuf *sb) return 0; stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - start = (char *)skip_prefix(sb->buf, tmpl.buf); - if (!start) + if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); return rest_is_empty(sb, start - sb->buf); @@ -971,7 +1057,8 @@ static const char *find_author_by_nickname(const char *name) revs.mailmap = &mailmap; read_mailmap(revs.mailmap, NULL); - prepare_revision_walk(&revs); + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; @@ -1161,22 +1248,21 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix, return commitable ? 0 : 1; } -static int parse_status_slot(const char *var, int offset) +static int parse_status_slot(const char *slot) { - if (!strcasecmp(var+offset, "header")) + if (!strcasecmp(slot, "header")) return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "branch")) + if (!strcasecmp(slot, "branch")) return WT_STATUS_ONBRANCH; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) + if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added")) return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) + if (!strcasecmp(slot, "changed")) return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) + if (!strcasecmp(slot, "untracked")) return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) + if (!strcasecmp(slot, "nobranch")) return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) + if (!strcasecmp(slot, "unmerged")) return WT_STATUS_UNMERGED; return -1; } @@ -1184,6 +1270,7 @@ static int parse_status_slot(const char *var, int offset) static int git_status_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + const char *slot_name; if (starts_with(k, "column.")) return git_column_config(k, v, "status", &s->colopts); @@ -1213,14 +1300,14 @@ static int git_status_config(const char *k, const char *v, void *cb) s->display_comment_prefix = git_config_bool(k, v); return 0; } - if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) { - int slot = parse_status_slot(k, 13); + if (skip_prefix(k, "status.color.", &slot_name) || + skip_prefix(k, "color.status.", &slot_name)) { + int slot = parse_status_slot(slot_name); if (slot < 0) return 0; if (!v) return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; + return color_parse(v, s->color_palette[slot]); } if (!strcmp(k, "status.relativepaths")) { s->relative_paths = git_config_bool(k, v); @@ -1325,6 +1412,24 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } +static const char *implicit_ident_advice(void) +{ + char *user_config = NULL; + char *xdg_config = NULL; + int config_exists; + + home_config_paths(&user_config, &xdg_config, "config"); + config_exists = file_exists(user_config) || file_exists(xdg_config); + free(user_config); + free(xdg_config); + + if (config_exists) + return _(implicit_ident_advice_config); + else + return _(implicit_ident_advice_noconfig); + +} + static void print_summary(const char *prefix, const unsigned char *sha1, int initial_commit) { @@ -1351,12 +1456,19 @@ static void print_summary(const char *prefix, const unsigned char *sha1, strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } + if (author_date_is_interesting()) { + struct strbuf date = STRBUF_INIT; + format_commit_message(commit, "%ad", &date, &pctx); + strbuf_addstr(&format, "\n Date: "); + strbuf_addbuf_percentquote(&format, &date); + strbuf_release(&date); + } if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, _(implicit_ident_advice)); + strbuf_addstr(&format, implicit_ident_advice()); } } strbuf_release(&author_ident); @@ -1377,14 +1489,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); - printf("[%s%s ", - starts_with(head, "refs/heads/") ? - head + 11 : - !strcmp(head, "HEAD") ? - _("detached HEAD") : - head, - initial_commit ? _(" (root-commit)") : ""); + head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL); + if (!strcmp(head, "HEAD")) + head = _("detached HEAD"); + else + skip_prefix(head, "refs/heads/", &head); + printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1424,7 +1534,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1, { /* oldsha1 SP newsha1 LF NUL */ static char buf[2*40 + 3]; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; int code; size_t n; @@ -1436,7 +1546,6 @@ static int run_rewrite_hook(const unsigned char *oldsha1, argv[1] = "amend"; argv[2] = NULL; - memset(&proc, 0, sizeof(proc)); proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; @@ -1536,11 +1645,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) const char *index_file, *reflog_msg; char *nl; unsigned char sha1[20]; - struct ref_lock *ref_lock; struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1654,20 +1764,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix) append_merge_tag_headers(parents, &tail); } - if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, - author_ident.buf, sign_commit, extra)) { + if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1, + parents, sha1, author_ident.buf, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); free_commit_extra_headers(extra); - ref_lock = lock_any_ref_for_update("HEAD", - !current_head - ? NULL - : current_head->object.sha1, - 0, NULL); - nl = strchr(sb.buf, '\n'); if (nl) strbuf_setlen(&sb, nl + 1 - sb.buf); @@ -1676,14 +1780,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix) strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - if (!ref_lock) { - rollback_index_files(); - die(_("cannot lock HEAD ref")); - } - if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, "HEAD", sha1, + current_head + ? current_head->object.sha1 : NULL, + 0, !!current_head, sb.buf, &err) || + ref_transaction_commit(transaction, &err)) { rollback_index_files(); - die(_("cannot update HEAD ref")); + die("%s", err.buf); } + ref_transaction_free(transaction); unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("REVERT_HEAD")); @@ -1694,7 +1801,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full or quota is\n" + "new_index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); @@ -1712,5 +1819,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!quiet) print_summary(prefix, sha1, !current_head); + strbuf_release(&err); return 0; } |