X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=builtin-commit.c;h=02279360f78f3533bcc3b9ab7caae866cb7ee97b;hb=a41acc63badf20cbb22cca61039dca9309deaa19;hp=4de316a366d1ee7201d89b776a4a9a4b499d32b8;hpb=99a12694582e2148fcd492f1eedaddcfe2a21621;p=git.git diff --git a/builtin-commit.c b/builtin-commit.c index 4de316a36..02279360f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -21,12 +21,18 @@ #include "utf8.h" #include "parse-options.h" #include "path-list.h" +#include "unpack-trees.h" static const char * const builtin_commit_usage[] = { "git-commit [options] [--] ...", NULL }; +static const char * const builtin_status_usage[] = { + "git-status [options] [--] ...", + NULL +}; + static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -41,9 +47,22 @@ static enum { static char *logfile, *force_author, *template_file; static char *edit_message, *use_message; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, untracked_files, no_verify; +static int quiet, verbose, untracked_files, no_verify, allow_empty; +/* + * The default commit message cleanup mode will remove the lines + * beginning with # (shell comments) and leading and trailing + * whitespaces (empty lines or containing only whitespaces) + * if editor is used, and only the whitespaces if the message + * is specified explicitly. + */ +static enum { + CLEANUP_SPACE, + CLEANUP_NONE, + CLEANUP_ALL, +} cleanup_mode; +static char *cleanup_arg; -static int no_edit, initial_commit, in_merge; +static int use_editor = 1, initial_commit, in_merge; const char *only_include_assumed; struct strbuf message; @@ -82,6 +101,8 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), OPT_BOOLEAN(0, "untracked-files", &untracked_files, "show all untracked files"), + OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), + OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), OPT_END() }; @@ -157,15 +178,39 @@ static void add_remove_files(struct path_list *list) } } -static char *prepare_index(const char **files, const char *prefix) +static void create_base_index(void) { - int fd; struct tree *tree; + struct unpack_trees_options opts; + struct tree_desc t; + + if (initial_commit) { + discard_cache(); + return; + } + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.index_only = 1; + opts.merge = 1; + + opts.fn = oneway_merge; + tree = parse_tree_indirect(head_sha1); + if (!tree) + die("failed to unpack HEAD tree object"); + parse_tree(tree); + init_tree_desc(&t, tree->buffer, tree->size); + unpack_trees(1, &t, &opts); +} + +static char *prepare_index(int argc, const char **argv, const char *prefix) +{ + int fd; struct path_list partial; const char **pathspec = NULL; if (interactive) { - interactive_add(); + interactive_add(argc, argv, prefix); commit_style = COMMIT_AS_IS; return get_index_file(); } @@ -173,8 +218,8 @@ static char *prepare_index(const char **files, const char *prefix) if (read_cache() < 0) die("index file corrupt"); - if (*files) - pathspec = get_pathspec(prefix, files); + if (*argv) + pathspec = get_pathspec(prefix, argv); /* * Non partial, non as-is commit. @@ -192,7 +237,8 @@ static char *prepare_index(const char **files, const char *prefix) int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(0, also ? prefix : NULL, pathspec); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || close(fd)) + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&index_lock)) die("unable to write new_index file"); commit_style = COMMIT_NORMAL; return index_lock.filename; @@ -211,7 +257,7 @@ static char *prepare_index(const char **files, const char *prefix) fd = hold_locked_index(&index_lock, 1); refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || - close(fd) || commit_locked_index(&index_lock)) + commit_locked_index(&index_lock)) die("unable to write new_index file"); commit_style = COMMIT_AS_IS; return get_index_file(); @@ -253,33 +299,30 @@ static char *prepare_index(const char **files, const char *prefix) fd = hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || close(fd)) + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&index_lock)) die("unable to write new_index file"); fd = hold_lock_file_for_update(&false_lock, git_path("next-index-%d", getpid()), 1); - discard_cache(); - if (!initial_commit) { - tree = parse_tree_indirect(head_sha1); - if (!tree) - die("failed to unpack HEAD tree object"); - if (read_tree(tree, 0, NULL)) - die("failed to read HEAD tree object"); - } + + create_base_index(); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || close(fd)) + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&false_lock)) die("unable to write temporary index file"); return false_lock.filename; } -static int run_status(FILE *fp, const char *index_file, const char *prefix) +static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn) { struct wt_status s; wt_status_prepare(&s); - s.prefix = prefix; + if (wt_status_relative_paths) + s.prefix = prefix; if (amend) { s.amend = 1; @@ -289,6 +332,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix) s.untracked = untracked_files; s.index_file = index_file; s.fp = fp; + s.nowarn = nowarn; wt_status_print(&s); @@ -336,9 +380,10 @@ static int prepare_log_message(const char *index_file, const char *prefix) fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) - die("could not open %s\n", git_path(commit_editmsg)); + die("could not open %s", git_path(commit_editmsg)); - stripspace(&sb, 0); + if (cleanup_mode != CLEANUP_NONE) + stripspace(&sb, 0); if (signoff) { struct strbuf sob; @@ -346,11 +391,9 @@ static int prepare_log_message(const char *index_file, const char *prefix) strbuf_init(&sob, 0); strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_ident(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"), - "", 1)); + strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); - for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { @@ -362,12 +405,37 @@ static int prepare_log_message(const char *index_file, const char *prefix) } if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die("could not write commit template: %s\n", - strerror(errno)); + die("could not write commit template: %s", strerror(errno)); strbuf_release(&sb); - if (in_merge && !no_edit) + if (!use_editor) { + struct rev_info rev; + unsigned char sha1[20]; + const char *parent = "HEAD"; + + fclose(fp); + + if (!active_nr && read_cache() < 0) + die("Cannot read index"); + + if (amend) + parent = "HEAD^1"; + + if (get_sha1(parent, sha1)) + return !!active_nr; + + init_revisions(&rev, ""); + rev.abbrev = 0; + setup_revisions(0, NULL, &rev, parent); + DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); + run_diff_index(&rev, 1 /* cached */); + + return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); + } + + if (in_merge) fprintf(fp, "#\n" "# It looks like you may be committing a MERGE.\n" @@ -380,13 +448,18 @@ static int prepare_log_message(const char *index_file, const char *prefix) fprintf(fp, "\n" "# Please enter the commit message for your changes.\n" - "# (Comment lines starting with '#' will not be included)\n"); + "# (Comment lines starting with '#' will "); + if (cleanup_mode == CLEANUP_ALL) + fprintf(fp, "not be included)\n"); + else /* CLEANUP_SPACE, that is. */ + fprintf(fp, "be kept.\n" + "# You can remove them yourself if you want to)\n"); if (only_include_assumed) fprintf(fp, "# %s\n", only_include_assumed); saved_color_setting = wt_status_use_color; wt_status_use_color = 0; - commitable = run_status(fp, index_file, prefix); + commitable = run_status(fp, index_file, prefix, 1); wt_status_use_color = saved_color_setting; fclose(fp); @@ -404,10 +477,13 @@ static int message_is_empty(struct strbuf *sb, int start) const char *nl; int eol, i; + if (cleanup_mode == CLEANUP_NONE && sb->len) + return 0; + /* See if the template is just a prefix of the message. */ strbuf_init(&tmpl, 0); if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) { - stripspace(&tmpl, 1); + stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); if (start + tmpl.len <= sb->len && memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0) start += tmpl.len; @@ -448,13 +524,13 @@ static void determine_author_info(struct strbuf *sb) a = strstr(use_message_buffer, "\nauthor "); if (!a) - die("invalid commit: %s\n", use_message); + die("invalid commit: %s", use_message); lb = strstr(a + 8, " <"); rb = strstr(a + 8, "> "); eol = strchr(a + 8, '\n'); if (!lb || !rb || !eol) - die("invalid commit: %s\n", use_message); + die("invalid commit: %s", use_message); name = xstrndup(a + 8, lb - (a + 8)); email = xstrndup(lb + 2, rb - (lb + 2)); @@ -466,25 +542,25 @@ static void determine_author_info(struct strbuf *sb) const char *rb = strchr(force_author, '>'); if (!lb || !rb) - die("malformed --author parameter\n"); + die("malformed --author parameter"); name = xstrndup(force_author, lb - force_author); email = xstrndup(lb + 2, rb - (lb + 2)); } - strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, 1)); + strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, IDENT_ERROR_ON_NO_NAME)); } -static int parse_and_validate_options(int argc, const char *argv[]) +static int parse_and_validate_options(int argc, const char *argv[], + const char * const usage[]) { int f = 0; - argc = parse_options(argc, argv, builtin_commit_options, - builtin_commit_usage, 0); + argc = parse_options(argc, argv, builtin_commit_options, usage, 0); if (logfile || message.len || use_message) - no_edit = 1; + use_editor = 0; if (edit_flag) - no_edit = 0; + use_editor = 1; if (get_sha1("HEAD", head_sha1)) initial_commit = 1; @@ -496,7 +572,7 @@ static int parse_and_validate_options(int argc, const char *argv[]) if (amend && initial_commit) die("You have nothing to amend."); if (amend && in_merge) - die("You are in the middle of a merger -- cannot amend."); + die("You are in the middle of a merge -- cannot amend."); if (use_message) f++; @@ -510,7 +586,7 @@ static int parse_and_validate_options(int argc, const char *argv[]) die("Option -m cannot be combined with -c/-C/-F."); if (edit_message) use_message = edit_message; - if (amend) + if (amend && !use_message) use_message = "HEAD"; if (use_message) { unsigned char sha1[20]; @@ -560,6 +636,16 @@ static int parse_and_validate_options(int argc, const char *argv[]) only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; also = 0; } + if (!cleanup_arg || !strcmp(cleanup_arg, "default")) + cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; + else if (!strcmp(cleanup_arg, "verbatim")) + cleanup_mode = CLEANUP_NONE; + else if (!strcmp(cleanup_arg, "whitespace")) + cleanup_mode = CLEANUP_SPACE; + else if (!strcmp(cleanup_arg, "strip")) + cleanup_mode = CLEANUP_ALL; + else + die("Invalid cleanup mode %s", cleanup_arg); if (all && argc > 0) die("Paths with -a does not make sense."); @@ -576,11 +662,11 @@ int cmd_status(int argc, const char **argv, const char *prefix) git_config(git_status_config); - argc = parse_and_validate_options(argc, argv); + argc = parse_and_validate_options(argc, argv, builtin_status_usage); - index_file = prepare_index(argv, prefix); + index_file = prepare_index(argc, argv, prefix); - commitable = run_status(stdout, index_file, prefix); + commitable = run_status(stdout, index_file, prefix, 0); rollback_index_files(); @@ -619,7 +705,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) commit = lookup_commit(sha1); if (!commit) - die("couldn't look up newly created commit\n"); + die("couldn't look up newly created commit"); if (!commit || parse_commit(commit)) die("could not parse newly created commit"); @@ -634,12 +720,20 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; rev.commit_format = get_commit_format("format:%h: %s"); - rev.always_show_header = 1; + rev.always_show_header = 0; + rev.diffopt.detect_rename = 1; + rev.diffopt.rename_limit = 100; + rev.diffopt.break_opt = 0; + diff_setup_done(&rev.diffopt); printf("Created %scommit ", initial_commit ? "initial " : ""); - log_tree_commit(&rev, commit); - printf("\n"); + if (!log_tree_commit(&rev, commit)) { + struct strbuf buf = STRBUF_INIT; + format_commit_message(commit, "%h: %s", &buf); + printf("%s\n", buf.buf); + strbuf_release(&buf); + } } int git_commit_config(const char *k, const char *v) @@ -652,11 +746,30 @@ int git_commit_config(const char *k, const char *v) return git_status_config(k, v); } +static int is_a_merge(const unsigned char *sha1) +{ + struct commit *commit = lookup_commit(sha1); + if (!commit || parse_commit(commit)) + die("could not parse HEAD commit"); + return !!(commit->parents && commit->parents->next); +} + static const char commit_utf8_warn[] = "Warning: commit message does not conform to UTF-8.\n" "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n"; +static void add_parent(struct strbuf *sb, const unsigned char *sha1) +{ + struct object *obj = parse_object(sha1); + const char *parent = sha1_to_hex(sha1); + if (!obj) + die("Unable to find commit parent %s", parent); + if (obj->type != OBJ_COMMIT) + die("Parent %s isn't a proper commit", parent); + strbuf_addf(sb, "parent %s\n", parent); +} + int cmd_commit(int argc, const char **argv, const char *prefix) { int header_len; @@ -668,17 +781,18 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_config(git_commit_config); - argc = parse_and_validate_options(argc, argv); + argc = parse_and_validate_options(argc, argv, builtin_commit_usage); - index_file = prepare_index(argv, prefix); + index_file = prepare_index(argc, argv, prefix); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) { rollback_index_files(); return 1; } - if (!prepare_log_message(index_file, prefix) && !in_merge) { - run_status(stdout, index_file, prefix); + if (!prepare_log_message(index_file, prefix) && !in_merge && + !allow_empty && !(amend && is_a_merge(head_sha1))) { + run_status(stdout, index_file, prefix, 0); rollback_index_files(); unlink(commit_editmsg); return 1; @@ -718,21 +832,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die("could not parse HEAD commit"); for (c = commit->parents; c; c = c->next) - strbuf_addf(&sb, "parent %s\n", - sha1_to_hex(c->item->object.sha1)); + add_parent(&sb, c->item->object.sha1); } else if (in_merge) { struct strbuf m; FILE *fp; reflog_msg = "commit (merge)"; - strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1)); + add_parent(&sb, head_sha1); strbuf_init(&m, 0); fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) die("could not open %s for reading: %s", git_path("MERGE_HEAD"), strerror(errno)); - while (strbuf_getline(&m, fp, '\n') != EOF) - strbuf_addf(&sb, "parent %s\n", m.buf); + while (strbuf_getline(&m, fp, '\n') != EOF) { + unsigned char sha1[20]; + if (get_sha1_hex(m.buf, sha1) < 0) + die("Corrupt MERGE_HEAD file (%s)", m.buf); + add_parent(&sb, sha1); + } fclose(fp); strbuf_release(&m); } else { @@ -741,33 +858,39 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } determine_author_info(&sb); - strbuf_addf(&sb, "committer %s\n", git_committer_info(1)); + strbuf_addf(&sb, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (!is_encoding_utf8(git_commit_encoding)) strbuf_addf(&sb, "encoding %s\n", git_commit_encoding); strbuf_addch(&sb, '\n'); /* Get the commit message and validate it */ header_len = sb.len; - if (!no_edit) - launch_editor(git_path(commit_editmsg), &sb); - else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { - rollback_index_files(); - die("could not read commit message\n"); + if (use_editor) { + char index[PATH_MAX]; + const char *env[2] = { index, NULL }; + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); + launch_editor(git_path(commit_editmsg), NULL, env); } - if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { + if (!no_verify && + run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { rollback_index_files(); exit(1); } + if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { + rollback_index_files(); + die("could not read commit message"); + } /* Truncate the message just before the diff, if any. */ p = strstr(sb.buf, "\ndiff --git a/"); if (p != NULL) - strbuf_setlen(&sb, p - sb.buf); + strbuf_setlen(&sb, p - sb.buf + 1); - stripspace(&sb, 1); + if (cleanup_mode != CLEANUP_NONE) + stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (sb.len < header_len || message_is_empty(&sb, header_len)) { rollback_index_files(); - die("* no commit message? aborting commit."); + die("no commit message? aborting commit."); } strbuf_addch(&sb, '\0'); if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))