From d63c2fd192271ea2d85c81edfad90aa42fec26ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Fri, 2 Nov 2007 11:33:06 -0400 Subject: Add testcase for amending and fixing author in git commit. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We used to clobber author time, but we shouldn't. Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 9dba104..e601028 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -242,4 +242,19 @@ test_expect_success 'multiple -m' ' ' +author="The Real Author " +test_expect_success 'amend commit to fix author' ' + + oldtick=$GIT_AUTHOR_DATE && + test_tick && + git reset --hard && + git cat-file -p HEAD | + sed -e "s/author.*/author $author $oldtick/" \ + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ + expected && + git commit --amend --author="$author" && + git cat-file -p HEAD > current && + diff expected current + +' test_done -- cgit v0.10.2-6-g49f6 From 943316e96ca2dad67086af2f945e42467a27ddd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Fri, 2 Nov 2007 11:33:08 -0400 Subject: Export launch_editor() and make it accept ':' as a no-op editor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-tag.c b/builtin-tag.c index cbb0f04..88a5449 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -17,7 +17,7 @@ static const char builtin_tag_usage[] = static char signingkey[1000]; -static void launch_editor(const char *path, struct strbuf *buffer) +void launch_editor(const char *path, struct strbuf *buffer) { const char *editor, *terminal; struct child_process child; @@ -42,6 +42,9 @@ static void launch_editor(const char *path, struct strbuf *buffer) if (!editor) editor = "vi"; + if (!strcmp(editor, ":")) + return; + memset(&child, 0, sizeof(child)); child.argv = args; args[0] = editor; diff --git a/strbuf.h b/strbuf.h index 1391912..8334a9b 100644 --- a/strbuf.h +++ b/strbuf.h @@ -117,5 +117,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); extern int strbuf_getline(struct strbuf *, FILE *, int); extern void stripspace(struct strbuf *buf, int skip_comments); +extern void launch_editor(const char *path, struct strbuf *buffer); #endif /* STRBUF_H */ -- cgit v0.10.2-6-g49f6 From f5bbc3225c4b073a7ff3218164a0c820299bc9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Thu, 8 Nov 2007 11:59:00 -0500 Subject: Port git commit to C. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes git commit a builtin and moves git-commit.sh to contrib/examples. This also removes the git-runstatus helper, which was mostly just a git-status.sh implementation detail. Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index c8c13f5..bbd7f55 100644 --- a/.gitignore +++ b/.gitignore @@ -109,7 +109,6 @@ git-rev-list git-rev-parse git-revert git-rm -git-runstatus git-send-email git-send-pack git-sh-setup diff --git a/Makefile b/Makefile index 7a0ee78..35f9c87 100644 --- a/Makefile +++ b/Makefile @@ -213,7 +213,7 @@ BASIC_LDFLAGS = SCRIPT_SH = \ git-bisect.sh git-checkout.sh \ - git-clean.sh git-clone.sh git-commit.sh \ + git-clean.sh git-clone.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ @@ -260,7 +260,7 @@ EXTRA_PROGRAMS = BUILT_INS = \ git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ git-get-tar-commit-id$X git-init$X git-repo-config$X \ - git-fsck-objects$X git-cherry-pick$X \ + git-fsck-objects$X git-cherry-pick$X git-status$X\ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir @@ -330,6 +330,7 @@ BUILTIN_OBJS = \ builtin-check-attr.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ + builtin-commit.o \ builtin-commit-tree.o \ builtin-count-objects.o \ builtin-describe.o \ @@ -369,7 +370,6 @@ BUILTIN_OBJS = \ builtin-rev-parse.o \ builtin-revert.o \ builtin-rm.o \ - builtin-runstatus.o \ builtin-shortlog.o \ builtin-show-branch.o \ builtin-stripspace.o \ @@ -838,9 +838,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ && \ mv $@+ $@ -git-status: git-commit - $(QUIET_GEN)cp $< $@+ && mv $@+ $@ - gitweb/gitweb.cgi: gitweb/gitweb.perl $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ diff --git a/builtin-commit.c b/builtin-commit.c new file mode 100644 index 0000000..669cc6b --- /dev/null +++ b/builtin-commit.c @@ -0,0 +1,614 @@ +/* + * Builtin "git commit" + * + * Copyright (c) 2007 Kristian Høgsberg + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds + */ + +#include "cache.h" +#include "cache-tree.h" +#include "builtin.h" +#include "diff.h" +#include "diffcore.h" +#include "commit.h" +#include "revision.h" +#include "wt-status.h" +#include "run-command.h" +#include "refs.h" +#include "log-tree.h" +#include "strbuf.h" +#include "utf8.h" +#include "parse-options.h" + +static const char * const builtin_commit_usage[] = { + "git-commit [options] [--] ...", + NULL +}; + +static unsigned char head_sha1[20], merge_head_sha1[20]; +static char *use_message_buffer; +static const char commit_editmsg[] = "COMMIT_EDITMSG"; +static struct lock_file lock_file; + +static char *logfile, *force_author, *message, *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 no_edit, initial_commit, in_merge; +const char *only_include_assumed; + +static struct option builtin_commit_options[] = { + OPT__QUIET(&quiet), + OPT__VERBOSE(&verbose), + OPT_GROUP("Commit message options"), + + OPT_STRING('F', "file", &logfile, "FILE", "read log from file"), + OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), + OPT_STRING('m', "message", &message, "MESSAGE", "specify commit message"), + OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), + OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by: header"), + OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"), + OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), + + OPT_GROUP("Commit contents options"), + OPT_BOOLEAN('a', "all", &all, "commit all changed files"), + OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), + OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), + OPT_BOOLEAN('o', "only", &only, ""), + 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_END() +}; + +static char *prepare_index(const char **files, const char *prefix) +{ + int fd; + struct tree *tree; + struct lock_file *next_index_lock; + + if (interactive) { + interactive_add(); + return get_index_file(); + } + + fd = hold_locked_index(&lock_file, 1); + if (read_cache() < 0) + die("index file corrupt"); + + if (all || also) { + add_files_to_cache(verbose, also ? prefix : NULL, files); + if (write_cache(fd, active_cache, active_nr) || close(fd)) + die("unable to write new_index file"); + return lock_file.filename; + } + + if (*files == NULL) { + /* Commit index as-is. */ + rollback_lock_file(&lock_file); + return get_index_file(); + } + + /* update the user index file */ + add_files_to_cache(verbose, prefix, files); + if (write_cache(fd, active_cache, active_nr) || close(fd)) + die("unable to write new_index file"); + + 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"); + } + + /* Use a lock file to garbage collect the temporary index file. */ + next_index_lock = xmalloc(sizeof(*next_index_lock)); + fd = hold_lock_file_for_update(next_index_lock, + git_path("next-index-%d", getpid()), 1); + add_files_to_cache(verbose, prefix, files); + if (write_cache(fd, active_cache, active_nr) || close(fd)) + die("unable to write new_index file"); + + return next_index_lock->filename; +} + +static int run_status(FILE *fp, const char *index_file) +{ + struct wt_status s; + + wt_status_prepare(&s); + + if (amend) { + s.amend = 1; + s.reference = "HEAD^1"; + } + s.verbose = verbose; + s.untracked = untracked_files; + s.index_file = index_file; + s.fp = fp; + + wt_status_print(&s); + + return s.commitable; +} + +static const char sign_off_header[] = "Signed-off-by: "; + +static int prepare_log_message(const char *index_file) +{ + struct stat statbuf; + int commitable; + struct strbuf sb; + char *buffer; + FILE *fp; + + strbuf_init(&sb, 0); + if (message) { + strbuf_add(&sb, message, strlen(message)); + } else if (logfile && !strcmp(logfile, "-")) { + if (isatty(0)) + fprintf(stderr, "(reading log message from standard input)\n"); + if (strbuf_read(&sb, 0, 0) < 0) + die("could not read log from standard input"); + } else if (logfile) { + if (strbuf_read_file(&sb, logfile, 0) < 0) + die("could not read log file '%s': %s", + logfile, strerror(errno)); + } else if (use_message) { + buffer = strstr(use_message_buffer, "\n\n"); + if (!buffer || buffer[2] == '\0') + die("commit has empty message"); + strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); + } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { + if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) + die("could not read MERGE_MSG: %s", strerror(errno)); + } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { + if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) + die("could not read SQUASH_MSG: %s", strerror(errno)); + } else if (template_file && !stat(template_file, &statbuf)) { + if (strbuf_read_file(&sb, template_file, 0) < 0) + die("could not read %s: %s", + template_file, strerror(errno)); + } + + fp = fopen(git_path(commit_editmsg), "w"); + if (fp == NULL) + die("could not open %s\n", git_path(commit_editmsg)); + + stripspace(&sb, 0); + if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) + die("could not write commit template: %s\n", + strerror(errno)); + + if (signoff) { + const char *info, *bol; + + info = git_committer_info(1); + strbuf_addch(&sb, '\0'); + bol = strrchr(sb.buf + sb.len - 1, '\n'); + if (!bol || prefixcmp(bol, sign_off_header)) + fprintf(fp, "\n"); + fprintf(fp, "%s%s\n", sign_off_header, git_committer_info(1)); + } + + strbuf_release(&sb); + + if (in_merge && !no_edit) + fprintf(fp, + "#\n" + "# It looks like you may be committing a MERGE.\n" + "# If this is not correct, please remove the file\n" + "# %s\n" + "# and try again.\n" + "#\n", + git_path("MERGE_HEAD")); + + fprintf(fp, + "\n" + "# Please enter the commit message for your changes.\n" + "# (Comment lines starting with '#' will not be included)\n"); + if (only_include_assumed) + fprintf(fp, "# %s\n", only_include_assumed); + + commitable = run_status(fp, index_file); + + fclose(fp); + + return commitable; +} + +/* + * Find out if the message starting at position 'start' in the strbuf + * contains only whitespace and Signed-off-by lines. + */ +static int message_is_empty(struct strbuf *sb, int start) +{ + struct strbuf tmpl; + const char *nl; + int eol, i; + + /* 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); + if (start + tmpl.len <= sb->len && + memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0) + start += tmpl.len; + } + strbuf_release(&tmpl); + + /* Check if the rest is just whitespace and Signed-of-by's. */ + for (i = start; i < sb->len; i++) { + nl = memchr(sb->buf + i, '\n', sb->len - i); + if (nl) + eol = nl - sb->buf; + else + eol = sb->len; + + if (strlen(sign_off_header) <= eol - i && + !prefixcmp(sb->buf + i, sign_off_header)) { + i = eol; + continue; + } + while (i < eol) + if (!isspace(sb->buf[i++])) + return 0; + } + + return 1; +} + +static void determine_author_info(struct strbuf *sb) +{ + char *name, *email, *date; + + name = getenv("GIT_AUTHOR_NAME"); + email = getenv("GIT_AUTHOR_EMAIL"); + date = getenv("GIT_AUTHOR_DATE"); + + if (use_message) { + const char *a, *lb, *rb, *eol; + + a = strstr(use_message_buffer, "\nauthor "); + if (!a) + die("invalid commit: %s\n", 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); + + name = xstrndup(a + 8, lb - (a + 8)); + email = xstrndup(lb + 2, rb - (lb + 2)); + date = xstrndup(rb + 2, eol - (rb + 2)); + } + + if (force_author) { + const char *lb = strstr(force_author, " <"); + const char *rb = strchr(force_author, '>'); + + if (!lb || !rb) + die("malformed --author parameter\n"); + 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)); +} + +static int parse_and_validate_options(int argc, const char *argv[]) +{ + int f = 0; + + argc = parse_options(argc, argv, builtin_commit_options, + builtin_commit_usage, 0); + + if (logfile || message || use_message) + no_edit = 1; + if (edit_flag) + no_edit = 0; + + if (get_sha1("HEAD", head_sha1)) + initial_commit = 1; + + if (!get_sha1("MERGE_HEAD", merge_head_sha1)) + in_merge = 1; + + /* Sanity check options */ + 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."); + + if (use_message) + f++; + if (edit_message) + f++; + if (logfile) + f++; + if (f > 1) + die("Only one of -c/-C/-F can be used."); + if (message && f > 0) + die("Option -m cannot be combined with -c/-C/-F."); + if (edit_message) + use_message = edit_message; + if (amend) + use_message = "HEAD"; + if (use_message) { + unsigned char sha1[20]; + static char utf8[] = "UTF-8"; + const char *out_enc; + char *enc, *end; + struct commit *commit; + + if (get_sha1(use_message, sha1)) + die("could not lookup commit %s", use_message); + commit = lookup_commit(sha1); + if (!commit || parse_commit(commit)) + die("could not parse commit %s", use_message); + + enc = strstr(commit->buffer, "\nencoding"); + if (enc) { + end = strchr(enc + 10, '\n'); + enc = xstrndup(enc + 10, end - (enc + 10)); + } else { + enc = utf8; + } + out_enc = git_commit_encoding ? git_commit_encoding : utf8; + + if (strcmp(out_enc, enc)) + use_message_buffer = + reencode_string(commit->buffer, out_enc, enc); + + /* + * If we failed to reencode the buffer, just copy it + * byte for byte so the user can try to fix it up. + * This also handles the case where input and output + * encodings are identical. + */ + if (use_message_buffer == NULL) + use_message_buffer = xstrdup(commit->buffer); + if (enc != utf8) + free(enc); + } + + if (!!also + !!only + !!all + !!interactive > 1) + die("Only one of --include/--only/--all/--interactive can be used."); + if (argc == 0 && (also || (only && !amend))) + die("No paths with --include/--only does not make sense."); + if (argc == 0 && only && amend) + only_include_assumed = "Clever... amending the last one with dirty index."; + if (argc > 0 && !also && !only) { + only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; + also = 0; + } + + if (all && argc > 0) + die("Paths with -a does not make sense."); + else if (interactive && argc > 0) + die("Paths with --interactive does not make sense."); + + return argc; +} + +int cmd_status(int argc, const char **argv, const char *prefix) +{ + const char *index_file; + int commitable; + + git_config(git_status_config); + + argc = parse_and_validate_options(argc, argv); + + index_file = prepare_index(argv, prefix); + + commitable = run_status(stdout, index_file); + + rollback_lock_file(&lock_file); + + return commitable ? 0 : 1; +} + +static int run_hook(const char *index_file, const char *name, const char *arg) +{ + struct child_process hook; + const char *argv[3], *env[2]; + char index[PATH_MAX]; + + argv[0] = git_path("hooks/%s", name); + argv[1] = arg; + argv[2] = NULL; + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); + env[0] = index; + env[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + hook.env = env; + + return run_command(&hook); +} + +static void print_summary(const char *prefix, const unsigned char *sha1) +{ + struct rev_info rev; + struct commit *commit; + + commit = lookup_commit(sha1); + if (!commit) + die("couldn't look up newly created commit\n"); + if (!commit || parse_commit(commit)) + die("could not parse newly created commit"); + + init_revisions(&rev, prefix); + setup_revisions(0, NULL, &rev, NULL); + + rev.abbrev = 0; + rev.diff = 1; + rev.diffopt.output_format = + DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; + + rev.verbose_header = 1; + rev.show_root_diff = 1; + rev.commit_format = get_commit_format("format:%h: %s"); + rev.always_show_header = 1; + + printf("Created %scommit ", initial_commit ? "initial " : ""); + + log_tree_commit(&rev, commit); +} + +int git_commit_config(const char *k, const char *v) +{ + if (!strcmp(k, "commit.template")) { + template_file = xstrdup(v); + return 0; + } + + return git_status_config(k, v); +} + +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"; + +int cmd_commit(int argc, const char **argv, const char *prefix) +{ + int header_len, parent_count = 0; + struct strbuf sb; + const char *index_file, *reflog_msg; + char *nl, *header_line; + unsigned char commit_sha1[20]; + struct ref_lock *ref_lock; + + git_config(git_commit_config); + + argc = parse_and_validate_options(argc, argv); + + index_file = prepare_index(argv, prefix); + + if (!no_verify && run_hook(index_file, "pre-commit", NULL)) + exit(1); + + if (!prepare_log_message(index_file) && !in_merge) { + run_status(stdout, index_file); + unlink(commit_editmsg); + return 1; + } + + strbuf_init(&sb, 0); + + /* Start building up the commit header */ + read_cache_from(index_file); + active_cache_tree = cache_tree(); + if (cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("Error building trees"); + strbuf_addf(&sb, "tree %s\n", + sha1_to_hex(active_cache_tree->sha1)); + + /* Determine parents */ + if (initial_commit) { + reflog_msg = "commit (initial)"; + parent_count = 0; + } else if (amend) { + struct commit_list *c; + struct commit *commit; + + reflog_msg = "commit (amend)"; + commit = lookup_commit(head_sha1); + if (!commit || parse_commit(commit)) + 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)); + } else if (in_merge) { + struct strbuf m; + FILE *fp; + + reflog_msg = "commit (merge)"; + strbuf_addf(&sb, "parent %s\n", sha1_to_hex(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); + fclose(fp); + strbuf_release(&m); + } else { + reflog_msg = "commit"; + strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1)); + } + + determine_author_info(&sb); + strbuf_addf(&sb, "committer %s\n", git_committer_info(1)); + 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) { + fprintf(stderr, "launching editor, log %s\n", logfile); + launch_editor(git_path(commit_editmsg), &sb); + } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) + die("could not read commit message\n"); + if (run_hook(index_file, "commit-msg", commit_editmsg)) + exit(1); + stripspace(&sb, 1); + if (sb.len < header_len || + message_is_empty(&sb, header_len)) + die("* no commit message? aborting commit."); + strbuf_addch(&sb, '\0'); + if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf)) + fprintf(stderr, commit_utf8_warn); + + if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) + die("failed to write commit object"); + + ref_lock = lock_any_ref_for_update("HEAD", + initial_commit ? NULL : head_sha1, + 0); + + nl = strchr(sb.buf + header_len, '\n'); + header_line = xstrndup(sb.buf + header_len, + nl - (sb.buf + header_len)); + strbuf_release(&sb); + strbuf_addf(&sb, "%s: %s\n", reflog_msg, header_line); + strbuf_addch(&sb, '\0'); + free(header_line); + + if (!ref_lock) + die("cannot lock HEAD ref"); + if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) + die("cannot update HEAD ref"); + + unlink(git_path("MERGE_HEAD")); + unlink(git_path("MERGE_MSG")); + + if (lock_file.filename[0] && commit_locked_index(&lock_file)) + die("failed to write new index"); + + rerere(); + + run_hook(index_file, "post-commit", NULL); + + if (!quiet) + print_summary(prefix, commit_sha1); + + return 0; +} diff --git a/builtin.h b/builtin.h index bcb54aa..caea1a9 100644 --- a/builtin.h +++ b/builtin.h @@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); +extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_describe(int argc, const char **argv, const char *prefix); @@ -69,10 +70,10 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); -extern int cmd_runstatus(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_status(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tag(int argc, const char **argv, const char *prefix); diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh new file mode 100755 index 0000000..4853397 --- /dev/null +++ b/contrib/examples/git-commit.sh @@ -0,0 +1,629 @@ +#!/bin/sh +# +# Copyright (c) 2005 Linus Torvalds +# Copyright (c) 2006 Junio C Hamano + +USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m | -F | (-C|-c) | --amend] [-u] [-e] [--author ] [--template ] [[-i | -o] ...]' +SUBDIRECTORY_OK=Yes +OPTIONS_SPEC= +. git-sh-setup +require_work_tree + +git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t + +case "$0" in +*status) + status_only=t + ;; +*commit) + status_only= + ;; +esac + +refuse_partial () { + echo >&2 "$1" + echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?" + exit 1 +} + +TMP_INDEX= +THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}" +NEXT_INDEX="$GIT_DIR/next-index$$" +rm -f "$NEXT_INDEX" +save_index () { + cp -p "$THIS_INDEX" "$NEXT_INDEX" +} + +run_status () { + # If TMP_INDEX is defined, that means we are doing + # "--only" partial commit, and that index file is used + # to build the tree for the commit. Otherwise, if + # NEXT_INDEX exists, that is the index file used to + # make the commit. Otherwise we are using as-is commit + # so the regular index file is what we use to compare. + if test '' != "$TMP_INDEX" + then + GIT_INDEX_FILE="$TMP_INDEX" + export GIT_INDEX_FILE + elif test -f "$NEXT_INDEX" + then + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE + fi + + if test "$status_only" = "t" -o "$use_status_color" = "t"; then + color= + else + color=--nocolor + fi + git runstatus ${color} \ + ${verbose:+--verbose} \ + ${amend:+--amend} \ + ${untracked_files:+--untracked} +} + +trap ' + test -z "$TMP_INDEX" || { + test -f "$TMP_INDEX" && rm -f "$TMP_INDEX" + } + rm -f "$NEXT_INDEX" +' 0 + +################################################################ +# Command line argument parsing and sanity checking + +all= +also= +interactive= +only= +logfile= +use_commit= +amend= +edit_flag= +no_edit= +log_given= +log_message= +verify=t +quiet= +verbose= +signoff= +force_author= +only_include_assumed= +untracked_files= +templatefile="`git config commit.template`" +while test $# != 0 +do + case "$1" in + -F|--F|-f|--f|--fi|--fil|--file) + case "$#" in 1) usage ;; esac + shift + no_edit=t + log_given=t$log_given + logfile="$1" + ;; + -F*|-f*) + no_edit=t + log_given=t$log_given + logfile="${1#-[Ff]}" + ;; + --F=*|--f=*|--fi=*|--fil=*|--file=*) + no_edit=t + log_given=t$log_given + logfile="${1#*=}" + ;; + -a|--a|--al|--all) + all=t + ;; + --au=*|--aut=*|--auth=*|--autho=*|--author=*) + force_author="${1#*=}" + ;; + --au|--aut|--auth|--autho|--author) + case "$#" in 1) usage ;; esac + shift + force_author="$1" + ;; + -e|--e|--ed|--edi|--edit) + edit_flag=t + ;; + -i|--i|--in|--inc|--incl|--inclu|--includ|--include) + also=t + ;; + --int|--inte|--inter|--intera|--interac|--interact|--interacti|\ + --interactiv|--interactive) + interactive=t + ;; + -o|--o|--on|--onl|--only) + only=t + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + case "$#" in 1) usage ;; esac + shift + log_given=m$log_given + log_message="${log_message:+${log_message} + +}$1" + no_edit=t + ;; + -m*) + log_given=m$log_given + log_message="${log_message:+${log_message} + +}${1#-m}" + no_edit=t + ;; + --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + log_given=m$log_given + log_message="${log_message:+${log_message} + +}${1#*=}" + no_edit=t + ;; + -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ + --no-verify) + verify= + ;; + --a|--am|--ame|--amen|--amend) + amend=t + use_commit=HEAD + ;; + -c) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + ;; + --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ + --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ + --reedit-messag=*|--reedit-message=*) + log_given=t$log_given + use_commit="${1#*=}" + no_edit= + ;; + --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ + --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ + --reedit-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + ;; + -C) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + ;; + --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ + --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ + --reuse-message=*) + log_given=t$log_given + use_commit="${1#*=}" + no_edit=t + ;; + --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ + --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + ;; + -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) + signoff=t + ;; + -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template) + case "$#" in 1) usage ;; esac + shift + templatefile="$1" + no_edit= + ;; + -q|--q|--qu|--qui|--quie|--quiet) + quiet=t + ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t + ;; + -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ + --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ + --untracked-file|--untracked-files) + untracked_files=t + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift +done +case "$edit_flag" in t) no_edit= ;; esac + +################################################################ +# Sanity check options + +case "$amend,$initial_commit" in +t,t) + die "You do not have anything to amend." ;; +t,) + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + die "You are in the middle of a merge -- cannot amend." + fi ;; +esac + +case "$log_given" in +tt*) + die "Only one of -c/-C/-F can be used." ;; +*tm*|*mt*) + die "Option -m cannot be combined with -c/-C/-F." ;; +esac + +case "$#,$also,$only,$amend" in +*,t,t,*) + die "Only one of --include/--only can be used." ;; +0,t,,* | 0,,t,) + die "No paths with --include/--only does not make sense." ;; +0,,t,t) + only_include_assumed="# Clever... amending the last one with dirty index." ;; +0,,,*) + ;; +*,,,*) + only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." + also= + ;; +esac +unset only +case "$all,$interactive,$also,$#" in +*t,*t,*) + die "Cannot use -a, --interactive or -i at the same time." ;; +t,,,[1-9]*) + die "Paths with -a does not make sense." ;; +,t,,[1-9]*) + die "Paths with --interactive does not make sense." ;; +,,t,0) + die "No paths with -i does not make sense." ;; +esac + +if test ! -z "$templatefile" -a -z "$log_given" +then + if test ! -f "$templatefile" + then + die "Commit template file does not exist." + fi +fi + +################################################################ +# Prepare index to have a tree to be committed + +case "$all,$also" in +t,) + if test ! -f "$THIS_INDEX" + then + die 'nothing to commit (use "git add file1 file2" to include for commit)' + fi + save_index && + ( + cd_to_toplevel && + GIT_INDEX_FILE="$NEXT_INDEX" && + export GIT_INDEX_FILE && + git diff-files --name-only -z | + git update-index --remove -z --stdin + ) || exit + ;; +,t) + save_index && + git ls-files --error-unmatch -- "$@" >/dev/null || exit + + git diff-files --name-only -z -- "$@" | + ( + cd_to_toplevel && + GIT_INDEX_FILE="$NEXT_INDEX" && + export GIT_INDEX_FILE && + git update-index --remove -z --stdin + ) || exit + ;; +,) + if test "$interactive" = t; then + git add --interactive || exit + fi + case "$#" in + 0) + ;; # commit as-is + *) + if test -f "$GIT_DIR/MERGE_HEAD" + then + refuse_partial "Cannot do a partial commit during a merge." + fi + + TMP_INDEX="$GIT_DIR/tmp-index$$" + W= + test -z "$initial_commit" && W=--with-tree=HEAD + commit_only=`git ls-files --error-unmatch $W -- "$@"` || exit + + # Build a temporary index and update the real index + # the same way. + if test -z "$initial_commit" + then + GIT_INDEX_FILE="$THIS_INDEX" \ + git read-tree --index-output="$TMP_INDEX" -i -m HEAD + else + rm -f "$TMP_INDEX" + fi || exit + + printf '%s\n' "$commit_only" | + GIT_INDEX_FILE="$TMP_INDEX" \ + git update-index --add --remove --stdin && + + save_index && + printf '%s\n' "$commit_only" | + ( + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE + git update-index --add --remove --stdin + ) || exit + ;; + esac + ;; +esac + +################################################################ +# If we do as-is commit, the index file will be THIS_INDEX, +# otherwise NEXT_INDEX after we make this commit. We leave +# the index as is if we abort. + +if test -f "$NEXT_INDEX" +then + USE_INDEX="$NEXT_INDEX" +else + USE_INDEX="$THIS_INDEX" +fi + +case "$status_only" in +t) + # This will silently fail in a read-only repository, which is + # what we want. + GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh + run_status + exit $? + ;; +'') + GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit + ;; +esac + +################################################################ +# Grab commit message, write out tree and make commit. + +if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit +then + GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \ + || exit +fi + +if test "$log_message" != '' +then + printf '%s\n' "$log_message" +elif test "$logfile" != "" +then + if test "$logfile" = - + then + test -t 0 && + echo >&2 "(reading log message from standard input)" + cat + else + cat <"$logfile" + fi +elif test "$use_commit" != "" +then + encoding=$(git config i18n.commitencoding || echo UTF-8) + git show -s --pretty=raw --encoding="$encoding" "$use_commit" | + sed -e '1,/^$/d' -e 's/^ //' +elif test -f "$GIT_DIR/MERGE_MSG" +then + cat "$GIT_DIR/MERGE_MSG" +elif test -f "$GIT_DIR/SQUASH_MSG" +then + cat "$GIT_DIR/SQUASH_MSG" +elif test "$templatefile" != "" +then + cat "$templatefile" +fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG + +case "$signoff" in +t) + sign=$(git-var GIT_COMMITTER_IDENT | sed -e ' + s/>.*/>/ + s/^/Signed-off-by: / + ') + blank_before_signoff= + tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG | + grep 'Signed-off-by:' >/dev/null || blank_before_signoff=' +' + tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG | + grep "$sign"$ >/dev/null || + printf '%s%s\n' "$blank_before_signoff" "$sign" \ + >>"$GIT_DIR"/COMMIT_EDITMSG + ;; +esac + +if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then + echo "#" + echo "# It looks like you may be committing a MERGE." + echo "# If this is not correct, please remove the file" + printf '%s\n' "# $GIT_DIR/MERGE_HEAD" + echo "# and try again" + echo "#" +fi >>"$GIT_DIR"/COMMIT_EDITMSG + +# Author +if test '' != "$use_commit" +then + eval "$(get_author_ident_from_commit "$use_commit")" + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE +fi +if test '' != "$force_author" +then + GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` && + GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` && + test '' != "$GIT_AUTHOR_NAME" && + test '' != "$GIT_AUTHOR_EMAIL" || + die "malformed --author parameter" + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL +fi + +PARENTS="-p HEAD" +if test -z "$initial_commit" +then + rloga='commit' + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + rloga='commit (merge)' + PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` + elif test -n "$amend"; then + rloga='commit (amend)' + PARENTS=$(git cat-file commit HEAD | + sed -n -e '/^$/q' -e 's/^parent /-p /p') + fi + current="$(git rev-parse --verify HEAD)" +else + if [ -z "$(git ls-files)" ]; then + echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)' + exit 1 + fi + PARENTS="" + rloga='commit (initial)' + current='' +fi +set_reflog_action "$rloga" + +if test -z "$no_edit" +then + { + echo "" + echo "# Please enter the commit message for your changes." + echo "# (Comment lines starting with '#' will not be included)" + test -z "$only_include_assumed" || echo "$only_include_assumed" + run_status + } >>"$GIT_DIR"/COMMIT_EDITMSG +else + # we need to check if there is anything to commit + run_status >/dev/null +fi +if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ] +then + rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG" + use_status_color=t + run_status + exit 1 +fi + +case "$no_edit" in +'') + git-var GIT_AUTHOR_IDENT > /dev/null || die + git-var GIT_COMMITTER_IDENT > /dev/null || die + git_editor "$GIT_DIR/COMMIT_EDITMSG" + ;; +esac + +case "$verify" in +t) + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit + fi +esac + +if test -z "$no_edit" +then + sed -e ' + /^diff --git a\/.*/{ + s/// + q + } + /^#/d + ' "$GIT_DIR"/COMMIT_EDITMSG +else + cat "$GIT_DIR"/COMMIT_EDITMSG +fi | +git stripspace >"$GIT_DIR"/COMMIT_MSG + +# Test whether the commit message has any content we didn't supply. +have_commitmsg= +grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG | + git stripspace > "$GIT_DIR"/COMMIT_BAREMSG + +# Is the commit message totally empty? +if test -s "$GIT_DIR"/COMMIT_BAREMSG +then + if test "$templatefile" != "" + then + # Test whether this is just the unaltered template. + if cnt=`sed -e '/^#/d' < "$templatefile" | + git stripspace | + diff "$GIT_DIR"/COMMIT_BAREMSG - | + wc -l` && + test 0 -lt $cnt + then + have_commitmsg=t + fi + else + # No template, so the content in the commit message must + # have come from the user. + have_commitmsg=t + fi +fi + +rm -f "$GIT_DIR"/COMMIT_BAREMSG + +if test "$have_commitmsg" = "t" +then + if test -z "$TMP_INDEX" + then + tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree) + else + tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) && + rm -f "$TMP_INDEX" + fi && + commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") && + rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && + git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" && + rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && + if test -f "$NEXT_INDEX" + then + mv "$NEXT_INDEX" "$THIS_INDEX" + else + : ;# happy + fi +else + echo >&2 "* no commit message? aborting commit." + false +fi +ret="$?" +rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG" + +cd_to_toplevel + +git rerere + +if test "$ret" = 0 +then + git gc --auto + if test -x "$GIT_DIR"/hooks/post-commit + then + "$GIT_DIR"/hooks/post-commit + fi + if test -z "$quiet" + then + commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\ + --summary --root HEAD --` + echo "Created${initial_commit:+ initial} commit $commit" + fi +fi + +exit "$ret" diff --git a/git-commit.sh b/git-commit.sh deleted file mode 100755 index 4853397..0000000 --- a/git-commit.sh +++ /dev/null @@ -1,629 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Linus Torvalds -# Copyright (c) 2006 Junio C Hamano - -USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m | -F | (-C|-c) | --amend] [-u] [-e] [--author ] [--template ] [[-i | -o] ...]' -SUBDIRECTORY_OK=Yes -OPTIONS_SPEC= -. git-sh-setup -require_work_tree - -git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t - -case "$0" in -*status) - status_only=t - ;; -*commit) - status_only= - ;; -esac - -refuse_partial () { - echo >&2 "$1" - echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?" - exit 1 -} - -TMP_INDEX= -THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}" -NEXT_INDEX="$GIT_DIR/next-index$$" -rm -f "$NEXT_INDEX" -save_index () { - cp -p "$THIS_INDEX" "$NEXT_INDEX" -} - -run_status () { - # If TMP_INDEX is defined, that means we are doing - # "--only" partial commit, and that index file is used - # to build the tree for the commit. Otherwise, if - # NEXT_INDEX exists, that is the index file used to - # make the commit. Otherwise we are using as-is commit - # so the regular index file is what we use to compare. - if test '' != "$TMP_INDEX" - then - GIT_INDEX_FILE="$TMP_INDEX" - export GIT_INDEX_FILE - elif test -f "$NEXT_INDEX" - then - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE - fi - - if test "$status_only" = "t" -o "$use_status_color" = "t"; then - color= - else - color=--nocolor - fi - git runstatus ${color} \ - ${verbose:+--verbose} \ - ${amend:+--amend} \ - ${untracked_files:+--untracked} -} - -trap ' - test -z "$TMP_INDEX" || { - test -f "$TMP_INDEX" && rm -f "$TMP_INDEX" - } - rm -f "$NEXT_INDEX" -' 0 - -################################################################ -# Command line argument parsing and sanity checking - -all= -also= -interactive= -only= -logfile= -use_commit= -amend= -edit_flag= -no_edit= -log_given= -log_message= -verify=t -quiet= -verbose= -signoff= -force_author= -only_include_assumed= -untracked_files= -templatefile="`git config commit.template`" -while test $# != 0 -do - case "$1" in - -F|--F|-f|--f|--fi|--fil|--file) - case "$#" in 1) usage ;; esac - shift - no_edit=t - log_given=t$log_given - logfile="$1" - ;; - -F*|-f*) - no_edit=t - log_given=t$log_given - logfile="${1#-[Ff]}" - ;; - --F=*|--f=*|--fi=*|--fil=*|--file=*) - no_edit=t - log_given=t$log_given - logfile="${1#*=}" - ;; - -a|--a|--al|--all) - all=t - ;; - --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author="${1#*=}" - ;; - --au|--aut|--auth|--autho|--author) - case "$#" in 1) usage ;; esac - shift - force_author="$1" - ;; - -e|--e|--ed|--edi|--edit) - edit_flag=t - ;; - -i|--i|--in|--inc|--incl|--inclu|--includ|--include) - also=t - ;; - --int|--inte|--inter|--intera|--interac|--interact|--interacti|\ - --interactiv|--interactive) - interactive=t - ;; - -o|--o|--on|--onl|--only) - only=t - ;; - -m|--m|--me|--mes|--mess|--messa|--messag|--message) - case "$#" in 1) usage ;; esac - shift - log_given=m$log_given - log_message="${log_message:+${log_message} - -}$1" - no_edit=t - ;; - -m*) - log_given=m$log_given - log_message="${log_message:+${log_message} - -}${1#-m}" - no_edit=t - ;; - --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) - log_given=m$log_given - log_message="${log_message:+${log_message} - -}${1#*=}" - no_edit=t - ;; - -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ - --no-verify) - verify= - ;; - --a|--am|--ame|--amen|--amend) - amend=t - use_commit=HEAD - ;; - -c) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - ;; - --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ - --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ - --reedit-messag=*|--reedit-message=*) - log_given=t$log_given - use_commit="${1#*=}" - no_edit= - ;; - --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ - --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ - --reedit-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - ;; - -C) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - ;; - --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ - --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ - --reuse-message=*) - log_given=t$log_given - use_commit="${1#*=}" - no_edit=t - ;; - --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ - --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - signoff=t - ;; - -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template) - case "$#" in 1) usage ;; esac - shift - templatefile="$1" - no_edit= - ;; - -q|--q|--qu|--qui|--quie|--quiet) - quiet=t - ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t - ;; - -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ - --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ - --untracked-file|--untracked-files) - untracked_files=t - ;; - --) - shift - break - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done -case "$edit_flag" in t) no_edit= ;; esac - -################################################################ -# Sanity check options - -case "$amend,$initial_commit" in -t,t) - die "You do not have anything to amend." ;; -t,) - if [ -f "$GIT_DIR/MERGE_HEAD" ]; then - die "You are in the middle of a merge -- cannot amend." - fi ;; -esac - -case "$log_given" in -tt*) - die "Only one of -c/-C/-F can be used." ;; -*tm*|*mt*) - die "Option -m cannot be combined with -c/-C/-F." ;; -esac - -case "$#,$also,$only,$amend" in -*,t,t,*) - die "Only one of --include/--only can be used." ;; -0,t,,* | 0,,t,) - die "No paths with --include/--only does not make sense." ;; -0,,t,t) - only_include_assumed="# Clever... amending the last one with dirty index." ;; -0,,,*) - ;; -*,,,*) - only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." - also= - ;; -esac -unset only -case "$all,$interactive,$also,$#" in -*t,*t,*) - die "Cannot use -a, --interactive or -i at the same time." ;; -t,,,[1-9]*) - die "Paths with -a does not make sense." ;; -,t,,[1-9]*) - die "Paths with --interactive does not make sense." ;; -,,t,0) - die "No paths with -i does not make sense." ;; -esac - -if test ! -z "$templatefile" -a -z "$log_given" -then - if test ! -f "$templatefile" - then - die "Commit template file does not exist." - fi -fi - -################################################################ -# Prepare index to have a tree to be committed - -case "$all,$also" in -t,) - if test ! -f "$THIS_INDEX" - then - die 'nothing to commit (use "git add file1 file2" to include for commit)' - fi - save_index && - ( - cd_to_toplevel && - GIT_INDEX_FILE="$NEXT_INDEX" && - export GIT_INDEX_FILE && - git diff-files --name-only -z | - git update-index --remove -z --stdin - ) || exit - ;; -,t) - save_index && - git ls-files --error-unmatch -- "$@" >/dev/null || exit - - git diff-files --name-only -z -- "$@" | - ( - cd_to_toplevel && - GIT_INDEX_FILE="$NEXT_INDEX" && - export GIT_INDEX_FILE && - git update-index --remove -z --stdin - ) || exit - ;; -,) - if test "$interactive" = t; then - git add --interactive || exit - fi - case "$#" in - 0) - ;; # commit as-is - *) - if test -f "$GIT_DIR/MERGE_HEAD" - then - refuse_partial "Cannot do a partial commit during a merge." - fi - - TMP_INDEX="$GIT_DIR/tmp-index$$" - W= - test -z "$initial_commit" && W=--with-tree=HEAD - commit_only=`git ls-files --error-unmatch $W -- "$@"` || exit - - # Build a temporary index and update the real index - # the same way. - if test -z "$initial_commit" - then - GIT_INDEX_FILE="$THIS_INDEX" \ - git read-tree --index-output="$TMP_INDEX" -i -m HEAD - else - rm -f "$TMP_INDEX" - fi || exit - - printf '%s\n' "$commit_only" | - GIT_INDEX_FILE="$TMP_INDEX" \ - git update-index --add --remove --stdin && - - save_index && - printf '%s\n' "$commit_only" | - ( - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE - git update-index --add --remove --stdin - ) || exit - ;; - esac - ;; -esac - -################################################################ -# If we do as-is commit, the index file will be THIS_INDEX, -# otherwise NEXT_INDEX after we make this commit. We leave -# the index as is if we abort. - -if test -f "$NEXT_INDEX" -then - USE_INDEX="$NEXT_INDEX" -else - USE_INDEX="$THIS_INDEX" -fi - -case "$status_only" in -t) - # This will silently fail in a read-only repository, which is - # what we want. - GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh - run_status - exit $? - ;; -'') - GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit - ;; -esac - -################################################################ -# Grab commit message, write out tree and make commit. - -if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit -then - GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \ - || exit -fi - -if test "$log_message" != '' -then - printf '%s\n' "$log_message" -elif test "$logfile" != "" -then - if test "$logfile" = - - then - test -t 0 && - echo >&2 "(reading log message from standard input)" - cat - else - cat <"$logfile" - fi -elif test "$use_commit" != "" -then - encoding=$(git config i18n.commitencoding || echo UTF-8) - git show -s --pretty=raw --encoding="$encoding" "$use_commit" | - sed -e '1,/^$/d' -e 's/^ //' -elif test -f "$GIT_DIR/MERGE_MSG" -then - cat "$GIT_DIR/MERGE_MSG" -elif test -f "$GIT_DIR/SQUASH_MSG" -then - cat "$GIT_DIR/SQUASH_MSG" -elif test "$templatefile" != "" -then - cat "$templatefile" -fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG - -case "$signoff" in -t) - sign=$(git-var GIT_COMMITTER_IDENT | sed -e ' - s/>.*/>/ - s/^/Signed-off-by: / - ') - blank_before_signoff= - tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG | - grep 'Signed-off-by:' >/dev/null || blank_before_signoff=' -' - tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG | - grep "$sign"$ >/dev/null || - printf '%s%s\n' "$blank_before_signoff" "$sign" \ - >>"$GIT_DIR"/COMMIT_EDITMSG - ;; -esac - -if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then - echo "#" - echo "# It looks like you may be committing a MERGE." - echo "# If this is not correct, please remove the file" - printf '%s\n' "# $GIT_DIR/MERGE_HEAD" - echo "# and try again" - echo "#" -fi >>"$GIT_DIR"/COMMIT_EDITMSG - -# Author -if test '' != "$use_commit" -then - eval "$(get_author_ident_from_commit "$use_commit")" - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE -fi -if test '' != "$force_author" -then - GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` && - GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` && - test '' != "$GIT_AUTHOR_NAME" && - test '' != "$GIT_AUTHOR_EMAIL" || - die "malformed --author parameter" - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL -fi - -PARENTS="-p HEAD" -if test -z "$initial_commit" -then - rloga='commit' - if [ -f "$GIT_DIR/MERGE_HEAD" ]; then - rloga='commit (merge)' - PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` - elif test -n "$amend"; then - rloga='commit (amend)' - PARENTS=$(git cat-file commit HEAD | - sed -n -e '/^$/q' -e 's/^parent /-p /p') - fi - current="$(git rev-parse --verify HEAD)" -else - if [ -z "$(git ls-files)" ]; then - echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)' - exit 1 - fi - PARENTS="" - rloga='commit (initial)' - current='' -fi -set_reflog_action "$rloga" - -if test -z "$no_edit" -then - { - echo "" - echo "# Please enter the commit message for your changes." - echo "# (Comment lines starting with '#' will not be included)" - test -z "$only_include_assumed" || echo "$only_include_assumed" - run_status - } >>"$GIT_DIR"/COMMIT_EDITMSG -else - # we need to check if there is anything to commit - run_status >/dev/null -fi -if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ] -then - rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG" - use_status_color=t - run_status - exit 1 -fi - -case "$no_edit" in -'') - git-var GIT_AUTHOR_IDENT > /dev/null || die - git-var GIT_COMMITTER_IDENT > /dev/null || die - git_editor "$GIT_DIR/COMMIT_EDITMSG" - ;; -esac - -case "$verify" in -t) - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit - fi -esac - -if test -z "$no_edit" -then - sed -e ' - /^diff --git a\/.*/{ - s/// - q - } - /^#/d - ' "$GIT_DIR"/COMMIT_EDITMSG -else - cat "$GIT_DIR"/COMMIT_EDITMSG -fi | -git stripspace >"$GIT_DIR"/COMMIT_MSG - -# Test whether the commit message has any content we didn't supply. -have_commitmsg= -grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG | - git stripspace > "$GIT_DIR"/COMMIT_BAREMSG - -# Is the commit message totally empty? -if test -s "$GIT_DIR"/COMMIT_BAREMSG -then - if test "$templatefile" != "" - then - # Test whether this is just the unaltered template. - if cnt=`sed -e '/^#/d' < "$templatefile" | - git stripspace | - diff "$GIT_DIR"/COMMIT_BAREMSG - | - wc -l` && - test 0 -lt $cnt - then - have_commitmsg=t - fi - else - # No template, so the content in the commit message must - # have come from the user. - have_commitmsg=t - fi -fi - -rm -f "$GIT_DIR"/COMMIT_BAREMSG - -if test "$have_commitmsg" = "t" -then - if test -z "$TMP_INDEX" - then - tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree) - else - tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) && - rm -f "$TMP_INDEX" - fi && - commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") && - rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" && - rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && - if test -f "$NEXT_INDEX" - then - mv "$NEXT_INDEX" "$THIS_INDEX" - else - : ;# happy - fi -else - echo >&2 "* no commit message? aborting commit." - false -fi -ret="$?" -rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG" - -cd_to_toplevel - -git rerere - -if test "$ret" = 0 -then - git gc --auto - if test -x "$GIT_DIR"/hooks/post-commit - then - "$GIT_DIR"/hooks/post-commit - fi - if test -z "$quiet" - then - commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\ - --summary --root HEAD --` - echo "Created${initial_commit:+ initial} commit $commit" - fi -fi - -exit "$ret" diff --git a/git.c b/git.c index 80c2f14..a5adc34 100644 --- a/git.c +++ b/git.c @@ -293,6 +293,7 @@ static void handle_internal_command(int argc, const char **argv) { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, + { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP }, @@ -342,10 +343,10 @@ static void handle_internal_command(int argc, const char **argv) { "rev-parse", cmd_rev_parse }, { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, - { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE }, { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From e97c9ad96b60d51b0b3852aaaff146015d01cb62 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 8 Nov 2007 14:06:52 +0000 Subject: launch_editor(): read the file, even when EDITOR=: Earlier we just returned in case EDITOR=: but the message stored in the file was not read back. Fix this, at the same time simplifying the code as suggested by Johannes Sixt. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-tag.c b/builtin-tag.c index 88a5449..566b9d1 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -20,8 +20,6 @@ static char signingkey[1000]; void launch_editor(const char *path, struct strbuf *buffer) { const char *editor, *terminal; - struct child_process child; - const char *args[3]; editor = getenv("GIT_EDITOR"); if (!editor && editor_program) @@ -42,17 +40,12 @@ void launch_editor(const char *path, struct strbuf *buffer) if (!editor) editor = "vi"; - if (!strcmp(editor, ":")) - return; - - memset(&child, 0, sizeof(child)); - child.argv = args; - args[0] = editor; - args[1] = path; - args[2] = NULL; + if (strcmp(editor, ":")) { + const char *args[] = { editor, path, NULL }; - if (run_command(&child)) - die("There was a problem with the editor %s.", editor); + if (run_command_v_opt(args, 0)) + die("There was a problem with the editor %s.", editor); + } if (strbuf_read_file(buffer, path, 0) < 0) die("could not read message file '%s': %s", -- cgit v0.10.2-6-g49f6 From 741707b1e2261d522a92948ff7d7b897ff00e587 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 8 Nov 2007 12:15:26 +0000 Subject: builtin-commit: fix reflog message generation Instead of strdup()ing, we can just reuse the buffer in which the commit message is stored, and which is supposed to hold the reflog message anyway. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 669cc6b..c8f79a8 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -488,7 +488,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int header_len, parent_count = 0; struct strbuf sb; const char *index_file, *reflog_msg; - char *nl, *header_line; + char *nl; unsigned char commit_sha1[20]; struct ref_lock *ref_lock; @@ -585,12 +585,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 0); nl = strchr(sb.buf + header_len, '\n'); - header_line = xstrndup(sb.buf + header_len, - nl - (sb.buf + header_len)); - strbuf_release(&sb); - strbuf_addf(&sb, "%s: %s\n", reflog_msg, header_line); - strbuf_addch(&sb, '\0'); - free(header_line); + if (nl) + strbuf_setlen(&sb, nl + 1 - sb.buf); + else + strbuf_addch(&sb, '\n'); + strbuf_remove(&sb, 0, header_len); + strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); + strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); if (!ref_lock) die("cannot lock HEAD ref"); -- cgit v0.10.2-6-g49f6 From d37d320386369375b4e5b95b98517503125376f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Fri, 9 Nov 2007 11:40:27 -0500 Subject: builtin-commit: Refresh cache after adding files. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have promised our users that after running git-status or git-commit the index will be refreshed for a long time since these commands were introduced. Do refresh the index before writing it out to keep the promise. Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index c8f79a8..a84a729 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -81,6 +81,7 @@ static char *prepare_index(const char **files, const char *prefix) if (all || also) { add_files_to_cache(verbose, also ? prefix : NULL, files); + refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close(fd)) die("unable to write new_index file"); return lock_file.filename; @@ -110,6 +111,7 @@ static char *prepare_index(const char **files, const char *prefix) fd = hold_lock_file_for_update(next_index_lock, git_path("next-index-%d", getpid()), 1); add_files_to_cache(verbose, prefix, files); + refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close(fd)) die("unable to write new_index file"); -- cgit v0.10.2-6-g49f6 From 367c98866c340bc9cf5cfa88c3b69f027165fc44 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 17:35:41 +0000 Subject: git status: show relative paths when run in a subdirectory To show the relative paths, the function formerly called quote_crlf() (now called quote_path()) takes the prefix as an additional argument. While at it, the static buffers were replaced by strbufs. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index a84a729..400ee93 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -118,11 +118,12 @@ static char *prepare_index(const char **files, const char *prefix) return next_index_lock->filename; } -static int run_status(FILE *fp, const char *index_file) +static int run_status(FILE *fp, const char *index_file, const char *prefix) { struct wt_status s; wt_status_prepare(&s); + s.prefix = prefix; if (amend) { s.amend = 1; @@ -140,7 +141,7 @@ static int run_status(FILE *fp, const char *index_file) static const char sign_off_header[] = "Signed-off-by: "; -static int prepare_log_message(const char *index_file) +static int prepare_log_message(const char *index_file, const char *prefix) { struct stat statbuf; int commitable; @@ -216,7 +217,7 @@ static int prepare_log_message(const char *index_file) if (only_include_assumed) fprintf(fp, "# %s\n", only_include_assumed); - commitable = run_status(fp, index_file); + commitable = run_status(fp, index_file, prefix); fclose(fp); @@ -409,7 +410,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) index_file = prepare_index(argv, prefix); - commitable = run_status(stdout, index_file); + commitable = run_status(stdout, index_file, prefix); rollback_lock_file(&lock_file); @@ -503,8 +504,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!no_verify && run_hook(index_file, "pre-commit", NULL)) exit(1); - if (!prepare_log_message(index_file) && !in_merge) { - run_status(stdout, index_file); + if (!prepare_log_message(index_file, prefix) && !in_merge) { + run_status(stdout, index_file, prefix); unlink(commit_editmsg); return 1; } diff --git a/builtin-runstatus.c b/builtin-runstatus.c index 2db25c8..8d167a9 100644 --- a/builtin-runstatus.c +++ b/builtin-runstatus.c @@ -14,6 +14,7 @@ int cmd_runstatus(int argc, const char **argv, const char *prefix) git_config(git_status_config); wt_status_prepare(&s); + s.prefix = prefix; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--color")) diff --git a/t/t7502-status.sh b/t/t7502-status.sh new file mode 100755 index 0000000..269b334 --- /dev/null +++ b/t/t7502-status.sh @@ -0,0 +1,91 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='git-status' + +. ./test-lib.sh + +test_expect_success 'setup' ' + : > tracked && + : > modified && + mkdir dir1 && + : > dir1/tracked && + : > dir1/modified && + mkdir dir2 && + : > dir1/tracked && + : > dir1/modified && + git add . && + test_tick && + git commit -m initial && + : > untracked && + : > dir1/untracked && + : > dir2/untracked && + echo 1 > dir1/modified && + echo 2 > dir2/modified && + echo 3 > dir2/added && + git add dir2/added +' + +cat > expect << \EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# new file: dir2/added +# +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +EOF + +test_expect_success 'status' ' + + git status > output && + git diff expect output + +' + +cat > expect << \EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# new file: ../dir2/added +# +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# +# modified: ../dir1/modified +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# untracked +# ../dir2/modified +# ../dir2/untracked +# ../expect +# ../output +# ../untracked +EOF + +test_expect_success 'status with relative paths' ' + + (cd dir1 && git status) > output && + git diff expect output + +' + +test_done diff --git a/wt-status.c b/wt-status.c index 9a6ef4a..d3c10b8 100644 --- a/wt-status.c +++ b/wt-status.c @@ -81,33 +81,46 @@ static void wt_status_print_trailer(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } -static const char *quote_crlf(const char *in, char *buf, size_t sz) +static char *quote_path(const char *in, int len, + struct strbuf *out, const char *prefix) { - const char *scan; - char *out; - const char *ret = in; + if (len > 0) + strbuf_grow(out, len); + strbuf_setlen(out, 0); + + if (prefix) { + int off = 0; + while (prefix[off] && off < len && prefix[off] == in[off]) + if (prefix[off] == '/') { + prefix += off + 1; + in += off + 1; + len -= off + 1; + off = 0; + } else + off++; + + for (; *prefix; prefix++) + if (*prefix == '/') + strbuf_addstr(out, "../"); + } - for (scan = in, out = buf; *scan; scan++) { - int ch = *scan; - int quoted; + for (; (len < 0 && *in) || len > 0; in++, len--) { + int ch = *in; switch (ch) { case '\n': - quoted = 'n'; + strbuf_addstr(out, "\\n"); break; case '\r': - quoted = 'r'; + strbuf_addstr(out, "\\r"); break; default: - *out++ = ch; + strbuf_addch(out, ch); continue; } - *out++ = '\\'; - *out++ = quoted; - ret = buf; } - *out = '\0'; - return ret; + + return out->buf; } static void wt_status_print_filepair(struct wt_status *s, @@ -115,10 +128,12 @@ static void wt_status_print_filepair(struct wt_status *s, { const char *c = color(t); const char *one, *two; - char onebuf[PATH_MAX], twobuf[PATH_MAX]; + struct strbuf onebuf, twobuf; - one = quote_crlf(p->one->path, onebuf, sizeof(onebuf)); - two = quote_crlf(p->two->path, twobuf, sizeof(twobuf)); + strbuf_init(&onebuf, 0); + strbuf_init(&twobuf, 0); + one = quote_path(p->one->path, -1, &onebuf, s->prefix); + two = quote_path(p->two->path, -1, &twobuf, s->prefix); color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); switch (p->status) { @@ -150,6 +165,8 @@ static void wt_status_print_filepair(struct wt_status *s, die("bug: unhandled diff status %c", p->status); } fprintf(s->fp, "\n"); + strbuf_release(&onebuf); + strbuf_release(&twobuf); } static void wt_status_print_updated_cb(struct diff_queue_struct *q, @@ -204,8 +221,9 @@ static void wt_read_cache(struct wt_status *s) static void wt_status_print_initial(struct wt_status *s) { int i; - char buf[PATH_MAX]; + struct strbuf buf; + strbuf_init(&buf, 0); wt_read_cache(s); if (active_nr) { s->commitable = 1; @@ -214,11 +232,12 @@ static void wt_status_print_initial(struct wt_status *s) for (i = 0; i < active_nr; i++) { color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s", - quote_crlf(active_cache[i]->name, - buf, sizeof(buf))); + quote_path(active_cache[i]->name, -1, + &buf, s->prefix)); } if (active_nr) wt_status_print_trailer(s); + strbuf_release(&buf); } static void wt_status_print_updated(struct wt_status *s) @@ -252,7 +271,9 @@ static void wt_status_print_untracked(struct wt_status *s) struct dir_struct dir; int i; int shown_header = 0; + struct strbuf buf; + strbuf_init(&buf, 0); memset(&dir, 0, sizeof(dir)); if (!s->untracked) { @@ -284,9 +305,11 @@ static void wt_status_print_untracked(struct wt_status *s) shown_header = 1; } color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s", - ent->len, ent->name); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s", + quote_path(ent->name, ent->len, + &buf, s->prefix)); } + strbuf_release(&buf); } static void wt_status_print_verbose(struct wt_status *s) diff --git a/wt-status.h b/wt-status.h index 7744932..f58ebcb 100644 --- a/wt-status.h +++ b/wt-status.h @@ -23,6 +23,7 @@ struct wt_status { int workdir_untracked; const char *index_file; FILE *fp; + const char *prefix; }; int git_status_config(const char *var, const char *value); -- cgit v0.10.2-6-g49f6 From 13208572fbe8838fd8835548d7502202d1f7b21d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 17:35:58 +0000 Subject: builtin-commit: fix --signoff The Signed-off-by: line contained a spurious timestamp. The reason was a call to git_committer_info(1), which automatically added the timestamp. Instead, fmt_ident() was taught to interpret an empty string for the date (as opposed to NULL, which still triggers the default behavior) as "do not bother with the timestamp", and builtin-commit.c uses it. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 400ee93..780eec7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -183,21 +183,29 @@ static int prepare_log_message(const char *index_file, const char *prefix) die("could not open %s\n", git_path(commit_editmsg)); stripspace(&sb, 0); - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die("could not write commit template: %s\n", - strerror(errno)); if (signoff) { - const char *info, *bol; - - info = git_committer_info(1); - strbuf_addch(&sb, '\0'); - bol = strrchr(sb.buf + sb.len - 1, '\n'); - if (!bol || prefixcmp(bol, sign_off_header)) - fprintf(fp, "\n"); - fprintf(fp, "%s%s\n", sign_off_header, git_committer_info(1)); + struct strbuf sob; + int i; + + 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_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)) + strbuf_addbuf(&sb, &sob); + strbuf_release(&sob); } + if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) + die("could not write commit template: %s\n", + strerror(errno)); + strbuf_release(&sb); if (in_merge && !no_edit) diff --git a/ident.c b/ident.c index 9b2a852..5be7533 100644 --- a/ident.c +++ b/ident.c @@ -224,13 +224,17 @@ const char *fmt_ident(const char *name, const char *email, } strcpy(date, git_default_date); - if (date_str) - parse_date(date_str, date, sizeof(date)); + if (date_str) { + if (*date_str) + parse_date(date_str, date, sizeof(date)); + else + date[0] = '\0'; + } i = copy(buffer, sizeof(buffer), 0, name); i = add_raw(buffer, sizeof(buffer), i, " <"); i = copy(buffer, sizeof(buffer), i, email); - i = add_raw(buffer, sizeof(buffer), i, "> "); + i = add_raw(buffer, sizeof(buffer), i, date[0] ? "> " : ">"); i = copy(buffer, sizeof(buffer), i, date); if (i >= sizeof(buffer)) die("Impossibly long personal identifier"); diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index cf389b8..49c1922 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -122,7 +122,19 @@ test_expect_success 'using alternate GIT_INDEX_FILE (2)' ' ) && cmp .git/index saved-index >/dev/null +' +cat > expect << EOF +zort +Signed-off-by: C O Mitter +EOF + +test_expect_success '--signoff' ' + echo "yet another content *narf*" >> foo && + echo "zort" | + GIT_EDITOR=../t7500/add-content git commit -s -F - foo && + git cat-file commit HEAD | sed "1,/^$/d" > output && + diff expect output ' test_done -- cgit v0.10.2-6-g49f6 From 2150554b0ed60356d8918b610834c04ad2eecdec Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 17:36:27 +0000 Subject: builtin-commit --s: add a newline if the last line was not a S-o-b The rule is this: if the last line already contains the sign off by the current committer, do nothing. If it contains another sign off, just add the sign off of the current committer. If the last line does not contain a sign off, add a new line before adding the sign off. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 780eec7..4dfa802 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -197,8 +197,11 @@ static int prepare_log_message(const char *index_file, const char *prefix) for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ - if (prefixcmp(sb.buf + i, sob.buf)) + if (prefixcmp(sb.buf + i, sob.buf)) { + if (prefixcmp(sb.buf + i, sign_off_header)) + strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); + } strbuf_release(&sob); } diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 49c1922..baed6ce 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -126,6 +126,7 @@ test_expect_success 'using alternate GIT_INDEX_FILE (2)' ' cat > expect << EOF zort + Signed-off-by: C O Mitter EOF -- cgit v0.10.2-6-g49f6 From f9568530c97757d840171c685fd623e1f68bc552 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 17:36:39 +0000 Subject: builtin-commit: resurrect behavior for multiple -m options When more than one -m option is given, the message does not replace the previous, but is appended as a new paragraph. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 4dfa802..ee79cf1 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -30,13 +30,27 @@ static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file lock_file; -static char *logfile, *force_author, *message, *template_file; +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 no_edit, initial_commit, in_merge; const char *only_include_assumed; +struct strbuf message; + +static int opt_parse_m(const struct option *opt, const char *arg, int unset) +{ + struct strbuf *buf = opt->value; + if (unset) + strbuf_setlen(buf, 0); + else { + strbuf_addstr(buf, arg); + strbuf_addch(buf, '\n'); + strbuf_addch(buf, '\n'); + } + return 0; +} static struct option builtin_commit_options[] = { OPT__QUIET(&quiet), @@ -45,7 +59,7 @@ static struct option builtin_commit_options[] = { OPT_STRING('F', "file", &logfile, "FILE", "read log from file"), OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), - OPT_STRING('m', "message", &message, "MESSAGE", "specify commit message"), + OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by: header"), @@ -150,8 +164,8 @@ static int prepare_log_message(const char *index_file, const char *prefix) FILE *fp; strbuf_init(&sb, 0); - if (message) { - strbuf_add(&sb, message, strlen(message)); + if (message.len) { + strbuf_addbuf(&sb, &message); } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) fprintf(stderr, "(reading log message from standard input)\n"); @@ -322,7 +336,7 @@ static int parse_and_validate_options(int argc, const char *argv[]) argc = parse_options(argc, argv, builtin_commit_options, builtin_commit_usage, 0); - if (logfile || message || use_message) + if (logfile || message.len || use_message) no_edit = 1; if (edit_flag) no_edit = 0; @@ -347,7 +361,7 @@ static int parse_and_validate_options(int argc, const char *argv[]) f++; if (f > 1) die("Only one of -c/-C/-F can be used."); - if (message && f > 0) + if (message.len && f > 0) die("Option -m cannot be combined with -c/-C/-F."); if (edit_message) use_message = edit_message; -- cgit v0.10.2-6-g49f6 From 129fa606365c172d07a5d98bea9345277f221363 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Nov 2007 17:36:52 +0000 Subject: builtin-commit: Add newline when showing which commit was created The function log_tree_commit() does not break the line, so we have to do it ourselves. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index ee79cf1..2233300 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -494,6 +494,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) printf("Created %scommit ", initial_commit ? "initial " : ""); log_tree_commit(&rev, commit); + printf("\n"); } int git_commit_config(const char *k, const char *v) -- cgit v0.10.2-6-g49f6 From ef12b50d0cf0123377a6fb96584a287a6c24346b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Mon, 12 Nov 2007 15:48:22 -0500 Subject: Call refresh_cache() when updating the user index for --only commits. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're guaranteeing the user that the index will be stat-clean after git commit. Thus, we need to call refresh_cache() for the user index too, in the 'git commit ' case. Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 2233300..ee9fe72 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -109,6 +109,7 @@ static char *prepare_index(const char **files, const char *prefix) /* update the user index file */ add_files_to_cache(verbose, prefix, files); + refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close(fd)) die("unable to write new_index file"); -- cgit v0.10.2-6-g49f6 From 18abc2dba4973c3c2cff286fac3340e95e0ee474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Wed, 14 Nov 2007 10:31:53 -0500 Subject: builtin-commit: Clean up an unused variable and a debug fprintf(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index ee9fe72..5e2257c 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -515,7 +515,7 @@ static const char commit_utf8_warn[] = int cmd_commit(int argc, const char **argv, const char *prefix) { - int header_len, parent_count = 0; + int header_len; struct strbuf sb; const char *index_file, *reflog_msg; char *nl; @@ -551,7 +551,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) /* Determine parents */ if (initial_commit) { reflog_msg = "commit (initial)"; - parent_count = 0; } else if (amend) { struct commit_list *c; struct commit *commit; @@ -592,10 +591,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) /* Get the commit message and validate it */ header_len = sb.len; - if (!no_edit) { - fprintf(stderr, "launching editor, log %s\n", logfile); + if (!no_edit) launch_editor(git_path(commit_editmsg), &sb); - } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) + else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) die("could not read commit message\n"); if (run_hook(index_file, "commit-msg", commit_editmsg)) exit(1); -- cgit v0.10.2-6-g49f6 From 1200993a1e885fd67d1a1d63da9d2a0e1ee5bcea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Thu, 15 Nov 2007 09:49:58 -0500 Subject: t7501-commit: Add test for git commit with dirty index. Signed-off-by: Junio C Hamano diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e601028..ce83af3 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -257,4 +257,14 @@ test_expect_success 'amend commit to fix author' ' diff expected current ' + +test_expect_success 'git commit with dirty index' ' + echo tacocat > elif && + echo tehlulz > chz && + git add chz && + git commit elif -m "tacocat is a palindrome" && + git show --stat | grep elif && + git diff --cached | grep chz +' + test_done -- cgit v0.10.2-6-g49f6 From 637efc3456576d548ed5b42e70deffca42e7428e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 15 Nov 2007 06:27:57 +0000 Subject: Replace "runstatus" with "status" in the tests We no longer have "runstatus", but running "status" is no longer that expensive anyway; it is a builtin. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index ae0639d..e25b255 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -86,7 +86,7 @@ EOF git config core.excludesFile excludes-file -git runstatus | grep "^# " > output +git status | grep "^# " > output cat > expect << EOF # .gitignore diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 063e792..877c1ea 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -71,10 +71,10 @@ test_expect_success 'favour same basenames over different ones' ' git rm path1 && mkdir subdir && git mv another-path subdir/path1 && - git runstatus | grep "renamed: .*path1 -> subdir/path1"' + git status | grep "renamed: .*path1 -> subdir/path1"' test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && - git runstatus | grep "renamed: .*path1 -> subdir/path1"' + git status | grep "renamed: .*path1 -> subdir/path1"' test_done -- cgit v0.10.2-6-g49f6 From a50f9fc5feb0a8b8afe51e75ae7c7a87446113e3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 01:58:16 -0800 Subject: file_exists(): dangling symlinks do exist This function is used to see if a path given by the user does exist on the filesystem. A symbolic link that does not point anywhere does exist but running stat() on it would yield an error, and it incorrectly said it does not exist. Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index 225fdfb..11a4cf3 100644 --- a/dir.c +++ b/dir.c @@ -690,11 +690,10 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i return dir->nr; } -int -file_exists(const char *f) +int file_exists(const char *f) { - struct stat sb; - return stat(f, &sb) == 0; + struct stat sb; + return lstat(f, &sb) == 0; } /* -- cgit v0.10.2-6-g49f6 From bc5d248a9fba11cb78dd0a3a91938d881dec1245 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 12:01:38 -0800 Subject: builtin-commit: do not color status output shown in the message template Noticed by Ping Yin on the list. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 5e2257c..7616dd1 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -159,7 +159,7 @@ static const char sign_off_header[] = "Signed-off-by: "; static int prepare_log_message(const char *index_file, const char *prefix) { struct stat statbuf; - int commitable; + int commitable, saved_color_setting; struct strbuf sb; char *buffer; FILE *fp; @@ -243,7 +243,10 @@ static int prepare_log_message(const char *index_file, const char *prefix) 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); + wt_status_use_color = saved_color_setting; fclose(fp); diff --git a/wt-status.h b/wt-status.h index f58ebcb..225fb4d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -27,6 +27,7 @@ struct wt_status { }; int git_status_config(const char *var, const char *value); +int wt_status_use_color; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); -- cgit v0.10.2-6-g49f6 From e06ad5ffb9f8d4207d676fadd675cf1b949ce358 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 12:21:17 -0800 Subject: builtin-commit: run commit-msg hook with correct message file It should run with $GIT_DIR/COMMIT_EDITMSG, not just COMMIT_EDITMSG. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 7616dd1..cd2f5ca 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -598,7 +598,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) launch_editor(git_path(commit_editmsg), &sb); else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) die("could not read commit message\n"); - if (run_hook(index_file, "commit-msg", commit_editmsg)) + if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) exit(1); stripspace(&sb, 1); if (sb.len < header_len || -- cgit v0.10.2-6-g49f6 From ee425e4643aa2d7be72cb4586d7554cecce44d6e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 01:13:32 -0800 Subject: Export three helper functions from ls-files This exports three helper functions from ls-files. * pathspec_match() checks if a given path matches a set of pathspecs and optionally records which pathspec was used. This function used to be called "match()" but renamed to be a bit less vague. * report_path_error() takes a set of pathspecs and the record pathspec_match() above leaves, and gives error message. This was split out of the main function of ls-files. * overlay_tree_on_cache() takes a tree-ish (typically "HEAD") and overlays it on the current in-core index. By iterating over the resulting index, the caller can find out the paths in either the index or the HEAD. This function used to be called "overlay_tree()" but renamed to be a bit more descriptive. Signed-off-by: Junio C Hamano diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 7f60709..0f0ab2d 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -38,28 +38,28 @@ static const char *tag_modified = ""; /* - * Match a pathspec against a filename. The first "len" characters + * Match a pathspec against a filename. The first "skiplen" characters * are the common prefix */ -static int match(const char **spec, char *ps_matched, - const char *filename, int len) +int pathspec_match(const char **spec, char *ps_matched, + const char *filename, int skiplen) { const char *m; while ((m = *spec++) != NULL) { - int matchlen = strlen(m + len); + int matchlen = strlen(m + skiplen); if (!matchlen) goto matched; - if (!strncmp(m + len, filename + len, matchlen)) { - if (m[len + matchlen - 1] == '/') + if (!strncmp(m + skiplen, filename + skiplen, matchlen)) { + if (m[skiplen + matchlen - 1] == '/') goto matched; - switch (filename[len + matchlen]) { + switch (filename[skiplen + matchlen]) { case '/': case '\0': goto matched; } } - if (!fnmatch(m + len, filename + len, 0)) + if (!fnmatch(m + skiplen, filename + skiplen, 0)) goto matched; if (ps_matched) ps_matched++; @@ -80,7 +80,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) if (len >= ent->len) die("git-ls-files: internal error - directory entry not superset of prefix"); - if (pathspec && !match(pathspec, ps_matched, ent->name, len)) + if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len)) return; fputs(tag, stdout); @@ -185,7 +185,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git-ls-files: internal error - cache entry not superset of prefix"); - if (pathspec && !match(pathspec, ps_matched, ce->name, len)) + if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len)) return; if (tag && *tag && show_valid_bit && @@ -331,7 +331,7 @@ static const char *verify_pathspec(const char *prefix) * that were given from the command line. We are not * going to write this index out. */ -static void overlay_tree(const char *tree_name, const char *prefix) +void overlay_tree_on_cache(const char *tree_name, const char *prefix) { struct tree *tree; unsigned char sha1[20]; @@ -384,6 +384,42 @@ static void overlay_tree(const char *tree_name, const char *prefix) } } +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) +{ + /* + * Make sure all pathspec matched; otherwise it is an error. + */ + int num, errors = 0; + for (num = 0; pathspec[num]; num++) { + int other, found_dup; + + if (ps_matched[num]) + continue; + /* + * The caller might have fed identical pathspec + * twice. Do not barf on such a mistake. + */ + for (found_dup = other = 0; + !found_dup && pathspec[other]; + other++) { + if (other == num || !ps_matched[other]) + continue; + if (!strcmp(pathspec[other], pathspec[num])) + /* + * Ok, we have a match already. + */ + found_dup = 1; + } + if (found_dup) + continue; + + error("pathspec '%s' did not match any file(s) known to git.", + pathspec[num] + prefix_offset); + errors++; + } + return errors; +} + static const char ls_files_usage[] = "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " "[ --ignored ] [--exclude=] [--exclude-from=] " @@ -568,47 +604,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) */ if (show_stage || show_unmerged) die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree(with_tree, prefix); + overlay_tree_on_cache(with_tree, prefix); } show_files(&dir, prefix); if (ps_matched) { - /* We need to make sure all pathspec matched otherwise - * it is an error. - */ - int num, errors = 0; - for (num = 0; pathspec[num]; num++) { - int other, found_dup; - - if (ps_matched[num]) - continue; - /* - * The caller might have fed identical pathspec - * twice. Do not barf on such a mistake. - */ - for (found_dup = other = 0; - !found_dup && pathspec[other]; - other++) { - if (other == num || !ps_matched[other]) - continue; - if (!strcmp(pathspec[other], pathspec[num])) - /* - * Ok, we have a match already. - */ - found_dup = 1; - } - if (found_dup) - continue; - - error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); - errors++; - } - - if (errors) + int bad; + bad = report_path_error(ps_matched, pathspec, prefix_offset); + if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); - return errors ? 1 : 0; + return bad ? 1 : 0; } return 0; diff --git a/cache.h b/cache.h index 33ebccf..26eec22 100644 --- a/cache.h +++ b/cache.h @@ -610,4 +610,10 @@ extern int diff_auto_refresh_index; /* match-trees.c */ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); + +/* ls-files */ +int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); +void overlay_tree_on_cache(const char *tree_name, const char *prefix); + #endif /* CACHE_H */ -- cgit v0.10.2-6-g49f6 From b6ec1d619fb54642388063a88e2255556cf5de06 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 01:12:04 -0800 Subject: Fix add_files_to_cache() to take pathspec, not user specified list of files This separates the logic to limit the extent of change to the index by where you are (controlled by "prefix") and what you specify from the command line (controlled by "pathspec"). Signed-off-by: Junio C Hamano diff --git a/builtin-add.c b/builtin-add.c index cf815a0..03508d3 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -105,12 +105,12 @@ static void update_callback(struct diff_queue_struct *q, } } -void add_files_to_cache(int verbose, const char *prefix, const char **files) +void add_files_to_cache(int verbose, const char *prefix, const char **pathspec) { struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = get_pathspec(prefix, files); + rev.prune_data = pathspec; rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &verbose; @@ -180,9 +180,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (take_worktree_changes) { + const char **pathspec; if (read_cache() < 0) die("index file corrupt"); - add_files_to_cache(verbose, prefix, argv); + pathspec = get_pathspec(prefix, argv); + add_files_to_cache(verbose, prefix, pathspec); goto finish; } diff --git a/cache.h b/cache.h index 26eec22..cf0bdc6 100644 --- a/cache.h +++ b/cache.h @@ -604,13 +604,15 @@ extern void trace_argv_printf(const char **argv, int count, const char *format, extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst); extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); +/* add */ +void add_files_to_cache(int verbose, const char *prefix, const char **pathspec); + /* diff.c */ extern int diff_auto_refresh_index; /* match-trees.c */ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); - /* ls-files */ int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); diff --git a/commit.h b/commit.h index aa67986..f450aae 100644 --- a/commit.h +++ b/commit.h @@ -114,7 +114,6 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(void); -extern void add_files_to_cache(int verbose, const char *prefix, const char **files); extern int rerere(void); static inline int single_parent(struct commit *commit) -- cgit v0.10.2-6-g49f6 From 2888605c649ccd423232161186d72c0e6c458a48 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 01:52:55 -0800 Subject: builtin-commit: fix partial-commit support When making a partial-commit, we need to prepare two index files, one to be used to write out the tree to be committed (temporary index) and the other to be used as the index file after the commit is made. The temporary index needs to be initialized to HEAD and then all the named paths on the command line need to be staged on top of the index. For this, running add_files_to_cache() that compares what is in the index and the paths given from the command line is not enough -- the comparison will miss the paths that the user previously ran "git add" to the index since the HEAD because the index reset to the HEAD would not know about them. The index file needs to get the same modification done when preparing the temporary index as described above. This implementation mimics the behaviour of the scripted version of git-commit. It first runs overlay_tree_on_cache(), which was stolen from ls-files with the earlier change, to get the list of paths that the user can potentially mean, and then uses pathspec_match() to find which ones the user meant. This list of paths is used to update both the temporary and the real index file. Additional fixes are: - read the index file after pre-commit hook returns, as the hook can modify it to affect the contents of the commit. - remove the temporary index file .git/next-index-* after commit is done or aborted. - run post-commit hook with the real index file to be used after the commit (previously it gave the temporary commit if a partial commit was made). - resurrect the safety mechanism to refuse partial commits during a merge to match the scripted version. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index cd2f5ca..e779db8 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -7,6 +7,7 @@ #include "cache.h" #include "cache-tree.h" +#include "dir.h" #include "builtin.h" #include "diff.h" #include "diffcore.h" @@ -19,6 +20,7 @@ #include "strbuf.h" #include "utf8.h" #include "parse-options.h" +#include "path-list.h" static const char * const builtin_commit_usage[] = { "git-commit [options] [--] ...", @@ -28,7 +30,13 @@ static const char * const builtin_commit_usage[] = { static unsigned char head_sha1[20], merge_head_sha1[20]; static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; -static struct lock_file lock_file; +static struct lock_file index_lock; /* real index */ +static struct lock_file false_lock; /* used only for partial commits */ +static enum { + COMMIT_AS_IS = 1, + COMMIT_NORMAL, + COMMIT_PARTIAL, +} commit_style; static char *logfile, *force_author, *template_file; static char *edit_message, *use_message; @@ -78,41 +86,179 @@ static struct option builtin_commit_options[] = { OPT_END() }; +static void rollback_index_files(void) +{ + switch (commit_style) { + case COMMIT_AS_IS: + break; /* nothing to do */ + case COMMIT_NORMAL: + rollback_lock_file(&index_lock); + break; + case COMMIT_PARTIAL: + rollback_lock_file(&index_lock); + rollback_lock_file(&false_lock); + break; + } +} + +static void commit_index_files(void) +{ + switch (commit_style) { + case COMMIT_AS_IS: + break; /* nothing to do */ + case COMMIT_NORMAL: + commit_lock_file(&index_lock); + break; + case COMMIT_PARTIAL: + commit_lock_file(&index_lock); + rollback_lock_file(&false_lock); + break; + } +} + +/* + * Take a union of paths in the index and the named tree (typically, "HEAD"), + * and return the paths that match the given pattern in list. + */ +static int list_paths(struct path_list *list, const char *with_tree, + const char *prefix, const char **pattern) +{ + int i; + char *m; + + for (i = 0; pattern[i]; i++) + ; + m = xcalloc(1, i); + + if (with_tree) + overlay_tree_on_cache(with_tree, prefix); + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce->ce_flags & htons(CE_UPDATE)) + continue; + if (!pathspec_match(pattern, m, ce->name, 0)) + continue; + path_list_insert(ce->name, list); + } + + return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); +} + +static void add_remove_files(struct path_list *list) +{ + int i; + for (i = 0; i < list->nr; i++) { + struct path_list_item *p = &(list->items[i]); + if (file_exists(p->path)) + add_file_to_cache(p->path, 0); + else + remove_file_from_cache(p->path); + } +} + static char *prepare_index(const char **files, const char *prefix) { int fd; struct tree *tree; - struct lock_file *next_index_lock; + struct path_list partial; + const char **pathspec = NULL; if (interactive) { interactive_add(); + commit_style = COMMIT_AS_IS; return get_index_file(); } - fd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) die("index file corrupt"); - if (all || also) { - add_files_to_cache(verbose, also ? prefix : NULL, files); + if (*files) + pathspec = get_pathspec(prefix, files); + + /* + * Non partial, non as-is commit. + * + * (1) get the real index; + * (2) update the_index as necessary; + * (3) write the_index out to the real index (still locked); + * (4) return the name of the locked index file. + * + * The caller should run hooks on the locked real index, and + * (A) if all goes well, commit the real index; + * (B) on failure, rollback the real index. + */ + if (all || (also && pathspec && *pathspec)) { + 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)) die("unable to write new_index file"); - return lock_file.filename; + commit_style = COMMIT_NORMAL; + return index_lock.filename; } - if (*files == NULL) { - /* Commit index as-is. */ - rollback_lock_file(&lock_file); + /* + * As-is commit. + * + * (1) return the name of the real index file. + * + * The caller should run hooks on the real index, and run + * hooks on the real index, and create commit from the_index. + * We still need to refresh the index here. + */ + if (!pathspec || !*pathspec) { + 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)) + die("unable to write new_index file"); + commit_style = COMMIT_AS_IS; return get_index_file(); } - /* update the user index file */ - add_files_to_cache(verbose, prefix, files); + /* + * A partial commit. + * + * (0) find the set of affected paths; + * (1) get lock on the real index file; + * (2) update the_index with the given paths; + * (3) write the_index out to the real index (still locked); + * (4) get lock on the false index file; + * (5) reset the_index from HEAD; + * (6) update the_index the same way as (2); + * (7) write the_index out to the false index file; + * (8) return the name of the false index file (still locked); + * + * The caller should run hooks on the locked false index, and + * create commit from it. Then + * (A) if all goes well, commit the real index; + * (B) on failure, rollback the real index; + * In either case, rollback the false index. + */ + commit_style = COMMIT_PARTIAL; + + if (file_exists(git_path("MERGE_HEAD"))) + die("cannot do a partial commit during a merge."); + + memset(&partial, 0, sizeof(partial)); + partial.strdup_paths = 1; + if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec)) + exit(1); + + discard_cache(); + if (read_cache() < 0) + die("cannot read the index"); + + 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)) 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) @@ -120,17 +266,12 @@ static char *prepare_index(const char **files, const char *prefix) if (read_tree(tree, 0, NULL)) die("failed to read HEAD tree object"); } - - /* Use a lock file to garbage collect the temporary index file. */ - next_index_lock = xmalloc(sizeof(*next_index_lock)); - fd = hold_lock_file_for_update(next_index_lock, - git_path("next-index-%d", getpid()), 1); - add_files_to_cache(verbose, prefix, files); + add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || close(fd)) - die("unable to write new_index file"); - return next_index_lock->filename; + if (write_cache(fd, active_cache, active_nr) || close(fd)) + die("unable to write temporary index file"); + return false_lock.filename; } static int run_status(FILE *fp, const char *index_file, const char *prefix) @@ -441,7 +582,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) commitable = run_status(stdout, index_file, prefix); - rollback_lock_file(&lock_file); + rollback_index_files(); return commitable ? 0 : 1; } @@ -531,23 +672,36 @@ int cmd_commit(int argc, const char **argv, const char *prefix) index_file = prepare_index(argv, prefix); - if (!no_verify && run_hook(index_file, "pre-commit", NULL)) - exit(1); + 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); + rollback_index_files(); unlink(commit_editmsg); return 1; } - strbuf_init(&sb, 0); - - /* Start building up the commit header */ + /* + * Re-read the index as pre-commit hook could have updated it, + * and write it out as a tree. + */ + discard_cache(); read_cache_from(index_file); - active_cache_tree = cache_tree(); + if (!active_cache_tree) + active_cache_tree = cache_tree(); if (cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) + active_cache, active_nr, 0, 0) < 0) { + rollback_index_files(); die("Error building trees"); + } + + /* + * The commit object + */ + strbuf_init(&sb, 0); strbuf_addf(&sb, "tree %s\n", sha1_to_hex(active_cache_tree->sha1)); @@ -596,20 +750,27 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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) + else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { + rollback_index_files(); die("could not read commit message\n"); - if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) + } + if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { + rollback_index_files(); exit(1); + } stripspace(&sb, 1); - if (sb.len < header_len || - message_is_empty(&sb, header_len)) + if (sb.len < header_len || message_is_empty(&sb, header_len)) { + rollback_index_files(); die("* no commit message? aborting commit."); + } strbuf_addch(&sb, '\0'); if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf)) fprintf(stderr, commit_utf8_warn); - if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) + if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) { + rollback_index_files(); die("failed to write commit object"); + } ref_lock = lock_any_ref_for_update("HEAD", initial_commit ? NULL : head_sha1, @@ -624,21 +785,22 @@ 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) + if (!ref_lock) { + rollback_index_files(); die("cannot lock HEAD ref"); - if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) + } + if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { + rollback_index_files(); die("cannot update HEAD ref"); + } unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); - if (lock_file.filename[0] && commit_locked_index(&lock_file)) - die("failed to write new index"); + commit_index_files(); rerere(); - - run_hook(index_file, "post-commit", NULL); - + run_hook(get_index_file(), "post-commit", NULL); if (!quiet) print_summary(prefix, commit_sha1); -- cgit v0.10.2-6-g49f6 From 99a12694582e2148fcd492f1eedaddcfe2a21621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Wed, 21 Nov 2007 21:54:49 -0500 Subject: builtin-commit: Include the diff in the commit message when verbose. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run_diff_index() and the entire diff machinery is hard coded to output to stdout, so just redirect that and restore it when done. Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index e779db8..4de316a 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -662,7 +662,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int header_len; struct strbuf sb; const char *index_file, *reflog_msg; - char *nl; + char *nl, *p; unsigned char commit_sha1[20]; struct ref_lock *ref_lock; @@ -758,6 +758,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) rollback_index_files(); exit(1); } + + /* 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); + stripspace(&sb, 1); if (sb.len < header_len || message_is_empty(&sb, header_len)) { rollback_index_files(); diff --git a/wt-status.c b/wt-status.c index d3c10b8..0e0439f 100644 --- a/wt-status.c +++ b/wt-status.c @@ -315,12 +315,28 @@ static void wt_status_print_untracked(struct wt_status *s) static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; + int saved_stdout; + + fflush(s->fp); + + /* Sigh, the entire diff machinery is hardcoded to output to + * stdout. Do the dup-dance...*/ + saved_stdout = dup(STDOUT_FILENO); + if (saved_stdout < 0 ||dup2(fileno(s->fp), STDOUT_FILENO) < 0) + die("couldn't redirect stdout\n"); + init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, s->reference); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; wt_read_cache(s); run_diff_index(&rev, 1); + + fflush(stdout); + + if (dup2(saved_stdout, STDOUT_FILENO) < 0) + die("couldn't restore stdout\n"); + close(saved_stdout); } void wt_status_print(struct wt_status *s) -- cgit v0.10.2-6-g49f6 From b468f0ce4881bf42ffc820b1cddad67dad17fd80 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 22 Nov 2007 16:21:49 -0800 Subject: Add a few more tests for git-commit Signed-off-by: Junio C Hamano diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh new file mode 100755 index 0000000..21ac785 --- /dev/null +++ b/t/t7502-commit.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='git commit porcelain-ish' + +. ./test-lib.sh + +test_expect_success 'the basics' ' + + echo doing partial >"commit is" && + mkdir not && + echo very much encouraged but we should >not/forbid && + git add "commit is" not && + echo update added "commit is" file >"commit is" && + echo also update another >not/forbid && + test_tick && + git commit -a -m "initial with -a" && + + git cat-file blob HEAD:"commit is" >current.1 && + git cat-file blob HEAD:not/forbid >current.2 && + + cmp current.1 "commit is" && + cmp current.2 not/forbid + +' + +test_expect_success 'partial' ' + + echo another >"commit is" && + echo another >not/forbid && + test_tick && + git commit -m "partial commit to handle a file" "commit is" && + + changed=$(git diff-tree --name-only HEAD^ HEAD) && + test "$changed" = "commit is" + +' + +test_expect_success 'partial modification in a subdirecotry' ' + + test_tick && + git commit -m "partial commit to subdirectory" not && + + changed=$(git diff-tree -r --name-only HEAD^ HEAD) && + test "$changed" = "not/forbid" + +' + +test_expect_success 'partial removal' ' + + git rm not/forbid && + git commit -m "partial commit to remove not/forbid" not && + + changed=$(git diff-tree -r --name-only HEAD^ HEAD) && + test "$changed" = "not/forbid" && + remain=$(git ls-tree -r --name-only HEAD) && + test "$remain" = "commit is" + +' + +test_expect_success 'sign off' ' + + >positive && + git add positive && + git commit -s -m "thank you" && + actual=$(git cat-file commit HEAD | sed -ne "s/Signed-off-by: //p") && + expected=$(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") && + test "z$actual" = "z$expected" + +' + +test_expect_success 'multiple -m' ' + + >negative && + git add negative && + git commit -m "one" -m "two" -m "three" && + actual=$(git cat-file commit HEAD | sed -e "1,/^\$/d") && + expected=$(echo one; echo; echo two; echo; echo three) && + test "z$actual" = "z$expected" + +' + +test_expect_success 'verbose' ' + + echo minus >negative && + git add negative && + git status -v | sed -ne "/^diff --git /p" >actual && + echo "diff --git a/negative b/negative" >expect && + diff -u expect actual + +' + +test_done -- cgit v0.10.2-6-g49f6 From 8babab95af7c01d9ea75c97ee0df40e5a2170b83 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Mon, 26 Nov 2007 09:59:27 +0100 Subject: builtin-commit.c: export GIT_INDEX_FILE for launch_editor as well. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The editor program to let the user edit the log message used to get GIT_INDEX_FILE environment variable pointing at the right file, but this was lost when git-commit was rewritten in C. Signed-off-by: Pierre Habouzit Acked-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 4de316a..f60bd7f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -748,9 +748,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) /* 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) { + if (!no_edit) { + 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), &sb, env); + } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { rollback_index_files(); die("could not read commit message\n"); } diff --git a/builtin-tag.c b/builtin-tag.c index 566b9d1..7626da3 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -17,7 +17,7 @@ static const char builtin_tag_usage[] = static char signingkey[1000]; -void launch_editor(const char *path, struct strbuf *buffer) +void launch_editor(const char *path, struct strbuf *buffer, const char *const *env) { const char *editor, *terminal; @@ -43,7 +43,7 @@ void launch_editor(const char *path, struct strbuf *buffer) if (strcmp(editor, ":")) { const char *args[] = { editor, path, NULL }; - if (run_command_v_opt(args, 0)) + if (run_command_v_opt_cd_env(args, 0, NULL, env)) die("There was a problem with the editor %s.", editor); } @@ -312,7 +312,7 @@ static void create_tag(const unsigned char *object, const char *tag, write_or_die(fd, tag_template, strlen(tag_template)); close(fd); - launch_editor(path, buf); + launch_editor(path, buf, NULL); unlink(path); free(path); diff --git a/strbuf.h b/strbuf.h index 8334a9b..36d61db 100644 --- a/strbuf.h +++ b/strbuf.h @@ -117,6 +117,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); extern int strbuf_getline(struct strbuf *, FILE *, int); extern void stripspace(struct strbuf *buf, int skip_comments); -extern void launch_editor(const char *path, struct strbuf *buffer); +extern void launch_editor(const char *path, struct strbuf *buffer, const char *const *env); #endif /* STRBUF_H */ -- cgit v0.10.2-6-g49f6 From a98b819183820cf87f1073c36b57883cb05e7049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20H=C3=B8gsberg?= Date: Mon, 26 Nov 2007 10:16:08 -0500 Subject: Fix off-by-one error when truncating the diff out of the commit message. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristian Høgsberg Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index f60bd7f..a35881e 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -765,7 +765,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) /* 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 (sb.len < header_len || message_is_empty(&sb, header_len)) { -- cgit v0.10.2-6-g49f6 From e475fe16a9b7f36478ee4f304e53d6979e4f1710 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 23 Nov 2007 15:35:08 -0500 Subject: Remove git-status from list of scripts as it is builtin Now that git-status is builtin on Cygwin this compiles as git-status.exe. We cannot continue to include git-status as a Makefile target as it will never be built. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 35f9c87..625b9c3 100644 --- a/Makefile +++ b/Makefile @@ -234,7 +234,7 @@ SCRIPT_PERL = \ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - git-status git-instaweb + git-instaweb # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ -- cgit v0.10.2-6-g49f6 From 7168624c3530d8c7ee32f930f8fb2ba302b9801f Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 28 Nov 2007 22:13:08 +0100 Subject: Do not generate full commit log message if it is not going to be used Like when it is already specified through -C, -F or -m to git-commit. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index a35881e..1a9a256 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -367,6 +367,28 @@ static int prepare_log_message(const char *index_file, const char *prefix) strbuf_release(&sb); + if (no_edit) { + struct rev_info rev; + unsigned char sha1[40]; + + fclose(fp); + + if (!active_nr && read_cache() < 0) + die("Cannot read index"); + + if (get_sha1("HEAD", sha1) != 0) + return !!active_nr; + + init_revisions(&rev, ""); + rev.abbrev = 0; + setup_revisions(0, NULL, &rev, "HEAD"); + 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 && !no_edit) fprintf(fp, "#\n" -- cgit v0.10.2-6-g49f6 From b5b644a93adb41bca590a3cdfd9b64ccf3614f50 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 2 Dec 2007 01:07:03 -0500 Subject: git-commit: clean up die messages These are three types of cleanups here: 1. remove newline from die message (die/report adds it already) 2. typo: s/merger/merge/ 3. the old "* no commit message? aborting commit." is now prepended with "fatal: ", making the asterisk look a little funny. Let's just remove it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 1a9a256..6c1ace3 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -336,7 +336,7 @@ 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); @@ -362,8 +362,7 @@ 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); @@ -470,13 +469,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)); @@ -488,7 +487,7 @@ 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)); } @@ -518,7 +517,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++; @@ -641,7 +640,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"); @@ -777,7 +776,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) launch_editor(git_path(commit_editmsg), &sb, env); } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { rollback_index_files(); - die("could not read commit message\n"); + die("could not read commit message"); } if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { rollback_index_files(); @@ -792,7 +791,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) stripspace(&sb, 1); 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)) -- cgit v0.10.2-6-g49f6 From d9ccfe7711a8bf1ed9d9cd87daa9863e0d564b23 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Dec 2007 13:43:34 -0800 Subject: Fix --signoff in builtin-commit differently. Introduce fmt_name() specifically meant for formatting the name and email pair, to add signed-off-by value. This reverts parts of 13208572fbe8838fd8835548d7502202d1f7b21d (builtin-commit: fix --signoff) so that an empty datestamp string given to fmt_ident() by mistake will error out as before. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 6c1ace3..05594f2 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -346,11 +346,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)) { diff --git a/cache.h b/cache.h index cf0bdc6..43cfebb 100644 --- a/cache.h +++ b/cache.h @@ -444,6 +444,7 @@ enum date_mode parse_date_format(const char *format); extern const char *git_author_info(int); extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); +extern const char *fmt_name(const char *name, const char *email); struct checkout { const char *base_dir; diff --git a/ident.c b/ident.c index 5be7533..021d79b 100644 --- a/ident.c +++ b/ident.c @@ -192,12 +192,14 @@ static const char *env_hint = "Omit --global to set the identity only in this repository.\n" "\n"; -const char *fmt_ident(const char *name, const char *email, - const char *date_str, int error_on_no_name) +static const char *fmt_ident_1(const char *name, const char *email, + const char *date_str, int flag) { static char buffer[1000]; char date[50]; int i; + int error_on_no_name = !!(flag & 01); + int name_addr_only = !!(flag & 02); setup_ident(); if (!name) @@ -224,24 +226,36 @@ const char *fmt_ident(const char *name, const char *email, } strcpy(date, git_default_date); - if (date_str) { - if (*date_str) - parse_date(date_str, date, sizeof(date)); - else - date[0] = '\0'; - } + if (!name_addr_only && date_str) + parse_date(date_str, date, sizeof(date)); i = copy(buffer, sizeof(buffer), 0, name); i = add_raw(buffer, sizeof(buffer), i, " <"); i = copy(buffer, sizeof(buffer), i, email); - i = add_raw(buffer, sizeof(buffer), i, date[0] ? "> " : ">"); - i = copy(buffer, sizeof(buffer), i, date); + if (!name_addr_only) { + i = add_raw(buffer, sizeof(buffer), i, "> "); + i = copy(buffer, sizeof(buffer), i, date); + } else { + i = add_raw(buffer, sizeof(buffer), i, ">"); + } if (i >= sizeof(buffer)) die("Impossibly long personal identifier"); buffer[i] = 0; return buffer; } +const char *fmt_ident(const char *name, const char *email, + const char *date_str, int error_on_no_name) +{ + int flag = (error_on_no_name ? 01 : 0); + return fmt_ident_1(name, email, date_str, flag); +} + +const char *fmt_name(const char *name, const char *email) +{ + return fmt_ident_1(name, email, NULL, 03); +} + const char *git_author_info(int error_on_no_name) { return fmt_ident(getenv("GIT_AUTHOR_NAME"), -- cgit v0.10.2-6-g49f6 From 2f02b25f36bce23e6b65c5112876796a56e084ca Mon Sep 17 00:00:00 2001 From: Shawn Bohrer Date: Sun, 2 Dec 2007 23:02:09 -0600 Subject: Make git status usage say git status instead of git commit git status shares the same usage information as git commit since it shows what would be committed if the same options are given. However, when displaying the usage information for git status it should say it is for git status not git commit. Signed-off-by: Shawn Bohrer Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 05594f2..f37a90f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -27,6 +27,11 @@ static const char * const builtin_commit_usage[] = { 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"; @@ -493,12 +498,12 @@ static void determine_author_info(struct strbuf *sb) strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, 1)); } -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; @@ -595,7 +600,7 @@ 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); @@ -687,7 +692,7 @@ 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); -- cgit v0.10.2-6-g49f6 From 69e7491835a0aa4e1a793a7c131783d8bb1cbb2b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 3 Dec 2007 00:30:01 -0500 Subject: quote_path: fix collapsing of relative paths The code tries to collapse identical leading components between the prefix and the path. So if we're in "dir1", the path "dir1/file" should become just "file". However, we were ending up with "../dir1/file". The included test expected the wrong output. The "len" parameter to quote_path can be negative to mean "this is a NUL terminated string". Simply count it so that the loop can rely on it being the length of the path. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/t/t7502-status.sh b/t/t7502-status.sh index 269b334..d6ae69d 100755 --- a/t/t7502-status.sh +++ b/t/t7502-status.sh @@ -68,7 +68,7 @@ cat > expect << \EOF # Changed but not updated: # (use "git add ..." to update what will be committed) # -# modified: ../dir1/modified +# modified: modified # # Untracked files: # (use "git add ..." to include in what will be committed) diff --git a/wt-status.c b/wt-status.c index 0e0439f..52ab41c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -82,12 +82,13 @@ static void wt_status_print_trailer(struct wt_status *s) } static char *quote_path(const char *in, int len, - struct strbuf *out, const char *prefix) + struct strbuf *out, const char *prefix) { - if (len > 0) - strbuf_grow(out, len); - strbuf_setlen(out, 0); + if (len < 0) + len = strlen(in); + strbuf_grow(out, len); + strbuf_setlen(out, 0); if (prefix) { int off = 0; while (prefix[off] && off < len && prefix[off] == in[off]) @@ -104,7 +105,7 @@ static char *quote_path(const char *in, int len, strbuf_addstr(out, "../"); } - for (; (len < 0 && *in) || len > 0; in++, len--) { + for ( ; len > 0; in++, len--) { int ch = *in; switch (ch) { -- cgit v0.10.2-6-g49f6 From 9663c3bc6a04b9b4f63a54b820d3edb16aa95e6d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 2 Dec 2007 23:55:23 -0800 Subject: git-commit: Allow to amend a merge commit that does not change the tree Normally, it should not be allowed to generate an empty commit. A merge commit generated with git 'merge -s ours' does not change the tree (along the first parent), but merges are not "empty" even if they do not change the tree. Hence, we should be careful not to forbid this case. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index f37a90f..6c2dc39 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -676,6 +676,14 @@ 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" @@ -701,7 +709,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) return 1; } - if (!prepare_log_message(index_file, prefix) && !in_merge) { + if (!prepare_log_message(index_file, prefix) && !in_merge && + !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix); rollback_index_files(); unlink(commit_editmsg); -- cgit v0.10.2-6-g49f6 From 5241b6bfe2285a6da598a0348c37b77964035bc8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 3 Dec 2007 00:03:10 -0800 Subject: git-commit --allow-empty It does not usually make sense to record a commit that has the exact same tree as its sole parent commit and that is why git-commit prevents you from making such a mistake, but when data from foreign scm is involved, it is a different story. We are equipped to represent such an (perhaps insane, perhaps by mistake, or perhaps done on purpose) empty change, and it is better to represent it bypassing the safety valve for native use. This is primarily for use by foreign scm interface scripts. Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 6c2dc39..e635d99 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -46,7 +46,7 @@ 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; static int no_edit, initial_commit, in_merge; const char *only_include_assumed; @@ -87,6 +87,7 @@ 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_END() }; @@ -710,7 +711,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (!prepare_log_message(index_file, prefix) && !in_merge && - !(amend && is_a_merge(head_sha1))) { + !allow_empty && !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix); rollback_index_files(); unlink(commit_editmsg); -- cgit v0.10.2-6-g49f6