From a00a42ae33708caa742d9e9fbf10692cfa42f032 Mon Sep 17 00:00:00 2001 From: Thomas Harning Date: Thu, 22 Nov 2007 15:19:40 -0500 Subject: git-merge-ours: make it a builtin. Except that this fixes a longstanding corner case bug by tightening the way underlying diff-index command is run, it is functionally equivalent to the scripted version. Signed-off-by: Thomas Harning Jr Signed-off-by: Junio C Hamano --- Makefile | 3 ++- builtin-merge-ours.c | 28 ++++++++++++++++++++++++++++ builtin.h | 1 + contrib/examples/git-merge-ours.sh | 14 ++++++++++++++ git-merge-ours.sh | 14 -------------- git.c | 1 + 6 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 builtin-merge-ours.c create mode 100755 contrib/examples/git-merge-ours.sh delete mode 100755 git-merge-ours.sh diff --git a/Makefile b/Makefile index cabde8177e..7a0ee780dc 100644 --- a/Makefile +++ b/Makefile @@ -221,7 +221,7 @@ SCRIPT_SH = \ git-sh-setup.sh \ git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ - git-merge-resolve.sh git-merge-ours.sh \ + git-merge-resolve.sh \ git-lost-found.sh git-quiltimport.sh git-submodule.sh \ git-filter-branch.sh \ git-stash.sh @@ -353,6 +353,7 @@ BUILTIN_OBJS = \ builtin-mailsplit.o \ builtin-merge-base.o \ builtin-merge-file.o \ + builtin-merge-ours.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c new file mode 100644 index 0000000000..8f5bbaf402 --- /dev/null +++ b/builtin-merge-ours.c @@ -0,0 +1,28 @@ +/* + * Implementation of git-merge-ours.sh as builtin + * + * Copyright (c) 2007 Thomas Harning Jr + * Original: + * Original Copyright (c) 2005 Junio C Hamano + * + * Pretend we resolved the heads, but declare our tree trumps everybody else. + */ +#include "git-compat-util.h" +#include "builtin.h" + +static const char *diff_index_args[] = { + "diff-index", "--quiet", "--cached", "HEAD", "--", NULL +}; +#define NARGS (ARRAY_SIZE(diff_index_args) - 1) + +int cmd_merge_ours(int argc, const char **argv, const char *prefix) +{ + /* + * We need to exit with 2 if the index does not match our HEAD tree, + * because the current index is what we will be committing as the + * merge result. + */ + if (cmd_diff_index(NARGS, diff_index_args, prefix)) + exit(2); + exit(0); +} diff --git a/builtin.h b/builtin.h index 9a6213af12..bcb54aaded 100644 --- a/builtin.h +++ b/builtin.h @@ -51,6 +51,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_merge_base(int argc, const char **argv, const char *prefix); +extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); diff --git a/contrib/examples/git-merge-ours.sh b/contrib/examples/git-merge-ours.sh new file mode 100755 index 0000000000..c81a790aa6 --- /dev/null +++ b/contrib/examples/git-merge-ours.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# +# Pretend we resolved the heads, but declare our tree trumps everybody else. +# + +# We need to exit with 2 if the index does not match our HEAD tree, +# because the current index is what we will be committing as the +# merge result. + +git diff-index --quiet --cached HEAD || exit 2 + +exit 0 diff --git a/git-merge-ours.sh b/git-merge-ours.sh deleted file mode 100755 index c81a790aa6..0000000000 --- a/git-merge-ours.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano -# -# Pretend we resolved the heads, but declare our tree trumps everybody else. -# - -# We need to exit with 2 if the index does not match our HEAD tree, -# because the current index is what we will be committing as the -# merge result. - -git diff-index --quiet --cached HEAD || exit 2 - -exit 0 diff --git a/git.c b/git.c index 7604319b5a..80c2f14a8b 100644 --- a/git.c +++ b/git.c @@ -325,6 +325,7 @@ static void handle_internal_command(int argc, const char **argv) { "mailsplit", cmd_mailsplit }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file }, + { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, -- cgit v1.2.1 From c5546e88fe492558313965a49512ce5c71a98dfe Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 22 Nov 2007 12:24:59 +0000 Subject: bundle create: keep symbolic refs' names instead of resolving them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When creating a bundle, symbolic refs used to be resolved to the non-symbolic refs they point to before being written to the list of contained refs. I.e. "git bundle create a1.bundle HEAD master" would show something like 388afe7881b33102fada216dd07806728773c011 refs/heads/master 388afe7881b33102fada216dd07806728773c011 refs/heads/master instead of 388afe7881b33102fada216dd07806728773c011 HEAD 388afe7881b33102fada216dd07806728773c011 refs/heads/master Introduce a special handling so that the symbolic refs are listed with the names passed on the command line. Noticed by Santi Béjar. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 9 ++++++++- t/t5510-fetch.sh | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 1b650069c9..d1840555d6 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -6,6 +6,7 @@ #include "revision.h" #include "list-objects.h" #include "run-command.h" +#include "refs.h" /* * Basic handler for bundle files to connect repositories via sneakernet. @@ -253,11 +254,17 @@ static int create_bundle(struct bundle_header *header, const char *path, struct object_array_entry *e = revs.pending.objects + i; unsigned char sha1[20]; char *ref; + const char *display_ref; + int flag; if (e->item->flags & UNINTERESTING) continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) continue; + if (!resolve_ref(e->name, sha1, 1, &flag)) + flag = 0; + display_ref = (flag & REF_ISSYMREF) ? e->name : ref; + /* * Make sure the refs we wrote out is correct; --max-count and * other limiting options could have prevented all the tips @@ -308,7 +315,7 @@ static int create_bundle(struct bundle_header *header, const char *path, ref_count++; write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); write_or_die(bundle_fd, " ", 1); - write_or_die(bundle_fd, ref, strlen(ref)); + write_or_die(bundle_fd, display_ref, strlen(display_ref)); write_or_die(bundle_fd, "\n", 1); free(ref); } diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 439430f569..7406de35ae 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -153,4 +153,17 @@ test_expect_success 'bundle should be able to create a full history' ' ' +test_expect_success 'bundle should record HEAD correctly' ' + + cd "$D" && + git bundle create bundle5 HEAD master && + git bundle list-heads bundle5 >actual && + for h in HEAD refs/heads/master + do + echo "$(git rev-parse --verify $h) $h" + done >expect && + diff -u expect actual + +' + test_done -- cgit v1.2.1 From 6047a234c5a6aef58c72a35feba326484f07660d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 22 Nov 2007 12:30:10 +0000 Subject: rebase -i: move help to end of todo file [PATCH] rebase -i: move help to end of todo file Many editors start in the first line, so the 9-line help text was an annoyance. So move it to the end. Requested by Junio. While at it, add a hint how to abort the rebase. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 14 ++++++++------ t/t3404-rebase-interactive.sh | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 66c80d4e16..bf44b6af58 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -485,8 +485,13 @@ do SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) SHORTHEAD=$(git rev-parse --short $HEAD) SHORTONTO=$(git rev-parse --short $ONTO) - cat > "$TODO" << EOF -# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO + git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ + --abbrev=7 --reverse --left-right --cherry-pick \ + $UPSTREAM...$HEAD | \ + sed -n "s/^>/pick /p" > "$TODO" + cat >> "$TODO" << EOF + +# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO # # Commands: # pick = use commit @@ -494,12 +499,9 @@ do # squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. +# However, if you remove everything, the rebase will be aborted. # EOF - git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ - --abbrev=7 --reverse --left-right --cherry-pick \ - $UPSTREAM...$HEAD | \ - sed -n "s/^>/pick /p" >> "$TODO" has_action "$TODO" || die_abort "Nothing to do" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index f1039d1a21..907c7f9f6b 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -149,7 +149,8 @@ test_expect_success 'stop on conflicting pick' ' diff -u expect .git/.dotest-merge/patch && diff -u expect2 file1 && test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) && - test 0 = $(grep -v "^#" < .git/.dotest-merge/git-rebase-todo | wc -l) + test 0 = $(grep -ve "^#" -e "^$" < .git/.dotest-merge/git-rebase-todo | + wc -l) ' test_expect_success 'abort' ' -- cgit v1.2.1 From 958e67c0a8b7f12462ad1828ac3b3178181ea4e9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 22 Nov 2007 16:48:55 -0800 Subject: Make test scripts executable. --- t/t4018-diff-funcname.sh | 0 t/t7501-commit.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 t/t4018-diff-funcname.sh mode change 100644 => 100755 t/t7501-commit.sh diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh old mode 100644 new mode 100755 diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh old mode 100644 new mode 100755 -- cgit v1.2.1 From 193f7e98da72f1012515b0389525cf9a6a269849 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 22 Nov 2007 16:52:17 -0800 Subject: Make test scripts executable. --- t/t9119-git-svn-info.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 t/t9119-git-svn-info.sh diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh old mode 100644 new mode 100755 -- cgit v1.2.1 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 --- t/t7501-commit.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 9dba104b1f..e601028d02 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 v1.2.1 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 --- builtin-tag.c | 5 ++++- strbuf.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin-tag.c b/builtin-tag.c index cbb0f04e85..88a5449e67 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 13919123dc..8334a9bad0 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 v1.2.1 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 --- .gitignore | 1 - Makefile | 9 +- builtin-commit.c | 614 ++++++++++++++++++++++++++++++++++++++++ builtin.h | 3 +- contrib/examples/git-commit.sh | 629 +++++++++++++++++++++++++++++++++++++++++ git-commit.sh | 629 ----------------------------------------- git.c | 3 +- 7 files changed, 1250 insertions(+), 638 deletions(-) create mode 100644 builtin-commit.c create mode 100755 contrib/examples/git-commit.sh delete mode 100755 git-commit.sh diff --git a/.gitignore b/.gitignore index c8c13f5503..bbd7f558e7 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 7a0ee780dc..35f9c873c4 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 0000000000..669cc6b6f1 --- /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 bcb54aaded..caea1a94e4 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 0000000000..485339754c --- /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 485339754c..0000000000 --- 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 80c2f14a8b..a5adc3487d 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 v1.2.1 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 --- builtin-tag.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/builtin-tag.c b/builtin-tag.c index 88a5449e67..566b9d186f 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 v1.2.1 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 --- builtin-commit.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 669cc6b6f1..c8f79a88fb 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 v1.2.1 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 --- builtin-commit.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-commit.c b/builtin-commit.c index c8f79a88fb..a84a729da1 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 v1.2.1 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 --- builtin-commit.c | 13 ++++---- builtin-runstatus.c | 1 + t/t7502-status.sh | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++ wt-status.c | 69 ++++++++++++++++++++++++++-------------- wt-status.h | 1 + 5 files changed, 146 insertions(+), 29 deletions(-) create mode 100755 t/t7502-status.sh diff --git a/builtin-commit.c b/builtin-commit.c index a84a729da1..400ee93a93 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 2db25c88bf..8d167a9674 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 0000000000..269b3341a2 --- /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 9a6ef4a89a..d3c10b8b8d 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 77449326db..f58ebcbb23 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 v1.2.1 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 --- builtin-commit.c | 30 +++++++++++++++++++----------- ident.c | 10 +++++++--- t/t7500-commit.sh | 12 ++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 400ee93a93..780eec79bd 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 9b2a852cb0..5be7533ffd 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 cf389b81da..49c1922dde 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 v1.2.1 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 --- builtin-commit.c | 5 ++++- t/t7500-commit.sh | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 780eec79bd..4dfa802758 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 49c1922dde..baed6ce96b 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 v1.2.1 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 --- builtin-commit.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 4dfa802758..ee79cf1b72 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 v1.2.1 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 --- builtin-commit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-commit.c b/builtin-commit.c index ee79cf1b72..2233300f40 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 v1.2.1 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 --- builtin-commit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-commit.c b/builtin-commit.c index 2233300f40..ee9fe72c61 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 v1.2.1 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 --- builtin-commit.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index ee9fe72c61..5e2257c961 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 v1.2.1 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 --- t/t7501-commit.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index e601028d02..ce83af3a02 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 v1.2.1 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 --- t/t3001-ls-files-others-exclude.sh | 2 +- t/t4001-diff-rename.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index ae0639d8f3..e25b255683 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 063e79257a..877c1ea5db 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 v1.2.1 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 --- dir.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dir.c b/dir.c index 225fdfb52c..11a4cf3e16 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 v1.2.1 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 --- builtin-commit.c | 5 ++++- wt-status.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 5e2257c961..7616dd152f 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 f58ebcbb23..225fb4d535 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 v1.2.1 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 --- builtin-commit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 7616dd152f..cd2f5cad1d 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 v1.2.1 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 --- builtin-ls-files.c | 98 +++++++++++++++++++++++++++++------------------------- cache.h | 6 ++++ 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 7f60709830..0f0ab2da16 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 33ebccf48d..26eec229eb 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 v1.2.1 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 --- builtin-add.c | 8 +++++--- cache.h | 4 +++- commit.h | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index cf815a0b8e..03508d3dcb 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 26eec229eb..cf0bdc674c 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 aa679867a9..f450aae8aa 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 v1.2.1 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 --- builtin-commit.c | 242 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 202 insertions(+), 40 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index cd2f5cad1d..e779db8ca3 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 v1.2.1 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 --- builtin-commit.c | 8 +++++++- wt-status.c | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index e779db8ca3..4de316a366 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 d3c10b8b8d..0e0439f2c2 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 v1.2.1 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 --- t/t7502-commit.sh | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100755 t/t7502-commit.sh diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh new file mode 100755 index 0000000000..21ac785e3d --- /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 v1.2.1