author | Junio C Hamano <gitster@pobox.com> | |
Wed, 10 Mar 2010 23:25:18 +0000 (15:25 -0800) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Wed, 10 Mar 2010 23:25:18 +0000 (15:25 -0800) |
* lt/deepen-builtin-source:
Move 'builtin-*' into a 'builtin/' subdirectory
Conflicts:
Makefile
Move 'builtin-*' into a 'builtin/' subdirectory
Conflicts:
Makefile
20 files changed:
diff --cc Makefile
index f64610a57bfea6176aa9f7d470b92014fe958432,f1025d5c036e142dea4e492b6ea4a165189d1b3d..cdc58fed7809c41a5b85162a3738ccca15720ab0
+++ b/Makefile
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
-PROGRAMS += git-fast-import$X
-PROGRAMS += git-imap-send$X
-PROGRAMS += git-shell$X
-PROGRAMS += git-show-index$X
-PROGRAMS += git-upload-pack$X
-PROGRAMS += git-http-backend$X
+
+PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += shell.o
+PROGRAM_OBJS += show-index.o
+PROGRAM_OBJS += upload-pack.o
+PROGRAM_OBJS += http-backend.o
+
+PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
+
+TEST_PROGRAMS_NEED_X += test-chmtime
+TEST_PROGRAMS_NEED_X += test-ctype
+TEST_PROGRAMS_NEED_X += test-date
+TEST_PROGRAMS_NEED_X += test-delta
+TEST_PROGRAMS_NEED_X += test-dump-cache-tree
+TEST_PROGRAMS_NEED_X += test-genrandom
+TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-parse-options
+TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-sha1
+TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-index-version
+
+TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
# List built-in command $C whose implementation cmd_$C() is not in
- # builtin-$C.o but is linked in as part of some other command.
- BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
+ # builtin/$C.o but is linked in as part of some other command.
+ BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS))
BUILT_INS += git-cherry$X
BUILT_INS += git-cherry-pick$X
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
: GIT-VERSION-FILE
-%.o: %.c GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
+ git.o http.o http-walker.o remote-curl.o
+XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
+ xdiff/xmerge.o xdiff/xpatience.o
+OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS)
+
+dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
+dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
+
+ifdef COMPUTE_HEADER_DEPENDENCIES
+$(dep_dirs):
+ mkdir -p $@
+
+missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
+dep_file = $(dir $@).depend/$(notdir $@).d
+dep_args = -MF $(dep_file) -MMD -MP
+ifdef CHECK_HEADER_DEPENDENCIES
+$(error cannot compute header dependencies outside a normal build. \
+Please unset CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef COMPUTE_HEADER_DEPENDENCIES
+ifndef CHECK_HEADER_DEPENDENCIES
+dep_dirs =
+missing_dep_dirs =
+dep_args =
+endif
+endif
+
+ifdef CHECK_HEADER_DEPENDENCIES
+ifndef PRINT_HEADER_DEPENDENCIES
+missing_deps = $(filter-out $(notdir $^), \
+ $(notdir $(shell $(MAKE) -s $@ \
+ CHECK_HEADER_DEPENDENCIES=YesPlease \
+ USE_COMPUTED_HEADER_DEPENDENCIES=YesPlease \
+ PRINT_HEADER_DEPENDENCIES=YesPlease)))
+endif
+endif
+
+ASM_SRC := $(wildcard $(OBJECTS:o=S))
+ASM_OBJ := $(ASM_SRC:S=o)
+C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
+
+.SUFFIXES:
+
+ifdef PRINT_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c FORCE
+ echo $^
+$(ASM_OBJ): %.o: %.S FORCE
+ echo $^
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(error cannot print header dependencies during a normal build. \
+Please set CHECK_HEADER_DEPENDENCIES and try again)
+endif
+endif
+
+ifndef PRINT_HEADER_DEPENDENCIES
+ifdef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c $(dep_files) FORCE
+ @set -e; echo CHECK $@; \
+ missing_deps="$(missing_deps)"; \
+ if test "$$missing_deps"; \
+ then \
+ echo missing dependencies: $$missing_deps; \
+ false; \
+ fi
+$(ASM_OBJ): %.o: %.S $(dep_files) FORCE
+ @set -e; echo CHECK $@; \
+ missing_deps="$(missing_deps)"; \
+ if test "$$missing_deps"; \
+ then \
+ echo missing dependencies: $$missing_deps; \
+ false; \
+ fi
+endif
+endif
+
+ifndef CHECK_HEADER_DEPENDENCIES
+$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $<
+$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $<
+endif
+
%.s: %.c GIT-CFLAGS FORCE
$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
-%.o: %.S GIT-CFLAGS
- $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+ifdef USE_COMPUTED_HEADER_DEPENDENCIES
+# Take advantage of gcc's on-the-fly dependency generation
+# See <http://gcc.gnu.org/gcc-3.0/features.html>.
+dep_files_present := $(wildcard $(dep_files))
+ifneq ($(dep_files_present),)
+include $(dep_files_present)
+endif
+else
+# Dependencies on header files, for platforms that do not support
+# the gcc -MMD option.
+#
+# Dependencies on automatically generated headers such as common-cmds.h
+# should _not_ be included here, since they are necessary even when
+# building an object for the first time.
+#
+# XXX. Please check occasionally that these include all dependencies
+# gcc detects!
+
+$(GIT_OBJS): $(LIB_H)
- builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o transport.o: branch.h
- builtin-bundle.o bundle.o transport.o: bundle.h
- builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h
- builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h
- builtin-grep.o: thread-utils.h
- builtin-send-pack.o transport.o: send-pack.h
- builtin-log.o builtin-shortlog.o: shortlog.h
- builtin-prune.o builtin-reflog.o reachable.o: reachable.h
- builtin-commit.o builtin-revert.o wt-status.o: wt-status.h
- builtin-tar-tree.o archive-tar.o: tar.h
- builtin-pack-objects.o: thread-utils.h
++builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o transport.o: branch.h
++builtin/bundle.o bundle.o transport.o: bundle.h
++builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
++builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
++builtin/grep.o: thread-utils.h
++builtin/send-pack.o transport.o: send-pack.h
++builtin/log.o builtin/shortlog.o: shortlog.h
++builtin/prune.o builtin/reflog.o reachable.o: reachable.h
++builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
++builtin/tar-tree.o archive-tar.o: tar.h
++builtin/pack-objects.o: thread-utils.h
+http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
+http.o http-walker.o http-push.o remote-curl.o: http.h
+
+xdiff-interface.o $(XDIFF_OBJS): \
+ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
+ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
+endif
exec_cmd.s exec_cmd.o: ALL_CFLAGS += \
'-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
diff --cc builtin/branch.c
index 0000000000000000000000000000000000000000,a28a13986d11ebfecd11a206d1b7a1fb626865db..6cf7e721e6b59f50c7a8018296aa49848deecda3
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/branch.c
+++ b/builtin/branch.c
- OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
+ /*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+ #include "cache.h"
+ #include "color.h"
+ #include "refs.h"
+ #include "commit.h"
+ #include "builtin.h"
+ #include "remote.h"
+ #include "parse-options.h"
+ #include "branch.h"
+ #include "diff.h"
+ #include "revision.h"
+
+ static const char * const builtin_branch_usage[] = {
+ "git branch [options] [-r | -a] [--merged | --no-merged]",
+ "git branch [options] [-l] [-f] <branchname> [<start-point>]",
+ "git branch [options] [-r] (-d | -D) <branchname>",
+ "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+ NULL
+ };
+
+ #define REF_LOCAL_BRANCH 0x01
+ #define REF_REMOTE_BRANCH 0x02
+
+ static const char *head;
+ static unsigned char head_sha1[20];
+
+ static int branch_use_color = -1;
+ static char branch_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RESET,
+ GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_RED, /* REMOTE */
+ GIT_COLOR_NORMAL, /* LOCAL */
+ GIT_COLOR_GREEN, /* CURRENT */
+ };
+ enum color_branch {
+ BRANCH_COLOR_RESET = 0,
+ BRANCH_COLOR_PLAIN = 1,
+ BRANCH_COLOR_REMOTE = 2,
+ BRANCH_COLOR_LOCAL = 3,
+ BRANCH_COLOR_CURRENT = 4,
+ };
+
+ static enum merge_filter {
+ NO_FILTER = 0,
+ SHOW_NOT_MERGED,
+ SHOW_MERGED,
+ } merge_filter;
+ static unsigned char merge_filter_ref[20];
+
+ static int parse_branch_color_slot(const char *var, int ofs)
+ {
+ if (!strcasecmp(var+ofs, "plain"))
+ return BRANCH_COLOR_PLAIN;
+ if (!strcasecmp(var+ofs, "reset"))
+ return BRANCH_COLOR_RESET;
+ if (!strcasecmp(var+ofs, "remote"))
+ return BRANCH_COLOR_REMOTE;
+ if (!strcasecmp(var+ofs, "local"))
+ return BRANCH_COLOR_LOCAL;
+ if (!strcasecmp(var+ofs, "current"))
+ return BRANCH_COLOR_CURRENT;
+ return -1;
+ }
+
+ static int git_branch_config(const char *var, const char *value, void *cb)
+ {
+ if (!strcmp(var, "color.branch")) {
+ branch_use_color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+ if (!prefixcmp(var, "color.branch.")) {
+ int slot = parse_branch_color_slot(var, 13);
+ if (slot < 0)
+ return 0;
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, branch_colors[slot]);
+ return 0;
+ }
+ return git_color_default_config(var, value, cb);
+ }
+
+ static const char *branch_get_color(enum color_branch ix)
+ {
+ if (branch_use_color > 0)
+ return branch_colors[ix];
+ return "";
+ }
+
+ static int branch_merged(int kind, const char *name,
+ struct commit *rev, struct commit *head_rev)
+ {
+ /*
+ * This checks whether the merge bases of branch and HEAD (or
+ * the other branch this branch builds upon) contains the
+ * branch, which means that the branch has already been merged
+ * safely to HEAD (or the other branch).
+ */
+ struct commit *reference_rev = NULL;
+ const char *reference_name = NULL;
+ int merged;
+
+ if (kind == REF_LOCAL_BRANCH) {
+ struct branch *branch = branch_get(name);
+ unsigned char sha1[20];
+
+ if (branch &&
+ branch->merge &&
+ branch->merge[0] &&
+ branch->merge[0]->dst &&
+ (reference_name =
+ resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ reference_rev = lookup_commit_reference(sha1);
+ }
+ if (!reference_rev)
+ reference_rev = head_rev;
+
+ merged = in_merge_bases(rev, &reference_rev, 1);
+
+ /*
+ * After the safety valve is fully redefined to "check with
+ * upstream, if any, otherwise with HEAD", we should just
+ * return the result of the in_merge_bases() above without
+ * any of the following code, but during the transition period,
+ * a gentle reminder is in order.
+ */
+ if ((head_rev != reference_rev) &&
+ in_merge_bases(rev, &head_rev, 1) != merged) {
+ if (merged)
+ warning("deleting branch '%s' that has been merged to\n"
+ " '%s', but it is not yet merged to HEAD.",
+ name, reference_name);
+ else
+ warning("not deleting branch '%s' that is not yet merged to\n"
+ " '%s', even though it is merged to HEAD.",
+ name, reference_name);
+ }
+ return merged;
+ }
+
+ static int delete_branches(int argc, const char **argv, int force, int kinds)
+ {
+ struct commit *rev, *head_rev = NULL;
+ unsigned char sha1[20];
+ char *name = NULL;
+ const char *fmt, *remote;
+ int i;
+ int ret = 0;
+ struct strbuf bname = STRBUF_INIT;
+
+ switch (kinds) {
+ case REF_REMOTE_BRANCH:
+ fmt = "refs/remotes/%s";
+ remote = "remote ";
+ force = 1;
+ break;
+ case REF_LOCAL_BRANCH:
+ fmt = "refs/heads/%s";
+ remote = "";
+ break;
+ default:
+ die("cannot use -a with -d");
+ }
+
+ if (!force) {
+ head_rev = lookup_commit_reference(head_sha1);
+ if (!head_rev)
+ die("Couldn't look up commit object for HEAD");
+ }
+ for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+ strbuf_branchname(&bname, argv[i]);
+ if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
+ error("Cannot delete the branch '%s' "
+ "which you are currently on.", bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ free(name);
+
+ name = xstrdup(mkpath(fmt, bname.buf));
+ if (!resolve_ref(name, sha1, 1, NULL)) {
+ error("%sbranch '%s' not found.",
+ remote, bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ rev = lookup_commit_reference(sha1);
+ if (!rev) {
+ error("Couldn't look up commit object for '%s'", name);
+ ret = 1;
+ continue;
+ }
+
+ if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
+ error("The branch '%s' is not fully merged.\n"
+ "If you are sure you want to delete it, "
+ "run 'git branch -D %s'.", bname.buf, bname.buf);
+ ret = 1;
+ continue;
+ }
+
+ if (delete_ref(name, sha1, 0)) {
+ error("Error deleting %sbranch '%s'", remote,
+ bname.buf);
+ ret = 1;
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ printf("Deleted %sbranch %s (was %s).\n", remote,
+ bname.buf,
+ find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ strbuf_addf(&buf, "branch.%s", bname.buf);
+ if (git_config_rename_section(buf.buf, NULL) < 0)
+ warning("Update of config-file failed");
+ strbuf_release(&buf);
+ }
+ }
+
+ free(name);
+
+ return(ret);
+ }
+
+ struct ref_item {
+ char *name;
+ char *dest;
+ unsigned int kind, len;
+ struct commit *commit;
+ };
+
+ struct ref_list {
+ struct rev_info revs;
+ int index, alloc, maxwidth, verbose, abbrev;
+ struct ref_item *list;
+ struct commit_list *with_commit;
+ int kinds;
+ };
+
+ static char *resolve_symref(const char *src, const char *prefix)
+ {
+ unsigned char sha1[20];
+ int flag;
+ const char *dst, *cp;
+
+ dst = resolve_ref(src, sha1, 0, &flag);
+ if (!(dst && (flag & REF_ISSYMREF)))
+ return NULL;
+ if (prefix && (cp = skip_prefix(dst, prefix)))
+ dst = cp;
+ return xstrdup(dst);
+ }
+
+ static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+ {
+ struct ref_list *ref_list = (struct ref_list*)(cb_data);
+ struct ref_item *newitem;
+ struct commit *commit;
+ int kind, i;
+ const char *prefix, *orig_refname = refname;
+
+ static struct {
+ int kind;
+ const char *prefix;
+ int pfxlen;
+ } ref_kind[] = {
+ { REF_LOCAL_BRANCH, "refs/heads/", 11 },
+ { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+ };
+
+ /* Detect kind */
+ for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+ prefix = ref_kind[i].prefix;
+ if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+ continue;
+ kind = ref_kind[i].kind;
+ refname += ref_kind[i].pfxlen;
+ break;
+ }
+ if (ARRAY_SIZE(ref_kind) <= i)
+ return 0;
+
+ /* Don't add types the caller doesn't want */
+ if ((kind & ref_list->kinds) == 0)
+ return 0;
+
+ commit = NULL;
+ if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return error("branch '%s' does not point at a commit", refname);
+
+ /* Filter with with_commit if specified */
+ if (!is_descendant_of(commit, ref_list->with_commit))
+ return 0;
+
+ if (merge_filter != NO_FILTER)
+ add_pending_object(&ref_list->revs,
+ (struct object *)commit, refname);
+ }
+
+ /* Resize buffer */
+ if (ref_list->index >= ref_list->alloc) {
+ ref_list->alloc = alloc_nr(ref_list->alloc);
+ ref_list->list = xrealloc(ref_list->list,
+ ref_list->alloc * sizeof(struct ref_item));
+ }
+
+ /* Record the new item */
+ newitem = &(ref_list->list[ref_list->index++]);
+ newitem->name = xstrdup(refname);
+ newitem->kind = kind;
+ newitem->commit = commit;
+ newitem->len = strlen(refname);
+ newitem->dest = resolve_symref(orig_refname, prefix);
+ /* adjust for "remotes/" */
+ if (newitem->kind == REF_REMOTE_BRANCH &&
+ ref_list->kinds != REF_REMOTE_BRANCH)
+ newitem->len += 8;
+ if (newitem->len > ref_list->maxwidth)
+ ref_list->maxwidth = newitem->len;
+
+ return 0;
+ }
+
+ static void free_ref_list(struct ref_list *ref_list)
+ {
+ int i;
+
+ for (i = 0; i < ref_list->index; i++) {
+ free(ref_list->list[i].name);
+ free(ref_list->list[i].dest);
+ }
+ free(ref_list->list);
+ }
+
+ static int ref_cmp(const void *r1, const void *r2)
+ {
+ struct ref_item *c1 = (struct ref_item *)(r1);
+ struct ref_item *c2 = (struct ref_item *)(r2);
+
+ if (c1->kind != c2->kind)
+ return c1->kind - c2->kind;
+ return strcmp(c1->name, c2->name);
+ }
+
+ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
+ int show_upstream_ref)
+ {
+ int ours, theirs;
+ struct branch *branch = branch_get(branch_name);
+
+ if (!stat_tracking_info(branch, &ours, &theirs)) {
+ if (branch && branch->merge && branch->merge[0]->dst &&
+ show_upstream_ref)
+ strbuf_addf(stat, "[%s] ",
+ shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+ return;
+ }
+
+ strbuf_addch(stat, '[');
+ if (show_upstream_ref)
+ strbuf_addf(stat, "%s: ",
+ shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+ if (!ours)
+ strbuf_addf(stat, "behind %d] ", theirs);
+ else if (!theirs)
+ strbuf_addf(stat, "ahead %d] ", ours);
+ else
+ strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
+ }
+
+ static int matches_merge_filter(struct commit *commit)
+ {
+ int is_merged;
+
+ if (merge_filter == NO_FILTER)
+ return 1;
+
+ is_merged = !!(commit->object.flags & UNINTERESTING);
+ return (is_merged == (merge_filter == SHOW_MERGED));
+ }
+
+ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
+ int abbrev, int current, char *prefix)
+ {
+ char c;
+ int color;
+ struct commit *commit = item->commit;
+ struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
+
+ if (!matches_merge_filter(commit))
+ return;
+
+ switch (item->kind) {
+ case REF_LOCAL_BRANCH:
+ color = BRANCH_COLOR_LOCAL;
+ break;
+ case REF_REMOTE_BRANCH:
+ color = BRANCH_COLOR_REMOTE;
+ break;
+ default:
+ color = BRANCH_COLOR_PLAIN;
+ break;
+ }
+
+ c = ' ';
+ if (current) {
+ c = '*';
+ color = BRANCH_COLOR_CURRENT;
+ }
+
+ strbuf_addf(&name, "%s%s", prefix, item->name);
+ if (verbose)
+ strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
+ maxwidth, name.buf,
+ branch_get_color(BRANCH_COLOR_RESET));
+ else
+ strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
+ name.buf, branch_get_color(BRANCH_COLOR_RESET));
+
+ if (item->dest)
+ strbuf_addf(&out, " -> %s", item->dest);
+ else if (verbose) {
+ struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
+ const char *sub = " **** invalid ref ****";
+
+ commit = item->commit;
+ if (commit && !parse_commit(commit)) {
+ struct pretty_print_context ctx = {0};
+ pretty_print_commit(CMIT_FMT_ONELINE, commit,
+ &subject, &ctx);
+ sub = subject.buf;
+ }
+
+ if (item->kind == REF_LOCAL_BRANCH)
+ fill_tracking_info(&stat, item->name, verbose > 1);
+
+ strbuf_addf(&out, " %s %s%s",
+ find_unique_abbrev(item->commit->object.sha1, abbrev),
+ stat.buf, sub);
+ strbuf_release(&stat);
+ strbuf_release(&subject);
+ }
+ printf("%s\n", out.buf);
+ strbuf_release(&name);
+ strbuf_release(&out);
+ }
+
+ static int calc_maxwidth(struct ref_list *refs)
+ {
+ int i, w = 0;
+ for (i = 0; i < refs->index; i++) {
+ if (!matches_merge_filter(refs->list[i].commit))
+ continue;
+ if (refs->list[i].len > w)
+ w = refs->list[i].len;
+ }
+ return w;
+ }
+
+
+ static void show_detached(struct ref_list *ref_list)
+ {
+ struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
+
+ if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
+ struct ref_item item;
+ item.name = xstrdup("(no branch)");
+ item.len = strlen(item.name);
+ item.kind = REF_LOCAL_BRANCH;
+ item.dest = NULL;
+ item.commit = head_commit;
+ if (item.len > ref_list->maxwidth)
+ ref_list->maxwidth = item.len;
+ print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
+ free(item.name);
+ }
+ }
+
+ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+ {
+ int i;
+ struct ref_list ref_list;
+
+ memset(&ref_list, 0, sizeof(ref_list));
+ ref_list.kinds = kinds;
+ ref_list.verbose = verbose;
+ ref_list.abbrev = abbrev;
+ ref_list.with_commit = with_commit;
+ if (merge_filter != NO_FILTER)
+ init_revisions(&ref_list.revs, NULL);
+ for_each_rawref(append_ref, &ref_list);
+ if (merge_filter != NO_FILTER) {
+ struct commit *filter;
+ filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+ filter->object.flags |= UNINTERESTING;
+ add_pending_object(&ref_list.revs,
+ (struct object *) filter, "");
+ ref_list.revs.limited = 1;
+ prepare_revision_walk(&ref_list.revs);
+ if (verbose)
+ ref_list.maxwidth = calc_maxwidth(&ref_list);
+ }
+
+ qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
+
+ detached = (detached && (kinds & REF_LOCAL_BRANCH));
+ if (detached)
+ show_detached(&ref_list);
+
+ for (i = 0; i < ref_list.index; i++) {
+ int current = !detached &&
+ (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
+ !strcmp(ref_list.list[i].name, head);
+ char *prefix = (kinds != REF_REMOTE_BRANCH &&
+ ref_list.list[i].kind == REF_REMOTE_BRANCH)
+ ? "remotes/" : "";
+ print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
+ abbrev, current, prefix);
+ }
+
+ free_ref_list(&ref_list);
+ }
+
+ static void rename_branch(const char *oldname, const char *newname, int force)
+ {
+ struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
+ unsigned char sha1[20];
+ struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+ int recovery = 0;
+
+ if (!oldname)
+ die("cannot rename the current branch while not on any.");
+
+ if (strbuf_check_branch_ref(&oldref, oldname)) {
+ /*
+ * Bad name --- this could be an attempt to rename a
+ * ref that we used to allow to be created by accident.
+ */
+ if (resolve_ref(oldref.buf, sha1, 1, NULL))
+ recovery = 1;
+ else
+ die("Invalid branch name: '%s'", oldname);
+ }
+
+ if (strbuf_check_branch_ref(&newref, newname))
+ die("Invalid branch name: '%s'", newname);
+
+ if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
+ die("A branch named '%s' already exists.", newref.buf + 11);
+
+ strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+ oldref.buf, newref.buf);
+
+ if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ die("Branch rename failed");
+ strbuf_release(&logmsg);
+
+ if (recovery)
+ warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
+
+ /* no need to pass logmsg here as HEAD didn't really move */
+ if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+ die("Branch renamed to %s, but HEAD is not updated!", newname);
+
+ strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
+ strbuf_release(&oldref);
+ strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
+ strbuf_release(&newref);
+ if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+ die("Branch is renamed, but update of config-file failed");
+ strbuf_release(&oldsection);
+ strbuf_release(&newsection);
+ }
+
+ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
+ {
+ merge_filter = ((opt->long_name[0] == 'n')
+ ? SHOW_NOT_MERGED
+ : SHOW_MERGED);
+ if (unset)
+ merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
+ if (!arg)
+ arg = "HEAD";
+ if (get_sha1(arg, merge_filter_ref))
+ die("malformed object name %s", arg);
+ return 0;
+ }
+
+ int cmd_branch(int argc, const char **argv, const char *prefix)
+ {
+ int delete = 0, rename = 0, force_create = 0;
+ int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
+ int reflog = 0;
+ enum branch_track track;
+ int kinds = REF_LOCAL_BRANCH;
+ struct commit_list *with_commit = NULL;
+
+ struct option options[] = {
+ OPT_GROUP("Generic options"),
+ OPT__VERBOSE(&verbose),
+ OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
+ BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
+ BRANCH_TRACK_OVERRIDE),
++ OPT__COLOR(&branch_use_color, "use colored output"),
+ OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
+ REF_REMOTE_BRANCH),
+ {
+ OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
+ "print only branches that contain the commit",
+ PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t)"HEAD",
+ },
+ {
+ OPTION_CALLBACK, 0, "with", &with_commit, "commit",
+ "print only branches that contain the commit",
+ PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t) "HEAD",
+ },
+ OPT__ABBREV(&abbrev),
+
+ OPT_GROUP("Specific git-branch actions:"),
+ OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+ REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+ OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+ OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
+ OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+ OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
+ OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
+ OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"),
+ {
+ OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
+ "commit", "print only not merged branches",
+ PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+ opt_parse_merge_filter, (intptr_t) "HEAD",
+ },
+ {
+ OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
+ "commit", "print only merged branches",
+ PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
+ opt_parse_merge_filter, (intptr_t) "HEAD",
+ },
+ OPT_END(),
+ };
+
+ git_config(git_branch_config, NULL);
+
+ if (branch_use_color == -1)
+ branch_use_color = git_use_color_default;
+
+ track = git_branch_track;
+
+ head = resolve_ref("HEAD", head_sha1, 0, NULL);
+ if (!head)
+ die("Failed to resolve HEAD as a valid ref.");
+ head = xstrdup(head);
+ if (!strcmp(head, "HEAD")) {
+ detached = 1;
+ } else {
+ if (prefixcmp(head, "refs/heads/"))
+ die("HEAD not found below refs/heads!");
+ head += 11;
+ }
+ hashcpy(merge_filter_ref, head_sha1);
+
+ argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
+ 0);
+ if (!!delete + !!rename + !!force_create > 1)
+ usage_with_options(builtin_branch_usage, options);
+
+ if (delete)
+ return delete_branches(argc, argv, delete > 1, kinds);
+ else if (argc == 0)
+ print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+ else if (rename && (argc == 1))
+ rename_branch(head, argv[0], rename > 1);
+ else if (rename && (argc == 2))
+ rename_branch(argv[0], argv[1], rename > 1);
+ else if (argc <= 2) {
+ if (kinds != REF_LOCAL_BRANCH)
+ die("-a and -r options to 'git branch' do not make sense with a branch name");
+ create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
+ force_create, reflog, track);
+ } else
+ usage_with_options(builtin_branch_usage, options);
+
+ return 0;
+ }
diff --cc builtin/checkout.c
index 0000000000000000000000000000000000000000,c5ab7835e1fe260b9ec61f216bf198011eabf6f7..acefaaf41a4e26e45225f6d228734c93c8f2b23c
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/checkout.c
+++ b/builtin/checkout.c
-/* NEEDSWORK: share with merge-recursive */
-static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
-{
- unsigned long size;
- enum object_type type;
-
- if (!hashcmp(sha1, null_sha1)) {
- mm->ptr = xstrdup("");
- mm->size = 0;
- return;
- }
-
- mm->ptr = read_sha1_file(sha1, &type, &size);
- if (!mm->ptr || type != OBJ_BLOB)
- die("unable to read blob object %s", sha1_to_hex(sha1));
- mm->size = size;
-}
-
+ #include "cache.h"
+ #include "builtin.h"
+ #include "parse-options.h"
+ #include "refs.h"
+ #include "commit.h"
+ #include "tree.h"
+ #include "tree-walk.h"
+ #include "cache-tree.h"
+ #include "unpack-trees.h"
+ #include "dir.h"
+ #include "run-command.h"
+ #include "merge-recursive.h"
+ #include "branch.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "remote.h"
+ #include "blob.h"
+ #include "xdiff-interface.h"
+ #include "ll-merge.h"
+ #include "resolve-undo.h"
+
+ static const char * const checkout_usage[] = {
+ "git checkout [options] <branch>",
+ "git checkout [options] [<branch>] -- <file>...",
+ NULL,
+ };
+
+ struct checkout_opts {
+ int quiet;
+ int merge;
+ int force;
+ int writeout_stage;
+ int writeout_error;
+
+ const char *new_branch;
+ int new_branch_log;
+ enum branch_track track;
+ };
+
+ static int post_checkout_hook(struct commit *old, struct commit *new,
+ int changed)
+ {
+ return run_hook(NULL, "post-checkout",
+ sha1_to_hex(old ? old->object.sha1 : null_sha1),
+ sha1_to_hex(new ? new->object.sha1 : null_sha1),
+ changed ? "1" : "0", NULL);
+ /* "new" can be NULL when checking out from the index before
+ a commit exists. */
+
+ }
+
+ static int update_some(const unsigned char *sha1, const char *base, int baselen,
+ const char *pathname, unsigned mode, int stage, void *context)
+ {
+ int len;
+ struct cache_entry *ce;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ len = baselen + strlen(pathname);
+ ce = xcalloc(1, cache_entry_size(len));
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, base, baselen);
+ memcpy(ce->name + baselen, pathname, len - baselen);
+ ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_mode = create_ce_mode(mode);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ return 0;
+ }
+
+ static int read_tree_some(struct tree *tree, const char **pathspec)
+ {
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
+
+ /* update the index with the given tree's info
+ * for all args, expanding wildcards, and exit
+ * with any non-zero return code.
+ */
+ return 0;
+ }
+
+ static int skip_same_name(struct cache_entry *ce, int pos)
+ {
+ while (++pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name))
+ ; /* skip */
+ return pos;
+ }
+
+ static int check_stage(int stage, struct cache_entry *ce, int pos)
+ {
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return 0;
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+ }
+
+ static int check_all_stages(struct cache_entry *ce, int pos)
+ {
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, ce->name) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, ce->name) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all three versions",
+ ce->name);
+ return 0;
+ }
+
+ static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+ struct checkout *state)
+ {
+ while (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, ce->name)) {
+ if (ce_stage(active_cache[pos]) == stage)
+ return checkout_entry(active_cache[pos], state, NULL);
+ pos++;
+ }
+ return error("path '%s' does not have %s version",
+ ce->name,
+ (stage == 2) ? "our" : "their");
+ }
+
- fill_mm(active_cache[pos]->sha1, &ancestor);
- fill_mm(active_cache[pos+1]->sha1, &ours);
- fill_mm(active_cache[pos+2]->sha1, &theirs);
+ static int checkout_merged(int pos, struct checkout *state)
+ {
+ struct cache_entry *ce = active_cache[pos];
+ const char *path = ce->name;
+ mmfile_t ancestor, ours, theirs;
+ int status;
+ unsigned char sha1[20];
+ mmbuffer_t result_buf;
+
+ if (ce_stage(ce) != 1 ||
+ active_nr <= pos + 2 ||
+ strcmp(active_cache[pos+1]->name, path) ||
+ ce_stage(active_cache[pos+1]) != 2 ||
+ strcmp(active_cache[pos+2]->name, path) ||
+ ce_stage(active_cache[pos+2]) != 3)
+ return error("path '%s' does not have all 3 versions", path);
+
++ read_mmblob(&ancestor, active_cache[pos]->sha1);
++ read_mmblob(&ours, active_cache[pos+1]->sha1);
++ read_mmblob(&theirs, active_cache[pos+2]->sha1);
+
+ status = ll_merge(&result_buf, path, &ancestor,
+ &ours, "ours", &theirs, "theirs", 0);
+ free(ancestor.ptr);
+ free(ours.ptr);
+ free(theirs.ptr);
+ if (status < 0 || !result_buf.ptr) {
+ free(result_buf.ptr);
+ return error("path '%s': cannot merge", path);
+ }
+
+ /*
+ * NEEDSWORK:
+ * There is absolutely no reason to write this as a blob object
+ * and create a phony cache entry just to leak. This hack is
+ * primarily to get to the write_entry() machinery that massages
+ * the contents to work-tree format and writes out which only
+ * allows it for a cache entry. The code in write_entry() needs
+ * to be refactored to allow us to feed a <buffer, size, mode>
+ * instead of a cache entry. Such a refactoring would help
+ * merge_recursive as well (it also writes the merge result to the
+ * object database even when it may contain conflicts).
+ */
+ if (write_sha1_file(result_buf.ptr, result_buf.size,
+ blob_type, sha1))
+ die("Unable to add merge result for '%s'", path);
+ ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
+ sha1,
+ path, 2, 0);
+ if (!ce)
+ die("make_cache_entry failed for path '%s'", path);
+ status = checkout_entry(ce, state, NULL);
+ return status;
+ }
+
+ static int checkout_paths(struct tree *source_tree, const char **pathspec,
+ struct checkout_opts *opts)
+ {
+ int pos;
+ struct checkout state;
+ static char *ps_matched;
+ unsigned char rev[20];
+ int flag;
+ struct commit *head;
+ int errs = 0;
+ int stage = opts->writeout_stage;
+ int merge = opts->merge;
+ int newfd;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ newfd = hold_locked_index(lock_file, 1);
+ if (read_cache_preload(pathspec) < 0)
+ return error("corrupt index file");
+
+ if (source_tree)
+ read_tree_some(source_tree, pathspec);
+
+ for (pos = 0; pathspec[pos]; pos++)
+ ;
+ ps_matched = xcalloc(1, pos);
+
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
+ }
+
+ if (report_path_error(ps_matched, pathspec, 0))
+ return 1;
+
+ /* "checkout -m path" to recreate conflicted state */
+ if (opts->merge)
+ unmerge_cache(pathspec);
+
+ /* Any unmerged paths? */
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ if (!ce_stage(ce))
+ continue;
+ if (opts->force) {
+ warning("path '%s' is unmerged", ce->name);
+ } else if (stage) {
+ errs |= check_stage(stage, ce, pos);
+ } else if (opts->merge) {
+ errs |= check_all_stages(ce, pos);
+ } else {
+ errs = 1;
+ error("path '%s' is unmerged", ce->name);
+ }
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+ if (errs)
+ return 1;
+
+ /* Now we are committed to check them out */
+ memset(&state, 0, sizeof(state));
+ state.force = 1;
+ state.refresh_cache = 1;
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ if (!ce_stage(ce)) {
+ errs |= checkout_entry(ce, &state, NULL);
+ continue;
+ }
+ if (stage)
+ errs |= checkout_stage(stage, ce, pos, &state);
+ else if (merge)
+ errs |= checkout_merged(pos, &state);
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ resolve_ref("HEAD", rev, 0, &flag);
+ head = lookup_commit_reference_gently(rev, 1);
+
+ errs |= post_checkout_hook(head, head, 0);
+ return errs;
+ }
+
+ static void show_local_changes(struct object *head)
+ {
+ struct rev_info rev;
+ /* I think we want full paths, even if we're in a subdirectory. */
+ init_revisions(&rev, NULL);
+ rev.abbrev = 0;
+ rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+ if (diff_setup_done(&rev.diffopt) < 0)
+ die("diff_setup_done failed");
+ add_pending_object(&rev, head, NULL);
+ run_diff_index(&rev, 0);
+ }
+
+ static void describe_detached_head(char *msg, struct commit *commit)
+ {
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ parse_commit(commit);
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
+ fprintf(stderr, "%s %s... %s\n", msg,
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+ strbuf_release(&sb);
+ }
+
+ static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
+ {
+ struct unpack_trees_options opts;
+ struct tree_desc tree_desc;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = -1;
+ opts.update = worktree;
+ opts.skip_unmerged = !worktree;
+ opts.reset = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = !o->quiet;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ parse_tree(tree);
+ init_tree_desc(&tree_desc, tree->buffer, tree->size);
+ switch (unpack_trees(1, &tree_desc, &opts)) {
+ case -2:
+ o->writeout_error = 1;
+ /*
+ * We return 0 nevertheless, as the index is all right
+ * and more importantly we have made best efforts to
+ * update paths in the work tree, and we cannot revert
+ * them.
+ */
+ case 0:
+ return 0;
+ default:
+ return 128;
+ }
+ }
+
+ struct branch_info {
+ const char *name; /* The short name used */
+ const char *path; /* The full name of a real branch */
+ struct commit *commit; /* The named commit */
+ };
+
+ static void setup_branch_path(struct branch_info *branch)
+ {
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_branchname(&buf, branch->name);
+ if (strcmp(buf.buf, branch->name))
+ branch->name = xstrdup(buf.buf);
+ strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+ branch->path = strbuf_detach(&buf, NULL);
+ }
+
+ static int merge_working_tree(struct checkout_opts *opts,
+ struct branch_info *old, struct branch_info *new)
+ {
+ int ret;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ int newfd = hold_locked_index(lock_file, 1);
+
+ if (read_cache_preload(NULL) < 0)
+ return error("corrupt index file");
+
+ resolve_undo_clear();
+ if (opts->force) {
+ ret = reset_tree(new->commit->tree, opts, 1);
+ if (ret)
+ return ret;
+ } else {
+ struct tree_desc trees[2];
+ struct tree *tree;
+ struct unpack_trees_options topts;
+
+ memset(&topts, 0, sizeof(topts));
+ topts.head_idx = -1;
+ topts.src_index = &the_index;
+ topts.dst_index = &the_index;
+
+ topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
+
+ refresh_cache(REFRESH_QUIET);
+
+ if (unmerged_cache()) {
+ error("you need to resolve your current index first");
+ return 1;
+ }
+
+ /* 2-way merge to the new branch */
+ topts.initial_checkout = is_cache_unborn();
+ topts.update = 1;
+ topts.merge = 1;
+ topts.gently = opts->merge && old->commit;
+ topts.verbose_update = !opts->quiet;
+ topts.fn = twoway_merge;
+ topts.dir = xcalloc(1, sizeof(*topts.dir));
+ topts.dir->flags |= DIR_SHOW_IGNORED;
+ topts.dir->exclude_per_dir = ".gitignore";
+ tree = parse_tree_indirect(old->commit ?
+ old->commit->object.sha1 :
+ (unsigned char *)EMPTY_TREE_SHA1_BIN);
+ init_tree_desc(&trees[0], tree->buffer, tree->size);
+ tree = parse_tree_indirect(new->commit->object.sha1);
+ init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+ ret = unpack_trees(2, trees, &topts);
+ if (ret == -1) {
+ /*
+ * Unpack couldn't do a trivial merge; either
+ * give up or do a real merge, depending on
+ * whether the merge flag was used.
+ */
+ struct tree *result;
+ struct tree *work;
+ struct merge_options o;
+ if (!opts->merge)
+ return 1;
+
+ /*
+ * Without old->commit, the below is the same as
+ * the two-tree unpack we already tried and failed.
+ */
+ if (!old->commit)
+ return 1;
+
+ /* Do more real merge */
+
+ /*
+ * We update the index fully, then write the
+ * tree from the index, then merge the new
+ * branch with the current tree, with the old
+ * branch as the base. Then we reset the index
+ * (but not the working tree) to the new
+ * branch, leaving the working tree as the
+ * merged version, but skipping unmerged
+ * entries in the index.
+ */
+
+ add_files_to_cache(NULL, NULL, 0);
+ init_merge_options(&o);
+ o.verbosity = 0;
+ work = write_tree_from_memory(&o);
+
+ ret = reset_tree(new->commit->tree, opts, 1);
+ if (ret)
+ return ret;
+ o.branch1 = new->name;
+ o.branch2 = "local";
+ merge_trees(&o, new->commit->tree, work,
+ old->commit->tree, &result);
+ ret = reset_tree(new->commit->tree, opts, 0);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ if (!opts->force && !opts->quiet)
+ show_local_changes(&new->commit->object);
+
+ return 0;
+ }
+
+ static void report_tracking(struct branch_info *new)
+ {
+ struct strbuf sb = STRBUF_INIT;
+ struct branch *branch = branch_get(new->name);
+
+ if (!format_tracking_info(branch, &sb))
+ return;
+ fputs(sb.buf, stdout);
+ strbuf_release(&sb);
+ }
+
+ static void detach_advice(const char *old_path, const char *new_name)
+ {
+ const char fmt[] =
+ "Note: checking out '%s'.\n\n"
+ "You are in 'detached HEAD' state. You can look around, make experimental\n"
+ "changes and commit them, and you can discard any commits you make in this\n"
+ "state without impacting any branches by performing another checkout.\n\n"
+ "If you want to create a new branch to retain commits you create, you may\n"
+ "do so (now or later) by using -b with the checkout command again. Example:\n\n"
+ " git checkout -b new_branch_name\n\n";
+
+ fprintf(stderr, fmt, new_name);
+ }
+
+ static void update_refs_for_switch(struct checkout_opts *opts,
+ struct branch_info *old,
+ struct branch_info *new)
+ {
+ struct strbuf msg = STRBUF_INIT;
+ const char *old_desc;
+ if (opts->new_branch) {
+ create_branch(old->name, opts->new_branch, new->name, 0,
+ opts->new_branch_log, opts->track);
+ new->name = opts->new_branch;
+ setup_branch_path(new);
+ }
+
+ old_desc = old->name;
+ if (!old_desc && old->commit)
+ old_desc = sha1_to_hex(old->commit->object.sha1);
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc ? old_desc : "(invalid)", new->name);
+
+ if (new->path) {
+ create_symref("HEAD", new->path, msg.buf);
+ if (!opts->quiet) {
+ if (old->path && !strcmp(new->path, old->path))
+ fprintf(stderr, "Already on '%s'\n",
+ new->name);
+ else
+ fprintf(stderr, "Switched to%s branch '%s'\n",
+ opts->new_branch ? " a new" : "",
+ new->name);
+ }
+ } else if (strcmp(new->name, "HEAD")) {
+ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+ REF_NODEREF, DIE_ON_ERR);
+ if (!opts->quiet) {
+ if (old->path && advice_detached_head)
+ detach_advice(old->path, new->name);
+ describe_detached_head("HEAD is now at", new->commit);
+ }
+ }
+ remove_branch_state();
+ strbuf_release(&msg);
+ if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ report_tracking(new);
+ }
+
+ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+ {
+ int ret = 0;
+ struct branch_info old;
+ unsigned char rev[20];
+ int flag;
+ memset(&old, 0, sizeof(old));
+ old.path = resolve_ref("HEAD", rev, 0, &flag);
+ old.commit = lookup_commit_reference_gently(rev, 1);
+ if (!(flag & REF_ISSYMREF))
+ old.path = NULL;
+
+ if (old.path && !prefixcmp(old.path, "refs/heads/"))
+ old.name = old.path + strlen("refs/heads/");
+
+ if (!new->name) {
+ new->name = "HEAD";
+ new->commit = old.commit;
+ if (!new->commit)
+ die("You are on a branch yet to be born");
+ parse_commit(new->commit);
+ }
+
+ ret = merge_working_tree(opts, &old, new);
+ if (ret)
+ return ret;
+
+ /*
+ * If we were on a detached HEAD, but have now moved to
+ * a new commit, we want to mention the old commit once more
+ * to remind the user that it might be lost.
+ */
+ if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+ describe_detached_head("Previous HEAD position was", old.commit);
+
+ update_refs_for_switch(opts, &old, new);
+
+ ret = post_checkout_hook(old.commit, new->commit, 1);
+ return ret || opts->writeout_error;
+ }
+
+ static int git_checkout_config(const char *var, const char *value, void *cb)
+ {
+ return git_xmerge_config(var, value, cb);
+ }
+
+ static int interactive_checkout(const char *revision, const char **pathspec,
+ struct checkout_opts *opts)
+ {
+ return run_add_interactive(revision, "--patch=checkout", pathspec);
+ }
+
+ struct tracking_name_data {
+ const char *name;
+ char *remote;
+ int unique;
+ };
+
+ static int check_tracking_name(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+ {
+ struct tracking_name_data *cb = cb_data;
+ const char *slash;
+
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ slash = strchr(refname + 13, '/');
+ if (!slash || strcmp(slash + 1, cb->name))
+ return 0;
+ if (cb->remote) {
+ cb->unique = 0;
+ return 0;
+ }
+ cb->remote = xstrdup(refname);
+ return 0;
+ }
+
+ static const char *unique_tracking_name(const char *name)
+ {
+ struct tracking_name_data cb_data = { name, NULL, 1 };
+ for_each_ref(check_tracking_name, &cb_data);
+ if (cb_data.unique)
+ return cb_data.remote;
+ free(cb_data.remote);
+ return NULL;
+ }
+
+ int cmd_checkout(int argc, const char **argv, const char *prefix)
+ {
+ struct checkout_opts opts;
+ unsigned char rev[20];
+ const char *arg;
+ struct branch_info new;
+ struct tree *source_tree = NULL;
+ char *conflict_style = NULL;
+ int patch_mode = 0;
+ int dwim_new_local_branch = 1;
+ struct option options[] = {
+ OPT__QUIET(&opts.quiet),
+ OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
+ OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
+ OPT_SET_INT('t', "track", &opts.track, "track",
+ BRANCH_TRACK_EXPLICIT),
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+ 2),
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+ 3),
+ OPT_BOOLEAN('f', "force", &opts.force, "force"),
+ OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+ OPT_STRING(0, "conflict", &conflict_style, "style",
+ "conflict style (merge or diff3)"),
+ OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+ { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
+ "second guess 'git checkout no-such-branch'",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_END(),
+ };
+ int has_dash_dash;
+
+ memset(&opts, 0, sizeof(opts));
+ memset(&new, 0, sizeof(new));
+
+ git_config(git_checkout_config, NULL);
+
+ opts.track = BRANCH_TRACK_UNSPECIFIED;
+
+ argc = parse_options(argc, argv, prefix, options, checkout_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (patch_mode && (opts.track > 0 || opts.new_branch
+ || opts.new_branch_log || opts.merge || opts.force))
+ die ("--patch is incompatible with all other options");
+
+ /* --track without -b should DWIM */
+ if (0 < opts.track && !opts.new_branch) {
+ const char *argv0 = argv[0];
+ if (!argc || !strcmp(argv0, "--"))
+ die ("--track needs a branch name");
+ if (!prefixcmp(argv0, "refs/"))
+ argv0 += 5;
+ if (!prefixcmp(argv0, "remotes/"))
+ argv0 += 8;
+ argv0 = strchr(argv0, '/');
+ if (!argv0 || !argv0[1])
+ die ("Missing branch name; try -b");
+ opts.new_branch = argv0 + 1;
+ }
+
+ if (conflict_style) {
+ opts.merge = 1; /* implied */
+ git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+ }
+
+ if (opts.force && opts.merge)
+ die("git checkout: -f and -m are incompatible");
+
+ /*
+ * case 1: git checkout <ref> -- [<paths>]
+ *
+ * <ref> must be a valid tree, everything after the '--' must be
+ * a path.
+ *
+ * case 2: git checkout -- [<paths>]
+ *
+ * everything after the '--' must be paths.
+ *
+ * case 3: git checkout <something> [<paths>]
+ *
+ * With no paths, if <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
+ *
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote tracking branch.
+ *
+ * Otherwise <something> shall not be ambiguous.
+ * - If it's *only* a reference, treat it like case (1).
+ * - If it's only a path, treat it like case (2).
+ * - else: fail.
+ *
+ */
+ if (argc) {
+ if (!strcmp(argv[0], "--")) { /* case (2) */
+ argv++;
+ argc--;
+ goto no_reference;
+ }
+
+ arg = argv[0];
+ has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+
+ if (!strcmp(arg, "-"))
+ arg = "@{-1}";
+
+ if (get_sha1_mb(arg, rev)) {
+ if (has_dash_dash) /* case (1) */
+ die("invalid reference: %s", arg);
+ if (!patch_mode &&
+ dwim_new_local_branch &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ goto no_reference;
+ opts.new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ }
+ else
+ goto no_reference;
+ }
+
+ /* we can't end up being in (2) anymore, eat the argument */
+ argv++;
+ argc--;
+
+ new.name = arg;
+ if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+ setup_branch_path(&new);
+
+ if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
+ resolve_ref(new.path, rev, 1, NULL))
+ ;
+ else
+ new.path = NULL;
+ parse_commit(new.commit);
+ source_tree = new.commit->tree;
+ } else
+ source_tree = parse_tree_indirect(rev);
+
+ if (!source_tree) /* case (1): want a tree */
+ die("reference is not a tree: %s", arg);
+ if (!has_dash_dash) {/* case (3 -> 1) */
+ /*
+ * Do not complain the most common case
+ * git checkout branch
+ * even if there happen to be a file called 'branch';
+ * it would be extremely annoying.
+ */
+ if (argc)
+ verify_non_filename(NULL, arg);
+ }
+ else {
+ argv++;
+ argc--;
+ }
+ }
+
+ no_reference:
+
+ if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+ opts.track = git_branch_track;
+
+ if (argc) {
+ const char **pathspec = get_pathspec(prefix, argv);
+
+ if (!pathspec)
+ die("invalid path specification");
+
+ if (patch_mode)
+ return interactive_checkout(new.name, pathspec, &opts);
+
+ /* Checkout paths */
+ if (opts.new_branch) {
+ if (argc == 1) {
+ die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+ } else {
+ die("git checkout: updating paths is incompatible with switching branches.");
+ }
+ }
+
+ if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+ die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
+
+ return checkout_paths(source_tree, pathspec, &opts);
+ }
+
+ if (patch_mode)
+ return interactive_checkout(new.name, NULL, &opts);
+
+ if (opts.new_branch) {
+ struct strbuf buf = STRBUF_INIT;
+ if (strbuf_check_branch_ref(&buf, opts.new_branch))
+ die("git checkout: we do not like '%s' as a branch name.",
+ opts.new_branch);
+ if (!get_sha1(buf.buf, rev))
+ die("git checkout: branch %s already exists", opts.new_branch);
+ strbuf_release(&buf);
+ }
+
+ if (new.name && !new.commit) {
+ die("Cannot switch branch to a non-commit.");
+ }
+ if (opts.writeout_stage)
+ die("--ours/--theirs is incompatible with switching branches.");
+
+ return switch_branches(&opts, &new);
+ }
diff --cc builtin/commit.c
index 0000000000000000000000000000000000000000,55676fd874466c70445e81c69cf397cd01380aaf..f4c73442cfba9483a826dcfbf68f5466d43e8351
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/commit.c
+++ b/builtin/commit.c
-" git config --global user.name Your Name\n"
+ /*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+ #include "cache.h"
+ #include "cache-tree.h"
+ #include "color.h"
+ #include "dir.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"
+ #include "string-list.h"
+ #include "rerere.h"
+ #include "unpack-trees.h"
+ #include "quote.h"
+
+ static const char * const builtin_commit_usage[] = {
+ "git commit [options] [--] <filepattern>...",
+ NULL
+ };
+
+ static const char * const builtin_status_usage[] = {
+ "git status [options] [--] <filepattern>...",
+ NULL
+ };
+
+ static const char implicit_ident_advice[] =
+ "Your name and email address were configured automatically based\n"
+ "on your username and hostname. Please check that they are accurate.\n"
+ "You can suppress this message by setting them explicitly:\n"
+ "\n"
- read_cache();
++" git config --global user.name \"Your Name\"\n"
+ " git config --global user.email you@example.com\n"
+ "\n"
+ "If the identity used for this commit is wrong, you can fix it with:\n"
+ "\n"
+ " git commit --amend --author='Your Name <you@example.com>'\n";
+
+ static unsigned char head_sha1[20];
+
+ static char *use_message_buffer;
+ static const char commit_editmsg[] = "COMMIT_EDITMSG";
+ 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 const char *logfile, *force_author;
+ static const char *template_file;
+ static char *edit_message, *use_message;
+ static char *author_name, *author_email, *author_date;
+ static int all, edit_flag, also, interactive, only, amend, signoff;
+ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+ static char *untracked_files_arg, *force_date;
+ /*
+ * The default commit message cleanup mode will remove the lines
+ * beginning with # (shell comments) and leading and trailing
+ * whitespaces (empty lines or containing only whitespaces)
+ * if editor is used, and only the whitespaces if the message
+ * is specified explicitly.
+ */
+ static enum {
+ CLEANUP_SPACE,
+ CLEANUP_NONE,
+ CLEANUP_ALL,
+ } cleanup_mode;
+ static char *cleanup_arg;
+
+ static int use_editor = 1, initial_commit, in_merge, include_status = 1;
+ static const char *only_include_assumed;
+ static struct strbuf message;
+
+ static int null_termination;
+ static enum {
+ STATUS_FORMAT_LONG,
+ STATUS_FORMAT_SHORT,
+ STATUS_FORMAT_PORCELAIN,
+ } status_format = STATUS_FORMAT_LONG;
+
+ 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_addstr(buf, "\n\n");
+ }
+ return 0;
+ }
+
+ static struct option builtin_commit_options[] = {
+ OPT__QUIET(&quiet),
+ OPT__VERBOSE(&verbose),
+
+ OPT_GROUP("Commit message options"),
+ OPT_FILENAME('F', "file", &logfile, "read log from file"),
+ OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
+ OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
+ 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(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
+ OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
+ OPT_FILENAME('t', "template", &template_file, "use specified template file"),
+ OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+ OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+ OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+ /* end commit message options */
+
+ 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, "commit only specified files"),
+ OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+ OPT_SET_INT(0, "short", &status_format, "show status concisely",
+ STATUS_FORMAT_SHORT),
+ OPT_SET_INT(0, "porcelain", &status_format,
+ "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+ OPT_BOOLEAN('z', "null", &null_termination,
+ "terminate entries with NUL"),
+ OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
+ /* end commit contents 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 int commit_index_files(void)
+ {
+ int err = 0;
+
+ switch (commit_style) {
+ case COMMIT_AS_IS:
+ break; /* nothing to do */
+ case COMMIT_NORMAL:
+ err = commit_lock_file(&index_lock);
+ break;
+ case COMMIT_PARTIAL:
+ err = commit_lock_file(&index_lock);
+ rollback_lock_file(&false_lock);
+ break;
+ }
+
+ return err;
+ }
+
+ /*
+ * 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 string_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];
+ struct string_list_item *item;
+
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+ continue;
+ item = string_list_insert(ce->name, list);
+ if (ce_skip_worktree(ce))
+ item->util = item; /* better a valid pointer than a fake one */
+ }
+
+ return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+ }
+
+ static void add_remove_files(struct string_list *list)
+ {
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ struct stat st;
+ struct string_list_item *p = &(list->items[i]);
+
+ /* p->util is skip-worktree */
+ if (p->util)
+ continue;
+
+ if (!lstat(p->string, &st)) {
+ if (add_to_cache(p->string, &st, 0))
+ die("updating files failed");
+ } else
+ remove_file_from_cache(p->string);
+ }
+ }
+
+ static void create_base_index(void)
+ {
+ struct tree *tree;
+ struct unpack_trees_options opts;
+ struct tree_desc t;
+
+ if (initial_commit) {
+ discard_cache();
+ return;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.index_only = 1;
+ opts.merge = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ opts.fn = oneway_merge;
+ tree = parse_tree_indirect(head_sha1);
+ if (!tree)
+ die("failed to unpack HEAD tree object");
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ if (unpack_trees(1, &t, &opts))
+ exit(128); /* We've already reported the error, finish dying */
+ }
+
+ static void refresh_cache_or_die(int refresh_flags)
+ {
+ /*
+ * refresh_flags contains REFRESH_QUIET, so the only errors
+ * are for unmerged entries.
+ */
+ if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN))
+ die_resolve_conflict("commit");
+ }
+
+ static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
+ {
+ int fd;
+ struct string_list partial;
+ const char **pathspec = NULL;
+ int refresh_flags = REFRESH_QUIET;
+
+ if (is_status)
+ refresh_flags |= REFRESH_UNMERGED;
+ if (interactive) {
+ if (interactive_add(argc, argv, prefix) != 0)
+ die("interactive add failed");
+ if (read_cache_preload(NULL) < 0)
+ die("index file corrupt");
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ if (*argv)
+ pathspec = get_pathspec(prefix, argv);
+
+ if (read_cache_preload(pathspec) < 0)
+ die("index file corrupt");
+
+ /*
+ * 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(also ? prefix : NULL, pathspec, 0);
+ refresh_cache_or_die(refresh_flags);
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_NORMAL;
+ return index_lock.filename;
+ }
+
+ /*
+ * 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_or_die(refresh_flags);
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(&index_lock))
+ die("unable to write new_index file");
+ commit_style = COMMIT_AS_IS;
+ return get_index_file();
+ }
+
+ /*
+ * 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 (in_merge)
+ die("cannot do a partial commit during a merge.");
+
+ memset(&partial, 0, sizeof(partial));
+ partial.strdup_strings = 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_lock_file(&index_lock))
+ die("unable to write new_index file");
+
+ fd = hold_lock_file_for_update(&false_lock,
+ git_path("next-index-%"PRIuMAX,
+ (uintmax_t) getpid()),
+ LOCK_DIE_ON_ERROR);
+
+ create_base_index();
+ add_remove_files(&partial);
+ refresh_cache(REFRESH_QUIET);
+
+ if (write_cache(fd, active_cache, active_nr) ||
+ close_lock_file(&false_lock))
+ die("unable to write temporary index file");
+
+ discard_cache();
+ read_cache_from(false_lock.filename);
+
+ return false_lock.filename;
+ }
+
+ static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
+ struct wt_status *s)
+ {
+ unsigned char sha1[20];
+
+ if (s->relative_paths)
+ s->prefix = prefix;
+
+ if (amend) {
+ s->amend = 1;
+ s->reference = "HEAD^1";
+ }
+ s->verbose = verbose;
+ s->index_file = index_file;
+ s->fp = fp;
+ s->nowarn = nowarn;
+ s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+
+ wt_status_collect(s);
+
+ switch (status_format) {
+ case STATUS_FORMAT_SHORT:
+ wt_shortstatus_print(s, null_termination);
+ break;
+ case STATUS_FORMAT_PORCELAIN:
+ wt_porcelain_print(s, null_termination);
+ break;
+ case STATUS_FORMAT_LONG:
+ wt_status_print(s);
+ break;
+ }
+
+ return s->commitable;
+ }
+
+ static int is_a_merge(const unsigned char *sha1)
+ {
+ struct commit *commit = lookup_commit(sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse HEAD commit");
+ return !!(commit->parents && commit->parents->next);
+ }
+
+ static const char sign_off_header[] = "Signed-off-by: ";
+
+ static void determine_author_info(void)
+ {
+ char *name, *email, *date;
+
+ name = getenv("GIT_AUTHOR_NAME");
+ email = getenv("GIT_AUTHOR_EMAIL");
+ date = getenv("GIT_AUTHOR_DATE");
+
+ if (use_message && !renew_authorship) {
+ const char *a, *lb, *rb, *eol;
+
+ a = strstr(use_message_buffer, "\nauthor ");
+ if (!a)
+ die("invalid commit: %s", use_message);
+
+ lb = strstr(a + 8, " <");
+ rb = strstr(a + 8, "> ");
+ eol = strchr(a + 8, '\n');
+ if (!lb || !rb || !eol)
+ die("invalid commit: %s", 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");
+ name = xstrndup(force_author, lb - force_author);
+ email = xstrndup(lb + 2, rb - (lb + 2));
+ }
+
+ if (force_date)
+ date = force_date;
+
+ author_name = name;
+ author_email = email;
+ author_date = date;
+ }
+
+ static int ends_rfc2822_footer(struct strbuf *sb)
+ {
+ int ch;
+ int hit = 0;
+ int i, j, k;
+ int len = sb->len;
+ int first = 1;
+ const char *buf = sb->buf;
+
+ for (i = len - 1; i > 0; i--) {
+ if (hit && buf[i] == '\n')
+ break;
+ hit = (buf[i] == '\n');
+ }
+
+ while (i < len - 1 && buf[i] == '\n')
+ i++;
+
+ for (; i < len; i = k) {
+ for (k = i; k < len && buf[k] != '\n'; k++)
+ ; /* do nothing */
+ k++;
+
+ if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+ continue;
+
+ first = 0;
+
+ for (j = 0; i + j < len; j++) {
+ ch = buf[i + j];
+ if (ch == ':')
+ break;
+ if (isalnum(ch) ||
+ (ch == '-'))
+ continue;
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+ static int prepare_to_commit(const char *index_file, const char *prefix,
+ struct wt_status *s)
+ {
+ struct stat statbuf;
+ int commitable, saved_color_setting;
+ struct strbuf sb = STRBUF_INIT;
+ char *buffer;
+ FILE *fp;
+ const char *hook_arg1 = NULL;
+ const char *hook_arg2 = NULL;
+ int ident_shown = 0;
+
+ if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+ return 0;
+
+ if (message.len) {
+ strbuf_addbuf(&sb, &message);
+ hook_arg1 = "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_errno("could not read log from standard input");
+ hook_arg1 = "message";
+ } else if (logfile) {
+ if (strbuf_read_file(&sb, logfile, 0) < 0)
+ die_errno("could not read log file '%s'",
+ logfile);
+ hook_arg1 = "message";
+ } 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));
+ hook_arg1 = "commit";
+ hook_arg2 = use_message;
+ } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+ die_errno("could not read MERGE_MSG");
+ hook_arg1 = "merge";
+ } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+ die_errno("could not read SQUASH_MSG");
+ hook_arg1 = "squash";
+ } else if (template_file && !stat(template_file, &statbuf)) {
+ if (strbuf_read_file(&sb, template_file, 0) < 0)
+ die_errno("could not read '%s'", template_file);
+ hook_arg1 = "template";
+ }
+
+ /*
+ * This final case does not modify the template message,
+ * it just sets the argument to the prepare-commit-msg hook.
+ */
+ else if (in_merge)
+ hook_arg1 = "merge";
+
+ fp = fopen(git_path(commit_editmsg), "w");
+ if (fp == NULL)
+ die_errno("could not open '%s'", git_path(commit_editmsg));
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, 0);
+
+ if (signoff) {
+ struct strbuf sob = STRBUF_INIT;
+ int i;
+
+ strbuf_addstr(&sob, sign_off_header);
+ strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL")));
+ strbuf_addch(&sob, '\n');
+ for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
+ ; /* do nothing */
+ if (prefixcmp(sb.buf + i, sob.buf)) {
+ if (!i || !ends_rfc2822_footer(&sb))
+ strbuf_addch(&sb, '\n');
+ strbuf_addbuf(&sb, &sob);
+ }
+ strbuf_release(&sob);
+ }
+
+ if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+ die_errno("could not write commit template");
+
+ strbuf_release(&sb);
+
+ determine_author_info();
+
+ /* This checks if committer ident is explicitly given */
+ git_committer_info(0);
+ if (use_editor && include_status) {
+ char *author_ident;
+ const char *committer_ident;
+
+ if (in_merge)
+ 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.");
+ if (cleanup_mode == CLEANUP_ALL)
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be ignored, and an empty"
+ " message aborts the commit.\n");
+ else /* CLEANUP_SPACE, that is. */
+ fprintf(fp,
+ " Lines starting\n"
+ "# with '#' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "# An empty message aborts the commit.\n");
+ if (only_include_assumed)
+ fprintf(fp, "# %s\n", only_include_assumed);
+
+ author_ident = xstrdup(fmt_name(author_name, author_email));
+ committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL"));
+ if (strcmp(author_ident, committer_ident))
+ fprintf(fp,
+ "%s"
+ "# Author: %s\n",
+ ident_shown++ ? "" : "#\n",
+ author_ident);
+ free(author_ident);
+
+ if (!user_ident_sufficiently_given())
+ fprintf(fp,
+ "%s"
+ "# Committer: %s\n",
+ ident_shown++ ? "" : "#\n",
+ committer_ident);
+
+ if (ident_shown)
+ fprintf(fp, "#\n");
+
+ saved_color_setting = s->use_color;
+ s->use_color = 0;
+ commitable = run_status(fp, index_file, prefix, 1, s);
+ s->use_color = saved_color_setting;
+ } else {
+ unsigned char sha1[20];
+ const char *parent = "HEAD";
+
+ if (!active_nr && read_cache() < 0)
+ die("Cannot read index");
+
+ if (amend)
+ parent = "HEAD^1";
+
+ if (get_sha1(parent, sha1))
+ commitable = !!active_nr;
+ else
+ commitable = index_differs_from(parent, 0);
+ }
+
+ fclose(fp);
+
+ if (!commitable && !in_merge && !allow_empty &&
+ !(amend && is_a_merge(head_sha1))) {
+ run_status(stdout, index_file, prefix, 0, s);
+ return 0;
+ }
+
+ /*
+ * Re-read the index as pre-commit hook could have updated it,
+ * and write it out as a tree. We must do this before we invoke
+ * the editor and after we invoke run_status above.
+ */
+ discard_cache();
+ read_cache_from(index_file);
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree,
+ active_cache, active_nr, 0, 0) < 0) {
+ error("Error building trees");
+ return 0;
+ }
+
+ if (run_hook(index_file, "prepare-commit-msg",
+ git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+ return 0;
+
+ if (use_editor) {
+ char index[PATH_MAX];
+ const char *env[2] = { index, NULL };
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ if (launch_editor(git_path(commit_editmsg), NULL, env)) {
+ fprintf(stderr,
+ "Please supply the message using either -m or -F option.\n");
+ exit(1);
+ }
+ }
+
+ if (!no_verify &&
+ run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+ static int message_is_empty(struct strbuf *sb)
+ {
+ struct strbuf tmpl = STRBUF_INIT;
+ const char *nl;
+ int eol, i, start = 0;
+
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+
+ /* See if the template is just a prefix of the message. */
+ if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
+ stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+ 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 const char *find_author_by_nickname(const char *name)
+ {
+ struct rev_info revs;
+ struct commit *commit;
+ struct strbuf buf = STRBUF_INIT;
+ const char *av[20];
+ int ac = 0;
+
+ init_revisions(&revs, NULL);
+ strbuf_addf(&buf, "--author=%s", name);
+ av[++ac] = "--all";
+ av[++ac] = "-i";
+ av[++ac] = buf.buf;
+ av[++ac] = NULL;
+ setup_revisions(ac, av, &revs, NULL);
+ prepare_revision_walk(&revs);
+ commit = get_revision(&revs);
+ if (commit) {
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
+ strbuf_release(&buf);
+ format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+ return strbuf_detach(&buf, NULL);
+ }
+ die("No existing author found with '%s'", name);
+ }
+
+
+ static void handle_untracked_files_arg(struct wt_status *s)
+ {
+ if (!untracked_files_arg)
+ ; /* default already initialized */
+ else if (!strcmp(untracked_files_arg, "no"))
+ s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "normal"))
+ s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+ else if (!strcmp(untracked_files_arg, "all"))
+ s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+ else
+ die("Invalid untracked files mode '%s'", untracked_files_arg);
+ }
+
+ static int parse_and_validate_options(int argc, const char *argv[],
+ const char * const usage[],
+ const char *prefix,
+ struct wt_status *s)
+ {
+ int f = 0;
+
+ argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
+ 0);
+
+ if (force_author && !strchr(force_author, '>'))
+ force_author = find_author_by_nickname(force_author);
+
+ if (force_author && renew_authorship)
+ die("Using both --reset-author and --author does not make sense");
+
+ if (logfile || message.len || use_message)
+ use_editor = 0;
+ if (edit_flag)
+ use_editor = 1;
+ if (!use_editor)
+ setenv("GIT_EDITOR", ":", 1);
+
+ if (get_sha1("HEAD", head_sha1))
+ initial_commit = 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 merge -- 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.len && f > 0)
+ die("Option -m cannot be combined with -c/-C/-F.");
+ if (edit_message)
+ use_message = edit_message;
+ if (amend && !use_message)
+ use_message = "HEAD";
+ if (!use_message && renew_authorship)
+ die("--reset-author can be used only with -C, -c or --amend.");
+ 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_reference(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...";
+ if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
+ cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "verbatim"))
+ cleanup_mode = CLEANUP_NONE;
+ else if (!strcmp(cleanup_arg, "whitespace"))
+ cleanup_mode = CLEANUP_SPACE;
+ else if (!strcmp(cleanup_arg, "strip"))
+ cleanup_mode = CLEANUP_ALL;
+ else
+ die("Invalid cleanup mode %s", cleanup_arg);
+
+ handle_untracked_files_arg(s);
+
+ 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.");
+
+ if (null_termination && status_format == STATUS_FORMAT_LONG)
+ status_format = STATUS_FORMAT_PORCELAIN;
+ if (status_format != STATUS_FORMAT_LONG)
+ dry_run = 1;
+
+ return argc;
+ }
+
+ static int dry_run_commit(int argc, const char **argv, const char *prefix,
+ struct wt_status *s)
+ {
+ int commitable;
+ const char *index_file;
+
+ index_file = prepare_index(argc, argv, prefix, 1);
+ commitable = run_status(stdout, index_file, prefix, 0, s);
+ rollback_index_files();
+
+ return commitable ? 0 : 1;
+ }
+
+ static int parse_status_slot(const char *var, int offset)
+ {
+ if (!strcasecmp(var+offset, "header"))
+ return WT_STATUS_HEADER;
+ if (!strcasecmp(var+offset, "updated")
+ || !strcasecmp(var+offset, "added"))
+ return WT_STATUS_UPDATED;
+ if (!strcasecmp(var+offset, "changed"))
+ return WT_STATUS_CHANGED;
+ if (!strcasecmp(var+offset, "untracked"))
+ return WT_STATUS_UNTRACKED;
+ if (!strcasecmp(var+offset, "nobranch"))
+ return WT_STATUS_NOBRANCH;
+ if (!strcasecmp(var+offset, "unmerged"))
+ return WT_STATUS_UNMERGED;
+ return -1;
+ }
+
+ static int git_status_config(const char *k, const char *v, void *cb)
+ {
+ struct wt_status *s = cb;
+
+ if (!strcmp(k, "status.submodulesummary")) {
+ int is_bool;
+ s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
+ if (is_bool && s->submodule_summary)
+ s->submodule_summary = -1;
+ return 0;
+ }
+ if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
+ s->use_color = git_config_colorbool(k, v, -1);
+ return 0;
+ }
+ if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
+ int slot = parse_status_slot(k, 13);
+ if (slot < 0)
+ return 0;
+ if (!v)
+ return config_error_nonbool(k);
+ color_parse(v, k, s->color_palette[slot]);
+ return 0;
+ }
+ if (!strcmp(k, "status.relativepaths")) {
+ s->relative_paths = git_config_bool(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "status.showuntrackedfiles")) {
+ if (!v)
+ return config_error_nonbool(k);
+ else if (!strcmp(v, "no"))
+ s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+ else if (!strcmp(v, "normal"))
+ s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+ else if (!strcmp(v, "all"))
+ s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+ else
+ return error("Invalid untracked files mode '%s'", v);
+ return 0;
+ }
+ return git_diff_ui_config(k, v, NULL);
+ }
+
+ int cmd_status(int argc, const char **argv, const char *prefix)
+ {
+ struct wt_status s;
+ unsigned char sha1[20];
+ static struct option builtin_status_options[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_SET_INT('s', "short", &status_format,
+ "show status concisely", STATUS_FORMAT_SHORT),
+ OPT_SET_INT(0, "porcelain", &status_format,
+ "show porcelain output format",
+ STATUS_FORMAT_PORCELAIN),
+ OPT_BOOLEAN('z', "null", &null_termination,
+ "terminate entries with NUL"),
+ { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
+ "mode",
+ "show untracked files, optional modes: all, normal, no. (Default: all)",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_END(),
+ };
+
+ if (null_termination && status_format == STATUS_FORMAT_LONG)
+ status_format = STATUS_FORMAT_PORCELAIN;
+
+ wt_status_prepare(&s);
+ git_config(git_status_config, &s);
+ in_merge = file_exists(git_path("MERGE_HEAD"));
+ argc = parse_options(argc, argv, prefix,
+ builtin_status_options,
+ builtin_status_usage, 0);
+ handle_untracked_files_arg(&s);
+
+ if (*argv)
+ s.pathspec = get_pathspec(prefix, argv);
+
++ read_cache_preload(s.pathspec);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+ s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+ s.in_merge = in_merge;
+ wt_status_collect(&s);
+
+ if (s.relative_paths)
+ s.prefix = prefix;
+ if (s.use_color == -1)
+ s.use_color = git_use_color_default;
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ switch (status_format) {
+ case STATUS_FORMAT_SHORT:
+ wt_shortstatus_print(&s, null_termination);
+ break;
+ case STATUS_FORMAT_PORCELAIN:
+ wt_porcelain_print(&s, null_termination);
+ break;
+ case STATUS_FORMAT_LONG:
+ s.verbose = verbose;
+ wt_status_print(&s);
+ break;
+ }
+ return 0;
+ }
+
+ static void print_summary(const char *prefix, const unsigned char *sha1)
+ {
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf format = STRBUF_INIT;
+ unsigned char junk_sha1[20];
+ const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+ struct pretty_print_context pctx = {0};
+ struct strbuf author_ident = STRBUF_INIT;
+ struct strbuf committer_ident = STRBUF_INIT;
+
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("couldn't look up newly created commit");
+ if (!commit || parse_commit(commit))
+ die("could not parse newly created commit");
+
+ strbuf_addstr(&format, "format:%h] %s");
+
+ format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+ format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+ if (strbuf_cmp(&author_ident, &committer_ident)) {
+ strbuf_addstr(&format, "\n Author: ");
+ strbuf_addbuf_percentquote(&format, &author_ident);
+ }
+ if (!user_ident_sufficiently_given()) {
+ strbuf_addstr(&format, "\n Committer: ");
+ strbuf_addbuf_percentquote(&format, &committer_ident);
+ if (advice_implicit_identity) {
+ strbuf_addch(&format, '\n');
+ strbuf_addstr(&format, implicit_ident_advice);
+ }
+ }
+ strbuf_release(&author_ident);
+ strbuf_release(&committer_ident);
+
+ 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;
+ get_commit_format(format.buf, &rev);
+ rev.always_show_header = 0;
+ rev.diffopt.detect_rename = 1;
+ rev.diffopt.rename_limit = 100;
+ rev.diffopt.break_opt = 0;
+ diff_setup_done(&rev.diffopt);
+
+ printf("[%s%s ",
+ !prefixcmp(head, "refs/heads/") ?
+ head + 11 :
+ !strcmp(head, "HEAD") ?
+ "detached HEAD" :
+ head,
+ initial_commit ? " (root-commit)" : "");
+
+ if (!log_tree_commit(&rev, commit)) {
+ struct pretty_print_context ctx = {0};
+ struct strbuf buf = STRBUF_INIT;
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, format.buf + 7, &buf, &ctx);
+ printf("%s\n", buf.buf);
+ strbuf_release(&buf);
+ }
+ strbuf_release(&format);
+ }
+
+ static int git_commit_config(const char *k, const char *v, void *cb)
+ {
+ struct wt_status *s = cb;
+
+ if (!strcmp(k, "commit.template"))
+ return git_config_pathname(&template_file, k, v);
+ if (!strcmp(k, "commit.status")) {
+ include_status = git_config_bool(k, v);
+ return 0;
+ }
+
+ return git_status_config(k, v, s);
+ }
+
+ int cmd_commit(int argc, const char **argv, const char *prefix)
+ {
+ struct strbuf sb = STRBUF_INIT;
+ const char *index_file, *reflog_msg;
+ char *nl, *p;
+ unsigned char commit_sha1[20];
+ struct ref_lock *ref_lock;
+ struct commit_list *parents = NULL, **pptr = &parents;
+ struct stat statbuf;
+ int allow_fast_forward = 1;
+ struct wt_status s;
+
+ wt_status_prepare(&s);
+ git_config(git_commit_config, &s);
+ in_merge = file_exists(git_path("MERGE_HEAD"));
+ s.in_merge = in_merge;
+
+ if (s.use_color == -1)
+ s.use_color = git_use_color_default;
+ argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+ prefix, &s);
+ if (dry_run) {
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+ return dry_run_commit(argc, argv, prefix, &s);
+ }
+ index_file = prepare_index(argc, argv, prefix, 0);
+
+ /* Set up everything for writing the commit object. This includes
+ running hooks, writing the trees, and interacting with the user. */
+ if (!prepare_to_commit(index_file, prefix, &s)) {
+ rollback_index_files();
+ return 1;
+ }
+
+ /* Determine parents */
+ if (initial_commit) {
+ reflog_msg = "commit (initial)";
+ } 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)
+ pptr = &commit_list_insert(c->item, pptr)->next;
+ } else if (in_merge) {
+ struct strbuf m = STRBUF_INIT;
+ FILE *fp;
+
+ reflog_msg = "commit (merge)";
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ fp = fopen(git_path("MERGE_HEAD"), "r");
+ if (fp == NULL)
+ die_errno("could not open '%s' for reading",
+ git_path("MERGE_HEAD"));
+ while (strbuf_getline(&m, fp, '\n') != EOF) {
+ unsigned char sha1[20];
+ if (get_sha1_hex(m.buf, sha1) < 0)
+ die("Corrupt MERGE_HEAD file (%s)", m.buf);
+ pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+ }
+ fclose(fp);
+ strbuf_release(&m);
+ if (!stat(git_path("MERGE_MODE"), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ die_errno("could not read MERGE_MODE");
+ if (!strcmp(sb.buf, "no-ff"))
+ allow_fast_forward = 0;
+ }
+ if (allow_fast_forward)
+ parents = reduce_heads(parents);
+ } else {
+ reflog_msg = "commit";
+ pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
+ }
+
+ /* Finally, get the commit message */
+ strbuf_reset(&sb);
+ if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+ int saved_errno = errno;
+ rollback_index_files();
+ die("could not read commit message: %s", strerror(saved_errno));
+ }
+
+ /* Truncate the message just before the diff, if any. */
+ if (verbose) {
+ p = strstr(sb.buf, "\ndiff --git ");
+ if (p != NULL)
+ strbuf_setlen(&sb, p - sb.buf + 1);
+ }
+
+ if (cleanup_mode != CLEANUP_NONE)
+ stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+ if (message_is_empty(&sb)) {
+ rollback_index_files();
+ fprintf(stderr, "Aborting commit due to empty commit message.\n");
+ exit(1);
+ }
+
+ if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
+ fmt_ident(author_name, author_email, author_date,
+ IDENT_ERROR_ON_NO_NAME))) {
+ rollback_index_files();
+ die("failed to write commit object");
+ }
+
+ ref_lock = lock_any_ref_for_update("HEAD",
+ initial_commit ? NULL : head_sha1,
+ 0);
+
+ nl = strchr(sb.buf, '\n');
+ if (nl)
+ strbuf_setlen(&sb, nl + 1 - sb.buf);
+ else
+ strbuf_addch(&sb, '\n');
+ strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
+ strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
+
+ if (!ref_lock) {
+ rollback_index_files();
+ die("cannot lock HEAD ref");
+ }
+ if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+ rollback_index_files();
+ die("cannot update HEAD ref");
+ }
+
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("MERGE_MODE"));
+ unlink(git_path("SQUASH_MSG"));
+
+ if (commit_index_files())
+ die ("Repository has been updated, but unable to write\n"
+ "new_index file. Check that disk is not full or quota is\n"
+ "not exceeded, and then \"git reset HEAD\" to recover.");
+
+ rerere(0);
+ run_hook(get_index_file(), "post-commit", NULL);
+ if (!quiet)
+ print_summary(prefix, commit_sha1);
+
+ return 0;
+ }
diff --cc builtin/fetch.c
index 0000000000000000000000000000000000000000,8654fa7a2dbe2c1ca76a493a4322447de5219b99..b6c5b344be8e8abb90cf3f03d3cea2600511787f
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/fetch.c
+++ b/builtin/fetch.c
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+ /*
+ * "git fetch"
+ */
+ #include "cache.h"
+ #include "refs.h"
+ #include "commit.h"
+ #include "builtin.h"
+ #include "string-list.h"
+ #include "remote.h"
+ #include "transport.h"
+ #include "run-command.h"
+ #include "parse-options.h"
+ #include "sigchain.h"
++#include "transport.h"
+
+ static const char * const builtin_fetch_usage[] = {
+ "git fetch [options] [<repository> <refspec>...]",
+ "git fetch [options] <group>",
+ "git fetch --multiple [options] [<repository> | <group>]...",
+ "git fetch --all [options]",
+ NULL
+ };
+
+ enum {
+ TAGS_UNSET = 0,
+ TAGS_DEFAULT = 1,
+ TAGS_SET = 2
+ };
+
+ static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+ static int tags = TAGS_DEFAULT;
+ static const char *depth;
+ static const char *upload_pack;
+ static struct strbuf default_rla = STRBUF_INIT;
+ static struct transport *transport;
+
+ static struct option builtin_fetch_options[] = {
+ OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "all", &all,
+ "fetch from all remotes"),
+ OPT_BOOLEAN('a', "append", &append,
+ "append to .git/FETCH_HEAD instead of overwriting"),
+ OPT_STRING(0, "upload-pack", &upload_pack, "PATH",
+ "path to upload pack on remote end"),
+ OPT_BOOLEAN('f', "force", &force,
+ "force overwrite of local branch"),
+ OPT_BOOLEAN('m', "multiple", &multiple,
+ "fetch from multiple remotes"),
+ OPT_SET_INT('t', "tags", &tags,
+ "fetch all tags and associated objects", TAGS_SET),
+ OPT_SET_INT('n', NULL, &tags,
+ "do not fetch all tags (--no-tags)", TAGS_UNSET),
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune tracking branches no longer on remote"),
+ OPT_BOOLEAN(0, "dry-run", &dry_run,
+ "dry run"),
+ OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
+ OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ "allow updating of HEAD ref"),
+ OPT_STRING(0, "depth", &depth, "DEPTH",
+ "deepen history of shallow clone"),
+ OPT_END()
+ };
+
+ static void unlock_pack(void)
+ {
+ if (transport)
+ transport_unlock_pack(transport);
+ }
+
+ static void unlock_pack_on_signal(int signo)
+ {
+ unlock_pack();
+ sigchain_pop(signo);
+ raise(signo);
+ }
+
+ static void add_merge_config(struct ref **head,
+ const struct ref *remote_refs,
+ struct branch *branch,
+ struct ref ***tail)
+ {
+ int i;
+
+ for (i = 0; i < branch->merge_nr; i++) {
+ struct ref *rm, **old_tail = *tail;
+ struct refspec refspec;
+
+ for (rm = *head; rm; rm = rm->next) {
+ if (branch_merge_matches(branch, i, rm->name)) {
+ rm->merge = 1;
+ break;
+ }
+ }
+ if (rm)
+ continue;
+
+ /*
+ * Not fetched to a tracking branch? We need to fetch
+ * it anyway to allow this branch's "branch.$name.merge"
+ * to be honored by 'git pull', but we do not have to
+ * fail if branch.$name.merge is misconfigured to point
+ * at a nonexisting branch. If we were indeed called by
+ * 'git pull', it will notice the misconfiguration because
+ * there is no entry in the resulting FETCH_HEAD marked
+ * for merging.
+ */
+ refspec.src = branch->merge[i]->src;
+ refspec.dst = NULL;
+ refspec.pattern = 0;
+ refspec.force = 0;
+ get_fetch_map(remote_refs, &refspec, tail, 1);
+ for (rm = *old_tail; rm; rm = rm->next)
+ rm->merge = 1;
+ }
+ }
+
+ static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail);
+
+ static struct ref *get_ref_map(struct transport *transport,
+ struct refspec *refs, int ref_count, int tags,
+ int *autotags)
+ {
+ int i;
+ struct ref *rm;
+ struct ref *ref_map = NULL;
+ struct ref **tail = &ref_map;
+
+ const struct ref *remote_refs = transport_get_remote_refs(transport);
+
+ if (ref_count || tags == TAGS_SET) {
+ for (i = 0; i < ref_count; i++) {
+ get_fetch_map(remote_refs, &refs[i], &tail, 0);
+ if (refs[i].dst && refs[i].dst[0])
+ *autotags = 1;
+ }
+ /* Merge everything on the command line, but not --tags */
+ for (rm = ref_map; rm; rm = rm->next)
+ rm->merge = 1;
+ if (tags == TAGS_SET)
+ get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ } else {
+ /* Use the defaults */
+ struct remote *remote = transport->remote;
+ struct branch *branch = branch_get(NULL);
+ int has_merge = branch_has_merge_config(branch);
+ if (remote && (remote->fetch_refspec_nr || has_merge)) {
+ for (i = 0; i < remote->fetch_refspec_nr; i++) {
+ get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
+ if (remote->fetch[i].dst &&
+ remote->fetch[i].dst[0])
+ *autotags = 1;
+ if (!i && !has_merge && ref_map &&
+ !remote->fetch[0].pattern)
+ ref_map->merge = 1;
+ }
+ /*
+ * if the remote we're fetching from is the same
+ * as given in branch.<name>.remote, we add the
+ * ref given in branch.<name>.merge, too.
+ */
+ if (has_merge &&
+ !strcmp(branch->remote_name, remote->name))
+ add_merge_config(&ref_map, remote_refs, branch, &tail);
+ } else {
+ ref_map = get_remote_ref(remote_refs, "HEAD");
+ if (!ref_map)
+ die("Couldn't find remote ref HEAD");
+ ref_map->merge = 1;
+ tail = &ref_map->next;
+ }
+ }
+ if (tags == TAGS_DEFAULT && *autotags)
+ find_non_local_tags(transport, &ref_map, &tail);
+ ref_remove_duplicates(ref_map);
+
+ return ref_map;
+ }
+
+ #define STORE_REF_ERROR_OTHER 1
+ #define STORE_REF_ERROR_DF_CONFLICT 2
+
+ static int s_update_ref(const char *action,
+ struct ref *ref,
+ int check_old)
+ {
+ char msg[1024];
+ char *rla = getenv("GIT_REFLOG_ACTION");
+ static struct ref_lock *lock;
+
+ if (dry_run)
+ return 0;
+ if (!rla)
+ rla = default_rla.buf;
+ snprintf(msg, sizeof(msg), "%s: %s", rla, action);
+ lock = lock_any_ref_for_update(ref->name,
+ check_old ? ref->old_sha1 : NULL, 0);
+ if (!lock)
+ return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
+ return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+ STORE_REF_ERROR_OTHER;
+ return 0;
+ }
+
- sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
+ #define REFCOL_WIDTH 10
+
+ static int update_local_ref(struct ref *ref,
+ const char *remote,
+ char *display)
+ {
+ struct commit *current = NULL, *updated;
+ enum object_type type;
+ struct branch *current_branch = branch_get(NULL);
+ const char *pretty_ref = prettify_refname(ref->name);
+
+ *display = 0;
+ type = sha1_object_info(ref->new_sha1, NULL);
+ if (type < 0)
+ die("object %s not found", sha1_to_hex(ref->new_sha1));
+
+ if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+ if (verbosity > 0)
- SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
++ sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
+ "[up to date]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 0;
+ }
+
+ if (current_branch &&
+ !strcmp(ref->name, current_branch->name) &&
+ !(update_head_ok || is_bare_repository()) &&
+ !is_null_sha1(ref->old_sha1)) {
+ /*
+ * If this is the head, and it's not okay to update
+ * the head, and the old value of the head isn't empty...
+ */
+ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
- SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
++ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+
+ if (!is_null_sha1(ref->old_sha1) &&
+ !prefixcmp(ref->name, "refs/tags/")) {
+ int r;
+ r = s_update_ref("updating tag", ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
- SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
++ TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ current = lookup_commit_reference_gently(ref->old_sha1, 1);
+ updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+ if (!current || !updated) {
+ const char *msg;
+ const char *what;
+ int r;
+ if (!strncmp(ref->name, "refs/tags/", 10)) {
+ msg = "storing tag";
+ what = "[new tag]";
+ }
+ else {
+ msg = "storing head";
+ what = "[new branch]";
+ }
+
+ r = s_update_ref(msg, ref, 0);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
- SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
++ TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
+ r ? " (unable to update local ref)" : "");
+ return r;
+ }
+
+ if (in_merge_bases(current, &updated, 1)) {
+ char quickref[83];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "..");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("fast-forward", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
- SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
++ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref, r ? " (unable to update local ref)" : "");
+ return r;
+ } else if (force || ref->force) {
+ char quickref[84];
+ int r;
+ strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+ strcat(quickref, "...");
+ strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ r = s_update_ref("forced-update", ref, 1);
+ sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
- SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
++ TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
+ pretty_ref,
+ r ? "unable to update local ref" : "forced update");
+ return r;
+ } else {
+ sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)",
- SUMMARY_WIDTH, *kind ? kind : "branch",
++ TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
+ return 1;
+ }
+ }
+
+ static int store_updated_refs(const char *raw_url, const char *remote_name,
+ struct ref *ref_map)
+ {
+ FILE *fp;
+ struct commit *commit;
+ int url_len, i, note_len, shown_url = 0, rc = 0;
+ char note[1024];
+ const char *what, *kind;
+ struct ref *rm;
+ char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+
+ fp = fopen(filename, "a");
+ if (!fp)
+ return error("cannot open %s: %s\n", filename, strerror(errno));
+
+ if (raw_url)
+ url = transport_anonymize_url(raw_url);
+ else
+ url = xstrdup("foreign");
+ for (rm = ref_map; rm; rm = rm->next) {
+ struct ref *ref = NULL;
+
+ if (rm->peer_ref) {
+ ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+ strcpy(ref->name, rm->peer_ref->name);
+ hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+ hashcpy(ref->new_sha1, rm->old_sha1);
+ ref->force = rm->peer_ref->force;
+ }
+
+ commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ if (!commit)
+ rm->merge = 0;
+
+ if (!strcmp(rm->name, "HEAD")) {
+ kind = "";
+ what = "";
+ }
+ else if (!prefixcmp(rm->name, "refs/heads/")) {
+ kind = "branch";
+ what = rm->name + 11;
+ }
+ else if (!prefixcmp(rm->name, "refs/tags/")) {
+ kind = "tag";
+ what = rm->name + 10;
+ }
+ else if (!prefixcmp(rm->name, "refs/remotes/")) {
+ kind = "remote branch";
+ what = rm->name + 13;
+ }
+ else {
+ kind = "";
+ what = rm->name;
+ }
+
+ url_len = strlen(url);
+ for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+ ;
+ url_len = i + 1;
+ if (4 < i && !strncmp(".git", url + i - 3, 4))
+ url_len = i - 3;
+
+ note_len = 0;
+ if (*what) {
+ if (*kind)
+ note_len += sprintf(note + note_len, "%s ",
+ kind);
+ note_len += sprintf(note + note_len, "'%s' of ", what);
+ }
+ note[note_len] = '\0';
+ fprintf(fp, "%s\t%s\t%s",
+ sha1_to_hex(commit ? commit->object.sha1 :
+ rm->old_sha1),
+ rm->merge ? "" : "not-for-merge",
+ note);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ fputs("\\n", fp);
+ else
+ fputc(url[i], fp);
+ fputc('\n', fp);
+
+ if (ref)
+ rc |= update_local_ref(ref, what, note);
+ else
+ sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
- SUMMARY_WIDTH, "[deleted]",
++ TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
+ REFCOL_WIDTH, *what ? what : "HEAD");
+ if (*note) {
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, "From %.*s\n",
+ url_len, url);
+ shown_url = 1;
+ }
+ if (verbosity >= 0)
+ fprintf(stderr, " %s\n", note);
+ }
+ }
+ free(url);
+ fclose(fp);
+ if (rc & STORE_REF_ERROR_DF_CONFLICT)
+ error("some local refs could not be updated; try running\n"
+ " 'git remote prune %s' to remove any old, conflicting "
+ "branches", remote_name);
+ return rc;
+ }
+
+ /*
+ * We would want to bypass the object transfer altogether if
+ * everything we are going to fetch already exists and is connected
+ * locally.
+ *
+ * The refs we are going to fetch are in ref_map. If running
+ *
+ * $ git rev-list --objects --stdin --not --all
+ *
+ * (feeding all the refs in ref_map on its standard input)
+ * does not error out, that means everything reachable from the
+ * refs we are going to fetch exists and is connected to some of
+ * our existing refs.
+ */
+ static int quickfetch(struct ref *ref_map)
+ {
+ struct child_process revlist;
+ struct ref *ref;
+ int err;
+ const char *argv[] = {"rev-list",
+ "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+
+ /*
+ * If we are deepening a shallow clone we already have these
+ * objects reachable. Running rev-list here will return with
+ * a good (0) exit status and we'll bypass the fetch that we
+ * really need to perform. Claiming failure now will ensure
+ * we perform the network exchange to deepen our history.
+ */
+ if (depth)
+ return -1;
+
+ if (!ref_map)
+ return 0;
+
+ memset(&revlist, 0, sizeof(revlist));
+ revlist.argv = argv;
+ revlist.git_cmd = 1;
+ revlist.no_stdout = 1;
+ revlist.no_stderr = 1;
+ revlist.in = -1;
+
+ err = start_command(&revlist);
+ if (err) {
+ error("could not run rev-list");
+ return err;
+ }
+
+ /*
+ * If rev-list --stdin encounters an unknown commit, it terminates,
+ * which will cause SIGPIPE in the write loop below.
+ */
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ for (ref = ref_map; ref; ref = ref->next) {
+ if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
+ write_str_in_full(revlist.in, "\n") < 0) {
+ if (errno != EPIPE && errno != EINVAL)
+ error("failed write to rev-list: %s", strerror(errno));
+ err = -1;
+ break;
+ }
+ }
+
+ if (close(revlist.in)) {
+ error("failed to close rev-list's stdin: %s", strerror(errno));
+ err = -1;
+ }
+
+ sigchain_pop(SIGPIPE);
+
+ return finish_command(&revlist) || err;
+ }
+
+ static int fetch_refs(struct transport *transport, struct ref *ref_map)
+ {
+ int ret = quickfetch(ref_map);
+ if (ret)
+ ret = transport_fetch_refs(transport, ref_map);
+ if (!ret)
+ ret |= store_updated_refs(transport->url,
+ transport->remote->name,
+ ref_map);
+ transport_unlock_pack(transport);
+ return ret;
+ }
+
+ static int prune_refs(struct transport *transport, struct ref *ref_map)
+ {
+ int result = 0;
+ struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ const char *dangling_msg = dry_run
+ ? " (%s will become dangling)\n"
+ : " (%s has become dangling)\n";
+
+ for (ref = stale_refs; ref; ref = ref->next) {
+ if (!dry_run)
+ result |= delete_ref(ref->name, NULL, 0);
+ if (verbosity >= 0) {
+ fprintf(stderr, " x %-*s %-*s -> %s\n",
- char *filename = git_path("FETCH_HEAD");
- FILE *fp = fopen(filename, "w");
- if (!fp)
- return error("cannot open %s: %s\n", filename, strerror(errno));
- fclose(fp);
++ TRANSPORT_SUMMARY_WIDTH, "[deleted]",
+ REFCOL_WIDTH, "(none)", prettify_refname(ref->name));
+ warn_dangling_symref(stderr, dangling_msg, ref->name);
+ }
+ }
+ free_refs(stale_refs);
+ return result;
+ }
+
+ static int add_existing(const char *refname, const unsigned char *sha1,
+ int flag, void *cbdata)
+ {
+ struct string_list *list = (struct string_list *)cbdata;
+ struct string_list_item *item = string_list_insert(refname, list);
+ item->util = (void *)sha1;
+ return 0;
+ }
+
+ static int will_fetch(struct ref **head, const unsigned char *sha1)
+ {
+ struct ref *rm = *head;
+ while (rm) {
+ if (!hashcmp(rm->old_sha1, sha1))
+ return 1;
+ rm = rm->next;
+ }
+ return 0;
+ }
+
+ struct tag_data {
+ struct ref **head;
+ struct ref ***tail;
+ };
+
+ static int add_to_tail(struct string_list_item *item, void *cb_data)
+ {
+ struct tag_data *data = (struct tag_data *)cb_data;
+ struct ref *rm = NULL;
+
+ /* We have already decided to ignore this item */
+ if (!item->util)
+ return 0;
+
+ rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+
+ **data->tail = rm;
+ *data->tail = &rm->next;
+
+ return 0;
+ }
+
+ static void find_non_local_tags(struct transport *transport,
+ struct ref **head,
+ struct ref ***tail)
+ {
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list remote_refs = { NULL, 0, 0, 0 };
+ struct tag_data data = {head, tail};
+ const struct ref *ref;
+ struct string_list_item *item = NULL;
+
+ for_each_ref(add_existing, &existing_refs);
+ for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+ if (prefixcmp(ref->name, "refs/tags"))
+ continue;
+
+ /*
+ * The peeled ref always follows the matching base
+ * ref, so if we see a peeled ref that we don't want
+ * to fetch then we can mark the ref entry in the list
+ * as one to ignore by setting util to NULL.
+ */
+ if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) {
+ if (item && !has_sha1_file(ref->old_sha1) &&
+ !will_fetch(head, ref->old_sha1) &&
+ !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+ item = NULL;
+ continue;
+ }
+
+ /*
+ * If item is non-NULL here, then we previously saw a
+ * ref not followed by a peeled reference, so we need
+ * to check if it is a lightweight tag that we want to
+ * fetch.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ item = NULL;
+
+ /* skip duplicates and refs that we already have */
+ if (string_list_has_string(&remote_refs, ref->name) ||
+ string_list_has_string(&existing_refs, ref->name))
+ continue;
+
+ item = string_list_insert(ref->name, &remote_refs);
+ item->util = (void *)ref->old_sha1;
+ }
+ string_list_clear(&existing_refs, 0);
+
+ /*
+ * We may have a final lightweight tag that needs to be
+ * checked to see if it needs fetching.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ /*
+ * For all the tags in the remote_refs string list, call
+ * add_to_tail to add them to the list of refs to be fetched
+ */
+ for_each_string_list(add_to_tail, &remote_refs, &data);
+
+ string_list_clear(&remote_refs, 0);
+ }
+
+ static void check_not_current_branch(struct ref *ref_map)
+ {
+ struct branch *current_branch = branch_get(NULL);
+
+ if (is_bare_repository() || !current_branch)
+ return;
+
+ for (; ref_map; ref_map = ref_map->next)
+ if (ref_map->peer_ref && !strcmp(current_branch->refname,
+ ref_map->peer_ref->name))
+ die("Refusing to fetch into current branch %s "
+ "of non-bare repository", current_branch->refname);
+ }
+
++static int truncate_fetch_head(void)
++{
++ char *filename = git_path("FETCH_HEAD");
++ FILE *fp = fopen(filename, "w");
++
++ if (!fp)
++ return error("cannot open %s: %s\n", filename, strerror(errno));
++ fclose(fp);
++ return 0;
++}
++
+ static int do_fetch(struct transport *transport,
+ struct refspec *refs, int ref_count)
+ {
+ struct string_list existing_refs = { NULL, 0, 0, 0 };
+ struct string_list_item *peer_item = NULL;
+ struct ref *ref_map;
+ struct ref *rm;
+ int autotags = (transport->remote->fetch_tags == 1);
+
+ for_each_ref(add_existing, &existing_refs);
+
+ if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET)
+ tags = TAGS_SET;
+ if (transport->remote->fetch_tags == -1)
+ tags = TAGS_UNSET;
+
+ if (!transport->get_refs_list || !transport->fetch)
+ die("Don't know how to fetch from %s", transport->url);
+
+ /* if not appending, truncate FETCH_HEAD */
+ if (!append && !dry_run) {
- const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL };
- int argc = 1;
++ int errcode = truncate_fetch_head();
++ if (errcode)
++ return errcode;
+ }
+
+ ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
+ if (!update_head_ok)
+ check_not_current_branch(ref_map);
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->peer_ref) {
+ peer_item = string_list_lookup(rm->peer_ref->name,
+ &existing_refs);
+ if (peer_item)
+ hashcpy(rm->peer_ref->old_sha1,
+ peer_item->util);
+ }
+ }
+
+ if (tags == TAGS_DEFAULT && autotags)
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (fetch_refs(transport, ref_map)) {
+ free_refs(ref_map);
+ return 1;
+ }
+ if (prune)
+ prune_refs(transport, ref_map);
+ free_refs(ref_map);
+
+ /* if neither --no-tags nor --tags was specified, do automated tag
+ * following ... */
+ if (tags == TAGS_DEFAULT && autotags) {
+ struct ref **tail = &ref_map;
+ ref_map = NULL;
+ find_non_local_tags(transport, &ref_map, &tail);
+ if (ref_map) {
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+ }
+ free_refs(ref_map);
+ }
+
+ return 0;
+ }
+
+ static void set_option(const char *name, const char *value)
+ {
+ int r = transport_set_option(transport, name, value);
+ if (r < 0)
+ die("Option \"%s\" value \"%s\" is not valid for %s",
+ name, value, transport->url);
+ if (r > 0)
+ warning("Option \"%s\" is ignored for %s\n",
+ name, transport->url);
+ }
+
+ static int get_one_remote_for_fetch(struct remote *remote, void *priv)
+ {
+ struct string_list *list = priv;
+ if (!remote->skip_default_update)
+ string_list_append(remote->name, list);
+ return 0;
+ }
+
+ struct remote_group_data {
+ const char *name;
+ struct string_list *list;
+ };
+
+ static int get_remote_group(const char *key, const char *value, void *priv)
+ {
+ struct remote_group_data *g = priv;
+
+ if (!prefixcmp(key, "remotes.") &&
+ !strcmp(key + 8, g->name)) {
+ /* split list by white space */
+ int space = strcspn(value, " \t\n");
+ while (*value) {
+ if (space > 1) {
+ string_list_append(xstrndup(value, space),
+ g->list);
+ }
+ value += space + (value[space] != '\0');
+ space = strcspn(value, " \t\n");
+ }
+ }
+
+ return 0;
+ }
+
+ static int add_remote_or_group(const char *name, struct string_list *list)
+ {
+ int prev_nr = list->nr;
+ struct remote_group_data g = { name, list };
+
+ git_config(get_remote_group, &g);
+ if (list->nr == prev_nr) {
+ struct remote *remote;
+ if (!remote_is_configured(name))
+ return 0;
+ remote = remote_get(name);
+ string_list_append(remote->name, list);
+ }
+ return 1;
+ }
+
+ static int fetch_multiple(struct string_list *list)
+ {
+ int i, result = 0;
++ const char *argv[11] = { "fetch", "--append" };
++ int argc = 2;
+
+ if (dry_run)
+ argv[argc++] = "--dry-run";
+ if (prune)
+ argv[argc++] = "--prune";
++ if (update_head_ok)
++ argv[argc++] = "--update-head-ok";
++ if (force)
++ argv[argc++] = "--force";
++ if (keep)
++ argv[argc++] = "--keep";
+ if (verbosity >= 2)
+ argv[argc++] = "-v";
+ if (verbosity >= 1)
+ argv[argc++] = "-v";
+ else if (verbosity < 0)
+ argv[argc++] = "-q";
+
++ if (!append && !dry_run) {
++ int errcode = truncate_fetch_head();
++ if (errcode)
++ return errcode;
++ }
++
+ for (i = 0; i < list->nr; i++) {
+ const char *name = list->items[i].string;
+ argv[argc] = name;
++ argv[argc + 1] = NULL;
+ if (verbosity >= 0)
+ printf("Fetching %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+ error("Could not fetch %s", name);
+ result = 1;
+ }
+ }
+
+ return result;
+ }
+
+ static int fetch_one(struct remote *remote, int argc, const char **argv)
+ {
+ int i;
+ static const char **refs = NULL;
+ int ref_nr = 0;
+ int exit_code;
+
+ if (!remote)
+ die("Where do you want to fetch from today?");
+
+ transport = transport_get(remote, NULL);
+ if (verbosity >= 2)
+ transport->verbose = verbosity <= 3 ? verbosity : 3;
+ if (verbosity < 0)
+ transport->verbose = -1;
+ if (upload_pack)
+ set_option(TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(TRANS_OPT_DEPTH, depth);
+
+ if (argc > 0) {
+ int j = 0;
+ refs = xcalloc(argc + 1, sizeof(const char *));
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "tag")) {
+ char *ref;
+ i++;
+ if (i >= argc)
+ die("You need to specify a tag name.");
+ ref = xmalloc(strlen(argv[i]) * 2 + 22);
+ strcpy(ref, "refs/tags/");
+ strcat(ref, argv[i]);
+ strcat(ref, ":refs/tags/");
+ strcat(ref, argv[i]);
+ refs[j++] = ref;
+ } else
+ refs[j++] = argv[i];
+ }
+ refs[j] = NULL;
+ ref_nr = j;
+ }
+
+ sigchain_push_common(unlock_pack_on_signal);
+ atexit(unlock_pack);
+ exit_code = do_fetch(transport,
+ parse_fetch_refspec(ref_nr, refs), ref_nr);
+ transport_disconnect(transport);
+ transport = NULL;
+ return exit_code;
+ }
+
+ int cmd_fetch(int argc, const char **argv, const char *prefix)
+ {
+ int i;
+ struct string_list list = { NULL, 0, 0, 0 };
+ struct remote *remote;
+ int result = 0;
+
+ /* Record the command line for the reflog */
+ strbuf_addstr(&default_rla, "fetch");
+ for (i = 1; i < argc; i++)
+ strbuf_addf(&default_rla, " %s", argv[i]);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_fetch_options, builtin_fetch_usage, 0);
+
+ if (all) {
+ if (argc == 1)
+ die("fetch --all does not take a repository argument");
+ else if (argc > 1)
+ die("fetch --all does not make sense with refspecs");
+ (void) for_each_remote(get_one_remote_for_fetch, &list);
+ result = fetch_multiple(&list);
+ } else if (argc == 0) {
+ /* No arguments -- use default remote */
+ remote = remote_get(NULL);
+ result = fetch_one(remote, argc, argv);
+ } else if (multiple) {
+ /* All arguments are assumed to be remotes or groups */
+ for (i = 0; i < argc; i++)
+ if (!add_remote_or_group(argv[i], &list))
+ die("No such remote or remote group: %s", argv[i]);
+ result = fetch_multiple(&list);
+ } else {
+ /* Single remote or group */
+ (void) add_remote_or_group(argv[0], &list);
+ if (list.nr > 1) {
+ /* More than one remote */
+ if (argc > 1)
+ die("Fetching a group and specifying refspecs does not make sense");
+ result = fetch_multiple(&list);
+ } else {
+ /* Zero or one remotes */
+ remote = remote_get(argv[0]);
+ result = fetch_one(remote, argc-1, argv+1);
+ }
+ }
+
+ /* All names were strdup()ed or strndup()ed */
+ list.strdup_strings = 1;
+ string_list_clear(&list, 0);
+
+ return result;
+ }
diff --cc builtin/for-each-ref.c
index 0000000000000000000000000000000000000000,a5a83f14693b94adf3ae0dbc1b500b2e6b2be54d..62be1bbfd6659f9dfac73a17acd1e2d5322dac66
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
+++ b/builtin/for-each-ref.c
-static int used_atom_cnt, sort_atom_limit, need_tagged;
+ #include "builtin.h"
+ #include "cache.h"
+ #include "refs.h"
+ #include "object.h"
+ #include "tag.h"
+ #include "commit.h"
+ #include "tree.h"
+ #include "blob.h"
+ #include "quote.h"
+ #include "parse-options.h"
+ #include "remote.h"
+
+ /* Quoting styles */
+ #define QUOTE_NONE 0
+ #define QUOTE_SHELL 1
+ #define QUOTE_PERL 2
+ #define QUOTE_PYTHON 4
+ #define QUOTE_TCL 8
+
+ typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+ struct atom_value {
+ const char *s;
+ unsigned long ul; /* used for sorting when not FIELD_STR */
+ };
+
+ struct ref_sort {
+ struct ref_sort *next;
+ int atom; /* index into used_atom array */
+ unsigned reverse : 1;
+ };
+
+ struct refinfo {
+ char *refname;
+ unsigned char objectname[20];
++ int flag;
++ const char *symref;
+ struct atom_value *value;
+ };
+
+ static struct {
+ const char *name;
+ cmp_type cmp_type;
+ } valid_atom[] = {
+ { "refname" },
+ { "objecttype" },
+ { "objectsize", FIELD_ULONG },
+ { "objectname" },
+ { "tree" },
+ { "parent" },
+ { "numparent", FIELD_ULONG },
+ { "object" },
+ { "type" },
+ { "tag" },
+ { "author" },
+ { "authorname" },
+ { "authoremail" },
+ { "authordate", FIELD_TIME },
+ { "committer" },
+ { "committername" },
+ { "committeremail" },
+ { "committerdate", FIELD_TIME },
+ { "tagger" },
+ { "taggername" },
+ { "taggeremail" },
+ { "taggerdate", FIELD_TIME },
+ { "creator" },
+ { "creatordate", FIELD_TIME },
+ { "subject" },
+ { "body" },
+ { "contents" },
+ { "upstream" },
++ { "symref" },
++ { "flag" },
+ };
+
+ /*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects. refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+ static const char **used_atom;
+ static cmp_type *used_atom_type;
- /* %( is the start of an atom;
++static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
+
+ /*
+ * Used to parse format string and sort specifiers
+ */
+ static int parse_atom(const char *atom, const char *ep)
+ {
+ const char *sp;
+ int i, at;
+
+ sp = atom;
+ if (*sp == '*' && sp < ep)
+ sp++; /* deref */
+ if (ep <= sp)
+ die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i]);
+ if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+ return i;
+ }
+
+ /* Is the atom a valid one? */
+ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+ int len = strlen(valid_atom[i].name);
+ /*
+ * If the atom name has a colon, strip it and everything after
+ * it off - it specifies the format for this entry, and
+ * shouldn't be used for checking against the valid_atom
+ * table.
+ */
+ const char *formatp = strchr(sp, ':');
+ if (!formatp || ep < formatp)
+ formatp = ep;
+ if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
+ break;
+ }
+
+ if (ARRAY_SIZE(valid_atom) <= i)
+ die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+ /* Add it in, including the deref prefix */
+ at = used_atom_cnt;
+ used_atom_cnt++;
+ used_atom = xrealloc(used_atom,
+ (sizeof *used_atom) * used_atom_cnt);
+ used_atom_type = xrealloc(used_atom_type,
+ (sizeof(*used_atom_type) * used_atom_cnt));
+ used_atom[at] = xmemdupz(atom, ep - atom);
+ used_atom_type[at] = valid_atom[i].cmp_type;
++ if (*atom == '*')
++ need_tagged = 1;
++ if (!strcmp(used_atom[at], "symref"))
++ need_symref = 1;
+ return at;
+ }
+
+ /*
+ * In a format string, find the next occurrence of %(atom).
+ */
+ static const char *find_next(const char *cp)
+ {
+ while (*cp) {
+ if (*cp == '%') {
- /* For a tag or a commit object, if "creator" or "creatordate" is
++ /*
++ * %( is the start of an atom;
+ * %% is a quoted per-cent.
+ */
+ if (cp[1] == '(')
+ return cp;
+ else if (cp[1] == '%')
+ cp++; /* skip over two % */
+ /* otherwise this is a singleton, literal % */
+ }
+ cp++;
+ }
+ return NULL;
+ }
+
+ /*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+ static int verify_format(const char *format)
+ {
+ const char *cp, *sp;
+ for (cp = format; *cp && (sp = find_next(cp)); ) {
+ const char *ep = strchr(sp, ')');
+ if (!ep)
+ return error("malformed format string %s", sp);
+ /* sp points at "%(" and ep points at the closing ")" */
+ parse_atom(sp + 2, ep);
+ cp = ep + 1;
+ }
+ return 0;
+ }
+
+ /*
+ * Given an object name, read the object data and size, and return a
+ * "struct object". If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+ static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+ {
+ enum object_type type;
+ void *buf = read_sha1_file(sha1, &type, sz);
+
+ if (buf)
+ *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+ else
+ *obj = NULL;
+ return buf;
+ }
+
+ /* See grab_values */
+ static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ int i;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "objecttype"))
+ v->s = typename(obj->type);
+ else if (!strcmp(name, "objectsize")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", sz);
+ v->ul = sz;
+ v->s = s;
+ }
+ else if (!strcmp(name, "objectname")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(obj->sha1));
+ v->s = s;
+ }
+ }
+ }
+
+ /* See grab_values */
+ static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ int i;
+ struct tag *tag = (struct tag *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tag"))
+ v->s = tag->tag;
+ else if (!strcmp(name, "type") && tag->tagged)
+ v->s = typename(tag->tagged->type);
+ else if (!strcmp(name, "object") && tag->tagged) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(tag->tagged->sha1));
+ v->s = s;
+ }
+ }
+ }
+
+ static int num_parents(struct commit *commit)
+ {
+ struct commit_list *parents;
+ int i;
+
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next)
+ i++;
+ return i;
+ }
+
+ /* See grab_values */
+ static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ int i;
+ struct commit *commit = (struct commit *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tree")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+ v->s = s;
+ }
+ if (!strcmp(name, "numparent")) {
+ char *s = xmalloc(40);
+ v->ul = num_parents(commit);
+ sprintf(s, "%lu", v->ul);
+ v->s = s;
+ }
+ else if (!strcmp(name, "parent")) {
+ int num = num_parents(commit);
+ int i;
+ struct commit_list *parents;
+ char *s = xmalloc(41 * num + 1);
+ v->s = s;
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next, i = i + 41) {
+ struct commit *parent = parents->item;
+ strcpy(s+i, sha1_to_hex(parent->object.sha1));
+ if (parents->next)
+ s[i+40] = ' ';
+ }
+ if (!i)
+ *s = '\0';
+ }
+ }
+ }
+
+ static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+ {
+ const char *eol;
+ while (*buf) {
+ if (!strncmp(buf, who, wholen) &&
+ buf[wholen] == ' ')
+ return buf + wholen + 1;
+ eol = strchr(buf, '\n');
+ if (!eol)
+ return "";
+ eol++;
+ if (*eol == '\n')
+ return ""; /* end of header */
+ buf = eol;
+ }
+ return "";
+ }
+
+ static const char *copy_line(const char *buf)
+ {
+ const char *eol = strchrnul(buf, '\n');
+ return xmemdupz(buf, eol - buf);
+ }
+
+ static const char *copy_name(const char *buf)
+ {
+ const char *cp;
+ for (cp = buf; *cp && *cp != '\n'; cp++) {
+ if (!strncmp(cp, " <", 2))
+ return xmemdupz(buf, cp - buf);
+ }
+ return "";
+ }
+
+ static const char *copy_email(const char *buf)
+ {
+ const char *email = strchr(buf, '<');
+ const char *eoemail;
+ if (!email)
+ return "";
+ eoemail = strchr(email, '>');
+ if (!eoemail)
+ return "";
+ return xmemdupz(email, eoemail + 1 - email);
+ }
+
+ static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
+ {
+ const char *eoemail = strstr(buf, "> ");
+ char *zone;
+ unsigned long timestamp;
+ long tz;
+ enum date_mode date_mode = DATE_NORMAL;
+ const char *formatp;
+
+ /*
+ * We got here because atomname ends in "date" or "date<something>";
+ * it's not possible that <something> is not ":<format>" because
+ * parse_atom() wouldn't have allowed it, so we can assume that no
+ * ":" means no format is specified, and use the default.
+ */
+ formatp = strchr(atomname, ':');
+ if (formatp != NULL) {
+ formatp++;
+ date_mode = parse_date_format(formatp);
+ }
+
+ if (!eoemail)
+ goto bad;
+ timestamp = strtoul(eoemail + 2, &zone, 10);
+ if (timestamp == ULONG_MAX)
+ goto bad;
+ tz = strtol(zone, NULL, 10);
+ if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+ goto bad;
+ v->s = xstrdup(show_date(timestamp, tz, date_mode));
+ v->ul = timestamp;
+ return;
+ bad:
+ v->s = "";
+ v->ul = 0;
+ }
+
+ /* See grab_values */
+ static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ int i;
+ int wholen = strlen(who);
+ const char *wholine = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strncmp(who, name, wholen))
+ continue;
+ if (name[wholen] != 0 &&
+ strcmp(name + wholen, "name") &&
+ strcmp(name + wholen, "email") &&
+ prefixcmp(name + wholen, "date"))
+ continue;
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return; /* no point looking for it */
+ if (name[wholen] == 0)
+ v->s = copy_line(wholine);
+ else if (!strcmp(name + wholen, "name"))
+ v->s = copy_name(wholine);
+ else if (!strcmp(name + wholen, "email"))
+ v->s = copy_email(wholine);
+ else if (!prefixcmp(name + wholen, "date"))
+ grab_date(wholine, v, name);
+ }
+
-/* We want to have empty print-string for field requests
++ /*
++ * For a tag or a commit object, if "creator" or "creatordate" is
+ * requested, do something special.
+ */
+ if (strcmp(who, "tagger") && strcmp(who, "committer"))
+ return; /* "author" for commit object is not wanted */
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return;
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+
+ if (!prefixcmp(name, "creatordate"))
+ grab_date(wholine, v, name);
+ else if (!strcmp(name, "creator"))
+ v->s = copy_line(wholine);
+ }
+ }
+
+ static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+ {
+ while (*buf) {
+ const char *eol = strchr(buf, '\n');
+ if (!eol)
+ return;
+ if (eol[1] == '\n') {
+ buf = eol + 1;
+ break; /* found end of header */
+ }
+ buf = eol + 1;
+ }
+ while (*buf == '\n')
+ buf++;
+ if (!*buf)
+ return;
+ *sub = buf; /* first non-empty line */
+ buf = strchr(buf, '\n');
+ if (!buf) {
+ *body = "";
+ return; /* no body */
+ }
+ while (*buf == '\n')
+ buf++; /* skip blank between subject and body */
+ *body = buf;
+ }
+
+ /* See grab_values */
+ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ int i;
+ const char *subpos = NULL, *bodypos = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strcmp(name, "subject") &&
+ strcmp(name, "body") &&
+ strcmp(name, "contents"))
+ continue;
+ if (!subpos)
+ find_subpos(buf, sz, &subpos, &bodypos);
+ if (!subpos)
+ return;
+
+ if (!strcmp(name, "subject"))
+ v->s = copy_line(subpos);
+ else if (!strcmp(name, "body"))
+ v->s = xstrdup(bodypos);
+ else if (!strcmp(name, "contents"))
+ v->s = xstrdup(subpos);
+ }
+ }
+
- /* If there is no atom that wants to know about tagged
++/*
++ * We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+ static void fill_missing_values(struct atom_value *val)
+ {
+ int i;
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &val[i];
+ if (v->s == NULL)
+ v->s = "";
+ }
+ }
+
+ /*
+ * val is a list of atom_value to hold returned values. Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+ static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+ {
+ grab_common_values(val, deref, obj, buf, sz);
+ switch (obj->type) {
+ case OBJ_TAG:
+ grab_tag_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("tagger", val, deref, obj, buf, sz);
+ break;
+ case OBJ_COMMIT:
+ grab_commit_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("author", val, deref, obj, buf, sz);
+ grab_person("committer", val, deref, obj, buf, sz);
+ break;
+ case OBJ_TREE:
+ // grab_tree_values(val, deref, obj, buf, sz);
+ break;
+ case OBJ_BLOB:
+ // grab_blob_values(val, deref, obj, buf, sz);
+ break;
+ default:
+ die("Eh? Object of type %d?", obj->type);
+ }
+ }
+
++static inline char *copy_advance(char *dst, const char *src)
++{
++ while (*src)
++ *dst++ = *src++;
++ return dst;
++}
++
+ /*
+ * Parse the object referred by ref, and grab needed value.
+ */
+ static void populate_value(struct refinfo *ref)
+ {
+ void *buf;
+ struct object *obj;
+ int eaten, i;
+ unsigned long size;
+ const unsigned char *tagged;
+
+ ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
++ if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
++ unsigned char unused1[20];
++ const char *symref;
++ symref = resolve_ref(ref->refname, unused1, 1, NULL);
++ if (symref)
++ ref->symref = xstrdup(symref);
++ else
++ ref->symref = "";
++ }
++
+ /* Fill in specials first */
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &ref->value[i];
+ int deref = 0;
+ const char *refname;
+ const char *formatp;
+
+ if (*name == '*') {
+ deref = 1;
+ name++;
+ }
+
+ if (!prefixcmp(name, "refname"))
+ refname = ref->refname;
++ else if (!prefixcmp(name, "symref"))
++ refname = ref->symref ? ref->symref : "";
+ else if (!prefixcmp(name, "upstream")) {
+ struct branch *branch;
+ /* only local branches may have an upstream */
+ if (prefixcmp(ref->refname, "refs/heads/"))
+ continue;
+ branch = branch_get(ref->refname + 11);
+
+ if (!branch || !branch->merge || !branch->merge[0] ||
+ !branch->merge[0]->dst)
+ continue;
+ refname = branch->merge[0]->dst;
+ }
++ else if (!strcmp(name, "flag")) {
++ char buf[256], *cp = buf;
++ if (ref->flag & REF_ISSYMREF)
++ cp = copy_advance(cp, ",symref");
++ if (ref->flag & REF_ISPACKED)
++ cp = copy_advance(cp, ",packed");
++ if (cp == buf)
++ v->s = "";
++ else {
++ *cp = '\0';
++ v->s = xstrdup(buf + 1);
++ }
++ continue;
++ }
+ else
+ continue;
+
+ formatp = strchr(name, ':');
+ /* look for "short" refname format */
+ if (formatp) {
+ formatp++;
+ if (!strcmp(formatp, "short"))
+ refname = shorten_unambiguous_ref(refname,
+ warn_ambiguous_refs);
+ else
+ die("unknown %.*s format %s",
+ (int)(formatp - name), name, formatp);
+ }
+
+ if (!deref)
+ v->s = refname;
+ else {
+ int len = strlen(refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", refname);
+ v->s = s;
+ }
+ }
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &ref->value[i];
+ if (v->s == NULL)
+ goto need_obj;
+ }
+ return;
+
+ need_obj:
+ buf = get_obj(ref->objectname, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+
+ grab_values(ref->value, 0, obj, buf, size);
+ if (!eaten)
+ free(buf);
+
- /* If it is a tag object, see if we use a value that derefs
++ /*
++ * If there is no atom that wants to know about tagged
+ * object, we are done.
+ */
+ if (!need_tagged || (obj->type != OBJ_TAG))
+ return;
+
- /* NEEDSWORK: This derefs tag only once, which
++ /*
++ * If it is a tag object, see if we use a value that derefs
+ * the object, and if we do grab the object it refers to.
+ */
+ tagged = ((struct tag *)obj)->tagged->sha1;
+
- * A call-back given to for_each_ref(). It is unfortunate that we
- * need to use global variables to pass extra information to this
- * function.
++ /*
++ * NEEDSWORK: This derefs tag only once, which
+ * is good to deal with chains of trust, but
+ * is not consistent with what deref_tag() does
+ * which peels the onion to the core.
+ */
+ buf = get_obj(tagged, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ grab_values(ref->value, 1, obj, buf, size);
+ if (!eaten)
+ free(buf);
+ }
+
+ /*
+ * Given a ref, return the value for the atom. This lazily gets value
+ * out of the object by calling populate value.
+ */
+ static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+ {
+ if (!ref->value) {
+ populate_value(ref);
+ fill_missing_values(ref->value);
+ }
+ *v = &ref->value[atom];
+ }
+
+ struct grab_ref_cbdata {
+ struct refinfo **grab_array;
+ const char **grab_pattern;
+ int grab_cnt;
+ };
+
+ /*
- /* We do not open the object yet; sort may only need refname
++ * A call-back given to for_each_ref(). Filter refs and keep them for
++ * later object processing.
+ */
+ static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ struct grab_ref_cbdata *cb = cb_data;
+ struct refinfo *ref;
+ int cnt;
+
+ if (*cb->grab_pattern) {
+ const char **pattern;
+ int namelen = strlen(refname);
+ for (pattern = cb->grab_pattern; *pattern; pattern++) {
+ const char *p = *pattern;
+ int plen = strlen(p);
+
+ if ((plen <= namelen) &&
+ !strncmp(refname, p, plen) &&
+ (refname[plen] == '\0' ||
+ refname[plen] == '/' ||
+ p[plen-1] == '/'))
+ break;
+ if (!fnmatch(p, refname, FNM_PATHNAME))
+ break;
+ }
+ if (!*pattern)
+ return 0;
+ }
+
- for (i = 0; i < used_atom_cnt; i++) {
- if (used_atom[i][0] == '*') {
- need_tagged = 1;
- break;
- }
- }
-
++ /*
++ * We do not open the object yet; sort may only need refname
+ * to do its job and the resulting list may yet to be pruned
+ * by maxcount logic.
+ */
+ ref = xcalloc(1, sizeof(*ref));
+ ref->refname = xstrdup(refname);
+ hashcpy(ref->objectname, sha1);
++ ref->flag = flag;
+
+ cnt = cb->grab_cnt;
+ cb->grab_array = xrealloc(cb->grab_array,
+ sizeof(*cb->grab_array) * (cnt + 1));
+ cb->grab_array[cnt++] = ref;
+ cb->grab_cnt = cnt;
+ return 0;
+ }
+
+ static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+ {
+ struct atom_value *va, *vb;
+ int cmp;
+ cmp_type cmp_type = used_atom_type[s->atom];
+
+ get_value(a, s->atom, &va);
+ get_value(b, s->atom, &vb);
+ switch (cmp_type) {
+ case FIELD_STR:
+ cmp = strcmp(va->s, vb->s);
+ break;
+ default:
+ if (va->ul < vb->ul)
+ cmp = -1;
+ else if (va->ul == vb->ul)
+ cmp = 0;
+ else
+ cmp = 1;
+ break;
+ }
+ return (s->reverse) ? -cmp : cmp;
+ }
+
+ static struct ref_sort *ref_sort;
+ static int compare_refs(const void *a_, const void *b_)
+ {
+ struct refinfo *a = *((struct refinfo **)a_);
+ struct refinfo *b = *((struct refinfo **)b_);
+ struct ref_sort *s;
+
+ for (s = ref_sort; s; s = s->next) {
+ int cmp = cmp_ref_sort(s, a, b);
+ if (cmp)
+ return cmp;
+ }
+ return 0;
+ }
+
+ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+ {
+ ref_sort = sort;
+ qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+ }
+
+ static void print_value(struct refinfo *ref, int atom, int quote_style)
+ {
+ struct atom_value *v;
+ get_value(ref, atom, &v);
+ switch (quote_style) {
+ case QUOTE_NONE:
+ fputs(v->s, stdout);
+ break;
+ case QUOTE_SHELL:
+ sq_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PERL:
+ perl_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PYTHON:
+ python_quote_print(stdout, v->s);
+ break;
+ case QUOTE_TCL:
+ tcl_quote_print(stdout, v->s);
+ break;
+ }
+ }
+
+ static int hex1(char ch)
+ {
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ else if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+ else if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+ return -1;
+ }
+ static int hex2(const char *cp)
+ {
+ if (cp[0] && cp[1])
+ return (hex1(cp[0]) << 4) | hex1(cp[1]);
+ else
+ return -1;
+ }
+
+ static void emit(const char *cp, const char *ep)
+ {
+ while (*cp && (!ep || cp < ep)) {
+ if (*cp == '%') {
+ if (cp[1] == '%')
+ cp++;
+ else {
+ int ch = hex2(cp + 1);
+ if (0 <= ch) {
+ putchar(ch);
+ cp += 3;
+ continue;
+ }
+ }
+ }
+ putchar(*cp);
+ cp++;
+ }
+ }
+
+ static void show_ref(struct refinfo *info, const char *format, int quote_style)
+ {
+ const char *cp, *sp, *ep;
+
+ for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+ ep = strchr(sp, ')');
+ if (cp < sp)
+ emit(cp, sp);
+ print_value(info, parse_atom(sp + 2, ep), quote_style);
+ }
+ if (*cp) {
+ sp = cp + strlen(cp);
+ emit(cp, sp);
+ }
+ putchar('\n');
+ }
+
+ static struct ref_sort *default_sort(void)
+ {
+ static const char cstr_name[] = "refname";
+
+ struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+ sort->next = NULL;
+ sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+ return sort;
+ }
+
+ static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
+ {
+ struct ref_sort **sort_tail = opt->value;
+ struct ref_sort *s;
+ int len;
+
+ if (!arg) /* should --no-sort void the list ? */
+ return -1;
+
+ *sort_tail = s = xcalloc(1, sizeof(*s));
+
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ len = strlen(arg);
+ s->atom = parse_atom(arg, arg+len);
+ return 0;
+ }
+
+ static char const * const for_each_ref_usage[] = {
+ "git for-each-ref [options] [<pattern>]",
+ NULL
+ };
+
+ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
+ {
+ int i, num_refs;
+ const char *format = "%(objectname) %(objecttype)\t%(refname)";
+ struct ref_sort *sort = NULL, **sort_tail = &sort;
+ int maxcount = 0, quote_style = 0;
+ struct refinfo **refs;
+ struct grab_ref_cbdata cbdata;
+
+ struct option opts[] = {
+ OPT_BIT('s', "shell", "e_style,
+ "quote placeholders suitably for shells", QUOTE_SHELL),
+ OPT_BIT('p', "perl", "e_style,
+ "quote placeholders suitably for perl", QUOTE_PERL),
+ OPT_BIT(0 , "python", "e_style,
+ "quote placeholders suitably for python", QUOTE_PYTHON),
+ OPT_BIT(0 , "tcl", "e_style,
+ "quote placeholders suitably for tcl", QUOTE_TCL),
+
+ OPT_GROUP(""),
+ OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
+ OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
+ OPT_CALLBACK(0 , "sort", sort_tail, "key",
+ "field name to sort on", &opt_parse_sort),
+ OPT_END(),
+ };
+
+ parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+ if (maxcount < 0) {
+ error("invalid --count argument: `%d'", maxcount);
+ usage_with_options(for_each_ref_usage, opts);
+ }
+ if (HAS_MULTI_BITS(quote_style)) {
+ error("more than one quoting style?");
+ usage_with_options(for_each_ref_usage, opts);
+ }
+ if (verify_format(format))
+ usage_with_options(for_each_ref_usage, opts);
+
+ if (!sort)
+ sort = default_sort();
+ sort_atom_limit = used_atom_cnt;
+
+ /* for warn_ambiguous_refs */
+ git_config(git_default_config, NULL);
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.grab_pattern = argv;
+ for_each_rawref(grab_single_ref, &cbdata);
+ refs = cbdata.grab_array;
+ num_refs = cbdata.grab_cnt;
+
+ sort_refs(sort, refs, num_refs);
+
+ if (!maxcount || num_refs < maxcount)
+ maxcount = num_refs;
+ for (i = 0; i < maxcount; i++)
+ show_ref(refs[i], format, quote_style);
+ return 0;
+ }
diff --cc builtin/grep.c
index 0000000000000000000000000000000000000000,552ef1face1e25c11d5f114c8d5c1265c6c3ae9b..40b9a93127482bebf6dc8c9eb39b2104711a543a
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/grep.c
+++ b/builtin/grep.c
- OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1),
+ /*
+ * Builtin "git grep"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+ #include "cache.h"
+ #include "blob.h"
+ #include "tree.h"
+ #include "commit.h"
+ #include "tag.h"
+ #include "tree-walk.h"
+ #include "builtin.h"
+ #include "parse-options.h"
+ #include "userdiff.h"
+ #include "grep.h"
+ #include "quote.h"
+ #include "dir.h"
+
+ #ifndef NO_PTHREADS
+ #include "thread-utils.h"
+ #include <pthread.h>
+ #endif
+
+ static char const * const grep_usage[] = {
+ "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
+ NULL
+ };
+
+ static int use_threads = 1;
+
+ #ifndef NO_PTHREADS
+ #define THREADS 8
+ static pthread_t threads[THREADS];
+
+ static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+ const char *name);
+ static void *load_file(const char *filename, size_t *sz);
+
+ enum work_type {WORK_SHA1, WORK_FILE};
+
+ /* We use one producer thread and THREADS consumer
+ * threads. The producer adds struct work_items to 'todo' and the
+ * consumers pick work items from the same array.
+ */
+ struct work_item
+ {
+ enum work_type type;
+ char *name;
+
+ /* if type == WORK_SHA1, then 'identifier' is a SHA1,
+ * otherwise type == WORK_FILE, and 'identifier' is a NUL
+ * terminated filename.
+ */
+ void *identifier;
+ char done;
+ struct strbuf out;
+ };
+
+ /* In the range [todo_done, todo_start) in 'todo' we have work_items
+ * that have been or are processed by a consumer thread. We haven't
+ * written the result for these to stdout yet.
+ *
+ * The work_items in [todo_start, todo_end) are waiting to be picked
+ * up by a consumer thread.
+ *
+ * The ranges are modulo TODO_SIZE.
+ */
+ #define TODO_SIZE 128
+ static struct work_item todo[TODO_SIZE];
+ static int todo_start;
+ static int todo_end;
+ static int todo_done;
+
+ /* Has all work items been added? */
+ static int all_work_added;
+
+ /* This lock protects all the variables above. */
+ static pthread_mutex_t grep_mutex;
+
+ /* Used to serialize calls to read_sha1_file. */
+ static pthread_mutex_t read_sha1_mutex;
+
+ #define grep_lock() pthread_mutex_lock(&grep_mutex)
+ #define grep_unlock() pthread_mutex_unlock(&grep_mutex)
+ #define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
+ #define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+
+ /* Signalled when a new work_item is added to todo. */
+ static pthread_cond_t cond_add;
+
+ /* Signalled when the result from one work_item is written to
+ * stdout.
+ */
+ static pthread_cond_t cond_write;
+
+ /* Signalled when we are finished with everything. */
+ static pthread_cond_t cond_result;
+
+ static void add_work(enum work_type type, char *name, void *id)
+ {
+ grep_lock();
+
+ while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
+ pthread_cond_wait(&cond_write, &grep_mutex);
+ }
+
+ todo[todo_end].type = type;
+ todo[todo_end].name = name;
+ todo[todo_end].identifier = id;
+ todo[todo_end].done = 0;
+ strbuf_reset(&todo[todo_end].out);
+ todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
+
+ pthread_cond_signal(&cond_add);
+ grep_unlock();
+ }
+
+ static struct work_item *get_work(void)
+ {
+ struct work_item *ret;
+
+ grep_lock();
+ while (todo_start == todo_end && !all_work_added) {
+ pthread_cond_wait(&cond_add, &grep_mutex);
+ }
+
+ if (todo_start == todo_end && all_work_added) {
+ ret = NULL;
+ } else {
+ ret = &todo[todo_start];
+ todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
+ }
+ grep_unlock();
+ return ret;
+ }
+
+ static void grep_sha1_async(struct grep_opt *opt, char *name,
+ const unsigned char *sha1)
+ {
+ unsigned char *s;
+ s = xmalloc(20);
+ memcpy(s, sha1, 20);
+ add_work(WORK_SHA1, name, s);
+ }
+
+ static void grep_file_async(struct grep_opt *opt, char *name,
+ const char *filename)
+ {
+ add_work(WORK_FILE, name, xstrdup(filename));
+ }
+
+ static void work_done(struct work_item *w)
+ {
+ int old_done;
+
+ grep_lock();
+ w->done = 1;
+ old_done = todo_done;
+ for(; todo[todo_done].done && todo_done != todo_start;
+ todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
+ w = &todo[todo_done];
+ write_or_die(1, w->out.buf, w->out.len);
+ free(w->name);
+ free(w->identifier);
+ }
+
+ if (old_done != todo_done)
+ pthread_cond_signal(&cond_write);
+
+ if (all_work_added && todo_done == todo_end)
+ pthread_cond_signal(&cond_result);
+
+ grep_unlock();
+ }
+
+ static void *run(void *arg)
+ {
+ int hit = 0;
+ struct grep_opt *opt = arg;
+
+ while (1) {
+ struct work_item *w = get_work();
+ if (!w)
+ break;
+
+ opt->output_priv = w;
+ if (w->type == WORK_SHA1) {
+ unsigned long sz;
+ void* data = load_sha1(w->identifier, &sz, w->name);
+
+ if (data) {
+ hit |= grep_buffer(opt, w->name, data, sz);
+ free(data);
+ }
+ } else if (w->type == WORK_FILE) {
+ size_t sz;
+ void* data = load_file(w->identifier, &sz);
+ if (data) {
+ hit |= grep_buffer(opt, w->name, data, sz);
+ free(data);
+ }
+ } else {
+ assert(0);
+ }
+
+ work_done(w);
+ }
+ free_grep_patterns(arg);
+ free(arg);
+
+ return (void*) (intptr_t) hit;
+ }
+
+ static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
+ {
+ struct work_item *w = opt->output_priv;
+ strbuf_add(&w->out, buf, size);
+ }
+
+ static void start_threads(struct grep_opt *opt)
+ {
+ int i;
+
+ pthread_mutex_init(&grep_mutex, NULL);
+ pthread_mutex_init(&read_sha1_mutex, NULL);
+ pthread_cond_init(&cond_add, NULL);
+ pthread_cond_init(&cond_write, NULL);
+ pthread_cond_init(&cond_result, NULL);
+
+ for (i = 0; i < ARRAY_SIZE(todo); i++) {
+ strbuf_init(&todo[i].out, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ int err;
+ struct grep_opt *o = grep_opt_dup(opt);
+ o->output = strbuf_out;
+ compile_grep_patterns(o);
+ err = pthread_create(&threads[i], NULL, run, o);
+
+ if (err)
+ die("grep: failed to create thread: %s",
+ strerror(err));
+ }
+ }
+
+ static int wait_all(void)
+ {
+ int hit = 0;
+ int i;
+
+ grep_lock();
+ all_work_added = 1;
+
+ /* Wait until all work is done. */
+ while (todo_done != todo_end)
+ pthread_cond_wait(&cond_result, &grep_mutex);
+
+ /* Wake up all the consumer threads so they can see that there
+ * is no more work to do.
+ */
+ pthread_cond_broadcast(&cond_add);
+ grep_unlock();
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ void *h;
+ pthread_join(threads[i], &h);
+ hit |= (int) (intptr_t) h;
+ }
+
+ pthread_mutex_destroy(&grep_mutex);
+ pthread_mutex_destroy(&read_sha1_mutex);
+ pthread_cond_destroy(&cond_add);
+ pthread_cond_destroy(&cond_write);
+ pthread_cond_destroy(&cond_result);
+
+ return hit;
+ }
+ #else /* !NO_PTHREADS */
+ #define read_sha1_lock()
+ #define read_sha1_unlock()
+
+ static int wait_all(void)
+ {
+ return 0;
+ }
+ #endif
+
+ static int grep_config(const char *var, const char *value, void *cb)
+ {
+ struct grep_opt *opt = cb;
+
+ switch (userdiff_config(var, value)) {
+ case 0: break;
+ case -1: return -1;
+ default: return 0;
+ }
+
+ if (!strcmp(var, "color.grep")) {
+ opt->color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+ if (!strcmp(var, "color.grep.match")) {
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, opt->color_match);
+ return 0;
+ }
+ return git_color_default_config(var, value, cb);
+ }
+
+ /*
+ * Return non-zero if max_depth is negative or path has no more then max_depth
+ * slashes.
+ */
+ static int accept_subdir(const char *path, int max_depth)
+ {
+ if (max_depth < 0)
+ return 1;
+
+ while ((path = strchr(path, '/')) != NULL) {
+ max_depth--;
+ if (max_depth < 0)
+ return 0;
+ path++;
+ }
+ return 1;
+ }
+
+ /*
+ * Return non-zero if name is a subdirectory of match and is not too deep.
+ */
+ static int is_subdir(const char *name, int namelen,
+ const char *match, int matchlen, int max_depth)
+ {
+ if (matchlen > namelen || strncmp(name, match, matchlen))
+ return 0;
+
+ if (name[matchlen] == '\0') /* exact match */
+ return 1;
+
+ if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
+ return accept_subdir(name + matchlen + 1, max_depth);
+
+ return 0;
+ }
+
+ /*
+ * git grep pathspecs are somewhat different from diff-tree pathspecs;
+ * pathname wildcards are allowed.
+ */
+ static int pathspec_matches(const char **paths, const char *name, int max_depth)
+ {
+ int namelen, i;
+ if (!paths || !*paths)
+ return accept_subdir(name, max_depth);
+ namelen = strlen(name);
+ for (i = 0; paths[i]; i++) {
+ const char *match = paths[i];
+ int matchlen = strlen(match);
+ const char *cp, *meta;
+
+ if (is_subdir(name, namelen, match, matchlen, max_depth))
+ return 1;
+ if (!fnmatch(match, name, 0))
+ return 1;
+ if (name[namelen-1] != '/')
+ continue;
+
+ /* We are being asked if the directory ("name") is worth
+ * descending into.
+ *
+ * Find the longest leading directory name that does
+ * not have metacharacter in the pathspec; the name
+ * we are looking at must overlap with that directory.
+ */
+ for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
+ char ch = *cp;
+ if (ch == '*' || ch == '[' || ch == '?') {
+ meta = cp;
+ break;
+ }
+ }
+ if (!meta)
+ meta = cp; /* fully literal */
+
+ if (namelen <= meta - match) {
+ /* Looking at "Documentation/" and
+ * the pattern says "Documentation/howto/", or
+ * "Documentation/diff*.txt". The name we
+ * have should match prefix.
+ */
+ if (!memcmp(match, name, namelen))
+ return 1;
+ continue;
+ }
+
+ if (meta - match < namelen) {
+ /* Looking at "Documentation/howto/" and
+ * the pattern says "Documentation/h*";
+ * match up to "Do.../h"; this avoids descending
+ * into "Documentation/technical/".
+ */
+ if (!memcmp(match, name, meta - match))
+ return 1;
+ continue;
+ }
+ }
+ return 0;
+ }
+
+ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+ {
+ void *data;
+
+ if (use_threads) {
+ read_sha1_lock();
+ data = read_sha1_file(sha1, type, size);
+ read_sha1_unlock();
+ } else {
+ data = read_sha1_file(sha1, type, size);
+ }
+ return data;
+ }
+
+ static void *load_sha1(const unsigned char *sha1, unsigned long *size,
+ const char *name)
+ {
+ enum object_type type;
+ void *data = lock_and_read_sha1_file(sha1, &type, size);
+
+ if (!data)
+ error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+
+ return data;
+ }
+
+ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+ const char *filename, int tree_name_len)
+ {
+ struct strbuf pathbuf = STRBUF_INIT;
+ char *name;
+
+ if (opt->relative && opt->prefix_length) {
+ quote_path_relative(filename + tree_name_len, -1, &pathbuf,
+ opt->prefix);
+ strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+ } else {
+ strbuf_addstr(&pathbuf, filename);
+ }
+
+ name = strbuf_detach(&pathbuf, NULL);
+
+ #ifndef NO_PTHREADS
+ if (use_threads) {
+ grep_sha1_async(opt, name, sha1);
+ return 0;
+ } else
+ #endif
+ {
+ int hit;
+ unsigned long sz;
+ void *data = load_sha1(sha1, &sz, name);
+ if (!data)
+ hit = 0;
+ else
+ hit = grep_buffer(opt, name, data, sz);
+
+ free(data);
+ free(name);
+ return hit;
+ }
+ }
+
+ static void *load_file(const char *filename, size_t *sz)
+ {
+ struct stat st;
+ char *data;
+ int i;
+
+ if (lstat(filename, &st) < 0) {
+ err_ret:
+ if (errno != ENOENT)
+ error("'%s': %s", filename, strerror(errno));
+ return 0;
+ }
+ if (!S_ISREG(st.st_mode))
+ return 0;
+ *sz = xsize_t(st.st_size);
+ i = open(filename, O_RDONLY);
+ if (i < 0)
+ goto err_ret;
+ data = xmalloc(*sz + 1);
+ if (st.st_size != read_in_full(i, data, *sz)) {
+ error("'%s': short read %s", filename, strerror(errno));
+ close(i);
+ free(data);
+ return 0;
+ }
+ close(i);
+ data[*sz] = 0;
+ return data;
+ }
+
+ static int grep_file(struct grep_opt *opt, const char *filename)
+ {
+ struct strbuf buf = STRBUF_INIT;
+ char *name;
+
+ if (opt->relative && opt->prefix_length)
+ quote_path_relative(filename, -1, &buf, opt->prefix);
+ else
+ strbuf_addstr(&buf, filename);
+ name = strbuf_detach(&buf, NULL);
+
+ #ifndef NO_PTHREADS
+ if (use_threads) {
+ grep_file_async(opt, name, filename);
+ return 0;
+ } else
+ #endif
+ {
+ int hit;
+ size_t sz;
+ void *data = load_file(filename, &sz);
+ if (!data)
+ hit = 0;
+ else
+ hit = grep_buffer(opt, name, data, sz);
+
+ free(data);
+ free(name);
+ return hit;
+ }
+ }
+
+ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+ {
+ int hit = 0;
+ int nr;
+ read_cache();
+
+ for (nr = 0; nr < active_nr; nr++) {
+ struct cache_entry *ce = active_cache[nr];
+ if (!S_ISREG(ce->ce_mode))
+ continue;
+ if (!pathspec_matches(paths, ce->name, opt->max_depth))
+ continue;
+ /*
+ * If CE_VALID is on, we assume worktree file and its cache entry
+ * are identical, even if worktree file has been modified, so use
+ * cache version instead
+ */
+ if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
+ if (ce_stage(ce))
+ continue;
+ hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+ }
+ else
+ hit |= grep_file(opt, ce->name);
+ if (ce_stage(ce)) {
+ do {
+ nr++;
+ } while (nr < active_nr &&
+ !strcmp(ce->name, active_cache[nr]->name));
+ nr--; /* compensate for loop control */
+ }
+ if (hit && opt->status_only)
+ break;
+ }
+ free_grep_patterns(opt);
+ return hit;
+ }
+
+ static int grep_tree(struct grep_opt *opt, const char **paths,
+ struct tree_desc *tree,
+ const char *tree_name, const char *base)
+ {
+ int len;
+ int hit = 0;
+ struct name_entry entry;
+ char *down;
+ int tn_len = strlen(tree_name);
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, PATH_MAX + tn_len);
+
+ if (tn_len) {
+ strbuf_add(&pathbuf, tree_name, tn_len);
+ strbuf_addch(&pathbuf, ':');
+ tn_len = pathbuf.len;
+ }
+ strbuf_addstr(&pathbuf, base);
+ len = pathbuf.len;
+
+ while (tree_entry(tree, &entry)) {
+ int te_len = tree_entry_len(entry.path, entry.sha1);
+ pathbuf.len = len;
+ strbuf_add(&pathbuf, entry.path, te_len);
+
+ if (S_ISDIR(entry.mode))
+ /* Match "abc/" against pathspec to
+ * decide if we want to descend into "abc"
+ * directory.
+ */
+ strbuf_addch(&pathbuf, '/');
+
+ down = pathbuf.buf + tn_len;
+ if (!pathspec_matches(paths, down, opt->max_depth))
+ ;
+ else if (S_ISREG(entry.mode))
+ hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+ else if (S_ISDIR(entry.mode)) {
+ enum object_type type;
+ struct tree_desc sub;
+ void *data;
+ unsigned long size;
+
+ data = lock_and_read_sha1_file(entry.sha1, &type, &size);
+ if (!data)
+ die("unable to read tree (%s)",
+ sha1_to_hex(entry.sha1));
+ init_tree_desc(&sub, data, size);
+ hit |= grep_tree(opt, paths, &sub, tree_name, down);
+ free(data);
+ }
+ if (hit && opt->status_only)
+ break;
+ }
+ strbuf_release(&pathbuf);
+ return hit;
+ }
+
+ static int grep_object(struct grep_opt *opt, const char **paths,
+ struct object *obj, const char *name)
+ {
+ if (obj->type == OBJ_BLOB)
+ return grep_sha1(opt, obj->sha1, name, 0);
+ if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
+ struct tree_desc tree;
+ void *data;
+ unsigned long size;
+ int hit;
+ data = read_object_with_reference(obj->sha1, tree_type,
+ &size, NULL);
+ if (!data)
+ die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+ init_tree_desc(&tree, data, size);
+ hit = grep_tree(opt, paths, &tree, name, "");
+ free(data);
+ return hit;
+ }
+ die("unable to grep from object of type %s", typename(obj->type));
+ }
+
+ static int grep_directory(struct grep_opt *opt, const char **paths)
+ {
+ struct dir_struct dir;
+ int i, hit = 0;
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
+
+ fill_directory(&dir, paths);
+ for (i = 0; i < dir.nr; i++) {
+ hit |= grep_file(opt, dir.entries[i]->name);
+ if (hit && opt->status_only)
+ break;
+ }
+ free_grep_patterns(opt);
+ return hit;
+ }
+
+ static int context_callback(const struct option *opt, const char *arg,
+ int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ int value;
+ const char *endp;
+
+ if (unset) {
+ grep_opt->pre_context = grep_opt->post_context = 0;
+ return 0;
+ }
+ value = strtol(arg, (char **)&endp, 10);
+ if (*endp) {
+ return error("switch `%c' expects a numerical value",
+ opt->short_name);
+ }
+ grep_opt->pre_context = grep_opt->post_context = value;
+ return 0;
+ }
+
+ static int file_callback(const struct option *opt, const char *arg, int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ FILE *patterns;
+ int lno = 0;
+ struct strbuf sb = STRBUF_INIT;
+
+ patterns = fopen(arg, "r");
+ if (!patterns)
+ die_errno("cannot open '%s'", arg);
+ while (strbuf_getline(&sb, patterns, '\n') == 0) {
+ /* ignore empty line like grep does */
+ if (sb.len == 0)
+ continue;
+ append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg,
+ ++lno, GREP_PATTERN);
+ }
+ fclose(patterns);
+ strbuf_release(&sb);
+ return 0;
+ }
+
+ static int not_callback(const struct option *opt, const char *arg, int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT);
+ return 0;
+ }
+
+ static int and_callback(const struct option *opt, const char *arg, int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND);
+ return 0;
+ }
+
+ static int open_callback(const struct option *opt, const char *arg, int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN);
+ return 0;
+ }
+
+ static int close_callback(const struct option *opt, const char *arg, int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN);
+ return 0;
+ }
+
+ static int pattern_callback(const struct option *opt, const char *arg,
+ int unset)
+ {
+ struct grep_opt *grep_opt = opt->value;
+ append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN);
+ return 0;
+ }
+
+ static int help_callback(const struct option *opt, const char *arg, int unset)
+ {
+ return -1;
+ }
+
+ int cmd_grep(int argc, const char **argv, const char *prefix)
+ {
+ int hit = 0;
+ int cached = 0;
+ int seen_dashdash = 0;
+ int external_grep_allowed__ignored;
+ struct grep_opt opt;
+ struct object_array list = { 0, 0, NULL };
+ const char **paths = NULL;
+ int i;
+ int dummy;
+ int nongit = 0, use_index = 1;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "cached", &cached,
+ "search in index instead of in the work tree"),
+ OPT_BOOLEAN(0, "index", &use_index,
+ "--no-index finds in contents not managed by git"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('v', "invert-match", &opt.invert,
+ "show non-matching lines"),
+ OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
+ "case insensitive matching"),
+ OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
+ "match patterns only at word boundaries"),
+ OPT_SET_INT('a', "text", &opt.binary,
+ "process binary files as text", GREP_BINARY_TEXT),
+ OPT_SET_INT('I', NULL, &opt.binary,
+ "don't match patterns in binary files",
+ GREP_BINARY_NOMATCH),
+ { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
+ "descend at most <depth> levels", PARSE_OPT_NONEG,
+ NULL, 1 },
+ OPT_GROUP(""),
+ OPT_BIT('E', "extended-regexp", &opt.regflags,
+ "use extended POSIX regular expressions", REG_EXTENDED),
+ OPT_NEGBIT('G', "basic-regexp", &opt.regflags,
+ "use basic POSIX regular expressions (default)",
+ REG_EXTENDED),
+ OPT_BOOLEAN('F', "fixed-strings", &opt.fixed,
+ "interpret patterns as fixed strings"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"),
+ OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
+ OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
+ OPT_NEGBIT(0, "full-name", &opt.relative,
+ "show filenames relative to top directory", 1),
+ OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
+ "show only filenames instead of matching lines"),
+ OPT_BOOLEAN(0, "name-only", &opt.name_only,
+ "synonym for --files-with-matches"),
+ OPT_BOOLEAN('L', "files-without-match",
+ &opt.unmatch_name_only,
+ "show only the names of files without match"),
+ OPT_BOOLEAN('z', "null", &opt.null_following_name,
+ "print NUL after filenames"),
+ OPT_BOOLEAN('c', "count", &opt.count,
+ "show the number of matches instead of matching lines"),
++ OPT__COLOR(&opt.color, "highlight matches"),
+ OPT_GROUP(""),
+ OPT_CALLBACK('C', NULL, &opt, "n",
+ "show <n> context lines before and after matches",
+ context_callback),
+ OPT_INTEGER('B', NULL, &opt.pre_context,
+ "show <n> context lines before matches"),
+ OPT_INTEGER('A', NULL, &opt.post_context,
+ "show <n> context lines after matches"),
+ OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
+ context_callback),
+ OPT_BOOLEAN('p', "show-function", &opt.funcname,
+ "show a line with the function name before matches"),
+ OPT_GROUP(""),
+ OPT_CALLBACK('f', NULL, &opt, "file",
+ "read patterns from file", file_callback),
+ { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
+ "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
+ { OPTION_CALLBACK, 0, "and", &opt, NULL,
+ "combine patterns specified with -e",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+ OPT_BOOLEAN(0, "or", &dummy, ""),
+ { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
+ { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ open_callback },
+ { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ close_callback },
+ OPT_BOOLEAN('q', "quiet", &opt.status_only,
+ "indicate hit with exit status without output"),
+ OPT_BOOLEAN(0, "all-match", &opt.all_match,
+ "show only matches from files that match all patterns"),
+ OPT_GROUP(""),
+ OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
+ "allow calling of grep(1) (ignored by this build)"),
+ { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
+ OPT_END()
+ };
+
+ prefix = setup_git_directory_gently(&nongit);
+
+ /*
+ * 'git grep -h', unlike 'git grep -h <pattern>', is a request
+ * to show usage information and exit.
+ */
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(grep_usage, options);
+
+ memset(&opt, 0, sizeof(opt));
+ opt.prefix = prefix;
+ opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
+ opt.relative = 1;
+ opt.pathname = 1;
+ opt.pattern_tail = &opt.pattern_list;
++ opt.header_tail = &opt.header_list;
+ opt.regflags = REG_NEWLINE;
+ opt.max_depth = -1;
+
+ strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
+ opt.color = -1;
+ git_config(grep_config, &opt);
+ if (opt.color == -1)
+ opt.color = git_use_color_default;
+
+ /*
+ * If there is no -- then the paths must exist in the working
+ * tree. If there is no explicit pattern specified with -e or
+ * -f, we take the first unrecognized non option to be the
+ * pattern, but then what follows it must be zero or more
+ * valid refs up to the -- (if exists), and then existing
+ * paths. If there is an explicit pattern, then the first
+ * unrecognized non option is the beginning of the refs list
+ * that continues up to the -- (if exists), and then paths.
+ */
+ argc = parse_options(argc, argv, prefix, options, grep_usage,
+ PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_STOP_AT_NON_OPTION |
+ PARSE_OPT_NO_INTERNAL_HELP);
+
+ if (use_index && nongit)
+ /* die the same way as if we did it at the beginning */
+ setup_git_directory();
+
+ /*
+ * skip a -- separator; we know it cannot be
+ * separating revisions from pathnames if
+ * we haven't even had any patterns yet
+ */
+ if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) {
+ argv++;
+ argc--;
+ }
+
+ /* First unrecognized non-option token */
+ if (argc > 0 && !opt.pattern_list) {
+ append_grep_pattern(&opt, argv[0], "command line", 0,
+ GREP_PATTERN);
+ argv++;
+ argc--;
+ }
+
+ if (!opt.pattern_list)
+ die("no pattern given.");
+ if (!opt.fixed && opt.ignore_case)
+ opt.regflags |= REG_ICASE;
+ if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+ die("cannot mix --fixed-strings and regexp");
+
+ #ifndef NO_PTHREADS
+ if (online_cpus() == 1 || !grep_threads_ok(&opt))
+ use_threads = 0;
+
+ if (use_threads)
+ start_threads(&opt);
+ #else
+ use_threads = 0;
+ #endif
+
+ compile_grep_patterns(&opt);
+
+ /* Check revs and then paths */
+ for (i = 0; i < argc; i++) {
+ const char *arg = argv[i];
+ unsigned char sha1[20];
+ /* Is it a rev? */
+ if (!get_sha1(arg, sha1)) {
+ struct object *object = parse_object(sha1);
+ if (!object)
+ die("bad object %s", arg);
+ add_object_array(object, arg, &list);
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ i++;
+ seen_dashdash = 1;
+ }
+ break;
+ }
+
+ /* The rest are paths */
+ if (!seen_dashdash) {
+ int j;
+ for (j = i; j < argc; j++)
+ verify_filename(prefix, argv[j]);
+ }
+
+ if (i < argc)
+ paths = get_pathspec(prefix, argv + i);
+ else if (prefix) {
+ paths = xcalloc(2, sizeof(const char *));
+ paths[0] = prefix;
+ paths[1] = NULL;
+ }
+
+ if (!use_index) {
+ int hit;
+ if (cached)
+ die("--cached cannot be used with --no-index.");
+ if (list.nr)
+ die("--no-index cannot be used with revs.");
+ hit = grep_directory(&opt, paths);
+ if (use_threads)
+ hit |= wait_all();
+ return !hit;
+ }
+
+ if (!list.nr) {
+ int hit;
+ if (!cached)
+ setup_work_tree();
+
+ hit = grep_cache(&opt, paths, cached);
+ if (use_threads)
+ hit |= wait_all();
+ return !hit;
+ }
+
+ if (cached)
+ die("both --cached and trees are given.");
+
+ for (i = 0; i < list.nr; i++) {
+ struct object *real_obj;
+ real_obj = deref_tag(list.objects[i].item, NULL, 0);
+ if (grep_object(&opt, paths, real_obj, list.objects[i].name)) {
+ hit = 1;
+ if (opt.status_only)
+ break;
+ }
+ }
+
+ if (use_threads)
+ hit |= wait_all();
+ free_grep_patterns(&opt);
+ return !hit;
+ }
diff --cc builtin/hash-object.c
index 0000000000000000000000000000000000000000,6a5f5b5f0eaf06b29a4e6afc3335618dc5ac5de8..080af1a01b8155680faf6c04101217b60ae7b919
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
+++ b/builtin/hash-object.c
- hash_object(buf.buf, type, write_objects, buf.buf);
+ /*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+ #include "cache.h"
+ #include "blob.h"
+ #include "quote.h"
+ #include "parse-options.h"
+ #include "exec_cmd.h"
+
+ static void hash_fd(int fd, const char *type, int write_object, const char *path)
+ {
+ struct stat st;
+ unsigned char sha1[20];
+ if (fstat(fd, &st) < 0 ||
+ index_fd(sha1, fd, &st, write_object, type_from_string(type), path))
+ die(write_object
+ ? "Unable to add %s to database"
+ : "Unable to hash %s", path);
+ printf("%s\n", sha1_to_hex(sha1));
+ maybe_flush_or_die(stdout, "hash to stdout");
+ }
+
+ static void hash_object(const char *path, const char *type, int write_object,
+ const char *vpath)
+ {
+ int fd;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ die_errno("Cannot open '%s'", path);
+ hash_fd(fd, type, write_object, vpath);
+ }
+
++static int no_filters;
++
+ static void hash_stdin_paths(const char *type, int write_objects)
+ {
+ struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ if (buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
-static int no_filters;
++ hash_object(buf.buf, type, write_objects,
++ no_filters ? NULL : buf.buf);
+ }
+ strbuf_release(&buf);
+ strbuf_release(&nbuf);
+ }
+
+ static const char * const hash_object_usage[] = {
+ "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
+ "git hash-object --stdin-paths < <list-of-paths>",
+ NULL
+ };
+
+ static const char *type;
+ static int write_object;
+ static int hashstdin;
+ static int stdin_paths;
- else if (no_filters)
- errstr = "Can't use --stdin-paths with --no-filters";
+ static const char *vpath;
+
+ static const struct option hash_object_options[] = {
+ OPT_STRING('t', NULL, &type, "type", "object type"),
+ OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
+ OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
+ OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
+ OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
+ OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
+ OPT_END()
+ };
+
+ int cmd_hash_object(int argc, const char **argv, const char *prefix)
+ {
+ int i;
+ int prefix_length = -1;
+ const char *errstr = NULL;
+
+ type = blob_type;
+
+ argc = parse_options(argc, argv, NULL, hash_object_options,
+ hash_object_usage, 0);
+
+ if (write_object) {
+ prefix = setup_git_directory();
+ prefix_length = prefix ? strlen(prefix) : 0;
+ if (vpath && prefix)
+ vpath = prefix_filename(prefix, prefix_length, vpath);
+ }
+
+ git_config(git_default_config, NULL);
+
+ if (stdin_paths) {
+ if (hashstdin)
+ errstr = "Can't use --stdin-paths with --stdin";
+ else if (argc)
+ errstr = "Can't specify files with --stdin-paths";
+ else if (vpath)
+ errstr = "Can't use --stdin-paths with --path";
+ }
+ else {
+ if (hashstdin > 1)
+ errstr = "Multiple --stdin arguments are not supported";
+ if (vpath && no_filters)
+ errstr = "Can't use --path with --no-filters";
+ }
+
+ if (errstr) {
+ error("%s", errstr);
+ usage_with_options(hash_object_usage, hash_object_options);
+ }
+
+ if (hashstdin)
+ hash_fd(0, type, write_object, vpath);
+
+ for (i = 0 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (0 <= prefix_length)
+ arg = prefix_filename(prefix, prefix_length, arg);
+ hash_object(arg, type, write_object,
+ no_filters ? NULL : vpath ? vpath : arg);
+ }
+
+ if (stdin_paths)
+ hash_stdin_paths(type, write_object);
+
+ return 0;
+ }
diff --cc builtin/init-db.c
index 0000000000000000000000000000000000000000,dd84caecbc2a07bca90c8524157d50a8fd5ae316..aae7a4d7ee946f0792a4be40ddaced570dc766ac
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/init-db.c
+++ b/builtin/init-db.c
- if (!(flags & INIT_DB_QUIET))
- printf("%s%s Git repository in %s/\n",
+ /*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+ #include "cache.h"
+ #include "builtin.h"
+ #include "exec_cmd.h"
+ #include "parse-options.h"
+
+ #ifndef DEFAULT_GIT_TEMPLATE_DIR
+ #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
+ #endif
+
+ #ifdef NO_TRUSTABLE_FILEMODE
+ #define TEST_FILEMODE 0
+ #else
+ #define TEST_FILEMODE 1
+ #endif
+
+ static int init_is_bare_repository = 0;
+ static int init_shared_repository = -1;
+
+ static void safe_create_dir(const char *dir, int share)
+ {
+ if (mkdir(dir, 0777) < 0) {
+ if (errno != EEXIST) {
+ perror(dir);
+ exit(1);
+ }
+ }
+ else if (share && adjust_shared_perm(dir))
+ die("Could not make %s writable by group", dir);
+ }
+
+ static void copy_templates_1(char *path, int baselen,
+ char *template, int template_baselen,
+ DIR *dir)
+ {
+ struct dirent *de;
+
+ /* Note: if ".git/hooks" file exists in the repository being
+ * re-initialized, /etc/core-git/templates/hooks/update would
+ * cause "git init" to fail here. I think this is sane but
+ * it means that the set of templates we ship by default, along
+ * with the way the namespace under .git/ is organized, should
+ * be really carefully chosen.
+ */
+ safe_create_dir(path, 1);
+ while ((de = readdir(dir)) != NULL) {
+ struct stat st_git, st_template;
+ int namelen;
+ int exists = 0;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if ((PATH_MAX <= baselen + namelen) ||
+ (PATH_MAX <= template_baselen + namelen))
+ die("insanely long template name %s", de->d_name);
+ memcpy(path + baselen, de->d_name, namelen+1);
+ memcpy(template + template_baselen, de->d_name, namelen+1);
+ if (lstat(path, &st_git)) {
+ if (errno != ENOENT)
+ die_errno("cannot stat '%s'", path);
+ }
+ else
+ exists = 1;
+
+ if (lstat(template, &st_template))
+ die_errno("cannot stat template '%s'", template);
+
+ if (S_ISDIR(st_template.st_mode)) {
+ DIR *subdir = opendir(template);
+ int baselen_sub = baselen + namelen;
+ int template_baselen_sub = template_baselen + namelen;
+ if (!subdir)
+ die_errno("cannot opendir '%s'", template);
+ path[baselen_sub++] =
+ template[template_baselen_sub++] = '/';
+ path[baselen_sub] =
+ template[template_baselen_sub] = 0;
+ copy_templates_1(path, baselen_sub,
+ template, template_baselen_sub,
+ subdir);
+ closedir(subdir);
+ }
+ else if (exists)
+ continue;
+ else if (S_ISLNK(st_template.st_mode)) {
+ char lnk[256];
+ int len;
+ len = readlink(template, lnk, sizeof(lnk));
+ if (len < 0)
+ die_errno("cannot readlink '%s'", template);
+ if (sizeof(lnk) <= len)
+ die("insanely long symlink %s", template);
+ lnk[len] = 0;
+ if (symlink(lnk, path))
+ die_errno("cannot symlink '%s' '%s'", lnk, path);
+ }
+ else if (S_ISREG(st_template.st_mode)) {
+ if (copy_file(path, template, st_template.st_mode))
+ die_errno("cannot copy '%s' to '%s'", template,
+ path);
+ }
+ else
+ error("ignoring template %s", template);
+ }
+ }
+
+ static void copy_templates(const char *template_dir)
+ {
+ char path[PATH_MAX];
+ char template_path[PATH_MAX];
+ int template_len;
+ DIR *dir;
+ const char *git_dir = get_git_dir();
+ int len = strlen(git_dir);
+
+ if (!template_dir)
+ template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+ if (!template_dir)
+ template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
+ if (!template_dir[0])
+ return;
+ template_len = strlen(template_dir);
+ if (PATH_MAX <= (template_len+strlen("/config")))
+ die("insanely long template path %s", template_dir);
+ strcpy(template_path, template_dir);
+ if (template_path[template_len-1] != '/') {
+ template_path[template_len++] = '/';
+ template_path[template_len] = 0;
+ }
+ dir = opendir(template_path);
+ if (!dir) {
+ warning("templates not found %s", template_dir);
+ return;
+ }
+
+ /* Make sure that template is from the correct vintage */
+ strcpy(template_path + template_len, "config");
+ repository_format_version = 0;
+ git_config_from_file(check_repository_format_version,
+ template_path, NULL);
+ template_path[template_len] = 0;
+
+ if (repository_format_version &&
+ repository_format_version != GIT_REPO_VERSION) {
+ warning("not copying templates of "
+ "a wrong format version %d from '%s'",
+ repository_format_version,
+ template_dir);
+ closedir(dir);
+ return;
+ }
+
+ memcpy(path, git_dir, len);
+ if (len && path[len - 1] != '/')
+ path[len++] = '/';
+ path[len] = 0;
+ copy_templates_1(path, len,
+ template_path, template_len,
+ dir);
+ closedir(dir);
+ }
+
+ static int create_default_files(const char *template_path)
+ {
+ const char *git_dir = get_git_dir();
+ unsigned len = strlen(git_dir);
+ static char path[PATH_MAX];
+ struct stat st1;
+ char repo_version_string[10];
+ char junk[2];
+ int reinit;
+ int filemode;
+
+ if (len > sizeof(path)-50)
+ die("insane git directory %s", git_dir);
+ memcpy(path, git_dir, len);
+
+ if (len && path[len-1] != '/')
+ path[len++] = '/';
+
+ /*
+ * Create .git/refs/{heads,tags}
+ */
+ safe_create_dir(git_path("refs"), 1);
+ safe_create_dir(git_path("refs/heads"), 1);
+ safe_create_dir(git_path("refs/tags"), 1);
+
+ /* First copy the templates -- we might have the default
+ * config file there, in which case we would want to read
+ * from it after installing.
+ */
+ copy_templates(template_path);
+
+ git_config(git_default_config, NULL);
+ is_bare_repository_cfg = init_is_bare_repository;
+
+ /* reading existing config may have overwrote it */
+ if (init_shared_repository != -1)
+ shared_repository = init_shared_repository;
+
+ /*
+ * We would have created the above under user's umask -- under
+ * shared-repository settings, we would need to fix them up.
+ */
+ if (shared_repository) {
+ adjust_shared_perm(get_git_dir());
+ adjust_shared_perm(git_path("refs"));
+ adjust_shared_perm(git_path("refs/heads"));
+ adjust_shared_perm(git_path("refs/tags"));
+ }
+
+ /*
+ * Create the default symlink from ".git/HEAD" to the "master"
+ * branch, if it does not exist yet.
+ */
+ strcpy(path + len, "HEAD");
+ reinit = (!access(path, R_OK)
+ || readlink(path, junk, sizeof(junk)-1) != -1);
+ if (!reinit) {
+ if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
+ exit(1);
+ }
+
+ /* This forces creation of new config file */
+ sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+ git_config_set("core.repositoryformatversion", repo_version_string);
+
+ path[len] = 0;
+ strcpy(path + len, "config");
+
+ /* Check filemode trustability */
+ filemode = TEST_FILEMODE;
+ if (TEST_FILEMODE && !lstat(path, &st1)) {
+ struct stat st2;
+ filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
+ !lstat(path, &st2) &&
+ st1.st_mode != st2.st_mode);
+ }
+ git_config_set("core.filemode", filemode ? "true" : "false");
+
+ if (is_bare_repository())
+ git_config_set("core.bare", "true");
+ else {
+ const char *work_tree = get_git_work_tree();
+ git_config_set("core.bare", "false");
+ /* allow template config file to override the default */
+ if (log_all_ref_updates == -1)
+ git_config_set("core.logallrefupdates", "true");
+ if (prefixcmp(git_dir, work_tree) ||
+ strcmp(git_dir + strlen(work_tree), "/.git")) {
+ git_config_set("core.worktree", work_tree);
+ }
+ }
+
+ if (!reinit) {
+ /* Check if symlink is supported in the work tree */
+ path[len] = 0;
+ strcpy(path + len, "tXXXXXX");
+ if (!close(xmkstemp(path)) &&
+ !unlink(path) &&
+ !symlink("testing", path) &&
+ !lstat(path, &st1) &&
+ S_ISLNK(st1.st_mode))
+ unlink(path); /* good */
+ else
+ git_config_set("core.symlinks", "false");
+
+ /* Check if the filesystem is case-insensitive */
+ path[len] = 0;
+ strcpy(path + len, "CoNfIg");
+ if (!access(path, F_OK))
+ git_config_set("core.ignorecase", "true");
+ }
+
+ return reinit;
+ }
+
+ int init_db(const char *template_dir, unsigned int flags)
+ {
+ const char *sha1_dir;
+ char *path;
+ int len, reinit;
+
+ safe_create_dir(get_git_dir(), 0);
+
+ init_is_bare_repository = is_bare_repository();
+
+ /* Check to see if the repository version is right.
+ * Note that a newly created repository does not have
+ * config file, so this will not fail. What we are catching
+ * is an attempt to reinitialize new repository with an old tool.
+ */
+ check_repository_format();
+
+ reinit = create_default_files(template_dir);
+
+ sha1_dir = get_object_directory();
+ len = strlen(sha1_dir);
+ path = xmalloc(len + 40);
+ memcpy(path, sha1_dir, len);
+
+ safe_create_dir(sha1_dir, 1);
+ strcpy(path+len, "/pack");
+ safe_create_dir(path, 1);
+ strcpy(path+len, "/info");
+ safe_create_dir(path, 1);
+
+ if (shared_repository) {
+ char buf[10];
+ /* We do not spell "group" and such, so that
+ * the configuration can be read by older version
+ * of git. Note, we use octal numbers for new share modes,
+ * and compatibility values for PERM_GROUP and
+ * PERM_EVERYBODY.
+ */
+ if (shared_repository < 0)
+ /* force to the mode value */
+ sprintf(buf, "0%o", -shared_repository);
+ else if (shared_repository == PERM_GROUP)
+ sprintf(buf, "%d", OLD_PERM_GROUP);
+ else if (shared_repository == PERM_EVERYBODY)
+ sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+ else
+ die("oops");
+ git_config_set("core.sharedrepository", buf);
+ git_config_set("receive.denyNonFastforwards", "true");
+ }
+
- get_git_dir());
++ if (!(flags & INIT_DB_QUIET)) {
++ const char *git_dir = get_git_dir();
++ int len = strlen(git_dir);
++ printf("%s%s Git repository in %s%s\n",
+ reinit ? "Reinitialized existing" : "Initialized empty",
+ shared_repository ? " shared" : "",
++ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
++ }
+
+ return 0;
+ }
+
+ static int guess_repository_type(const char *git_dir)
+ {
+ char cwd[PATH_MAX];
+ const char *slash;
+
+ /*
+ * "GIT_DIR=. git init" is always bare.
+ * "GIT_DIR=`pwd` git init" too.
+ */
+ if (!strcmp(".", git_dir))
+ return 1;
+ if (!getcwd(cwd, sizeof(cwd)))
+ die_errno("cannot tell cwd");
+ if (!strcmp(git_dir, cwd))
+ return 1;
+ /*
+ * "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
+ */
+ if (!strcmp(git_dir, ".git"))
+ return 0;
+ slash = strrchr(git_dir, '/');
+ if (slash && !strcmp(slash, "/.git"))
+ return 0;
+
+ /*
+ * Otherwise it is often bare. At this point
+ * we are just guessing.
+ */
+ return 1;
+ }
+
+ static int shared_callback(const struct option *opt, const char *arg, int unset)
+ {
+ *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
+ return 0;
+ }
+
+ static const char *const init_db_usage[] = {
+ "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]",
+ NULL
+ };
+
+ /*
+ * If you want to, you can share the DB area with any number of branches.
+ * That has advantages: you can save space by sharing all the SHA1 objects.
+ * On the other hand, it might just make lookup slower and messier. You
+ * be the judge. The default case is to have one DB per managed directory.
+ */
+ int cmd_init_db(int argc, const char **argv, const char *prefix)
+ {
+ const char *git_dir;
+ const char *template_dir = NULL;
+ unsigned int flags = 0;
+ const struct option init_db_options[] = {
+ OPT_STRING(0, "template", &template_dir, "template-directory",
+ "provide the directory from which templates will be used"),
+ OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
+ "create a bare repository", 1),
+ { OPTION_CALLBACK, 0, "shared", &init_shared_repository,
+ "permissions",
+ "specify that the git repository is to be shared amongst several users",
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
+ OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
+
+ if (argc == 1) {
+ int mkdir_tried = 0;
+ retry:
+ if (chdir(argv[0]) < 0) {
+ if (!mkdir_tried) {
+ int saved;
+ /*
+ * At this point we haven't read any configuration,
+ * and we know shared_repository should always be 0;
+ * but just in case we play safe.
+ */
+ saved = shared_repository;
+ shared_repository = 0;
+ switch (safe_create_leading_directories_const(argv[0])) {
+ case -3:
+ errno = EEXIST;
+ /* fallthru */
+ case -1:
+ die_errno("cannot mkdir %s", argv[0]);
+ break;
+ default:
+ break;
+ }
+ shared_repository = saved;
+ if (mkdir(argv[0], 0777) < 0)
+ die_errno("cannot mkdir %s", argv[0]);
+ mkdir_tried = 1;
+ goto retry;
+ }
+ die_errno("cannot chdir to %s", argv[0]);
+ }
+ } else if (0 < argc) {
+ usage(init_db_usage[0]);
+ }
+ if (is_bare_repository_cfg == 1) {
+ static char git_dir[PATH_MAX+1];
+
+ setenv(GIT_DIR_ENVIRONMENT,
+ getcwd(git_dir, sizeof(git_dir)), 0);
+ }
+
+ if (init_shared_repository != -1)
+ shared_repository = init_shared_repository;
+
+ /*
+ * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
+ * without --bare. Catch the error early.
+ */
+ git_dir = getenv(GIT_DIR_ENVIRONMENT);
+ if ((!git_dir || is_bare_repository_cfg == 1)
+ && getenv(GIT_WORK_TREE_ENVIRONMENT))
+ die("%s (or --work-tree=<directory>) not allowed without "
+ "specifying %s (or --git-dir=<directory>)",
+ GIT_WORK_TREE_ENVIRONMENT,
+ GIT_DIR_ENVIRONMENT);
+
+ /*
+ * Set up the default .git directory contents
+ */
+ if (!git_dir)
+ git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+
+ if (is_bare_repository_cfg < 0)
+ is_bare_repository_cfg = guess_repository_type(git_dir);
+
+ if (!is_bare_repository_cfg) {
+ if (git_dir) {
+ const char *git_dir_parent = strrchr(git_dir, '/');
+ if (git_dir_parent) {
+ char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
+ git_work_tree_cfg = xstrdup(make_absolute_path(rel));
+ free(rel);
+ }
+ }
+ if (!git_work_tree_cfg) {
+ git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+ if (!getcwd(git_work_tree_cfg, PATH_MAX))
+ die_errno ("Cannot access current working directory");
+ }
+ if (access(get_git_work_tree(), X_OK))
+ die_errno ("Cannot access work tree '%s'",
+ get_git_work_tree());
+ }
+
+ set_git_dir(make_absolute_path(git_dir));
+
+ return init_db(template_dir, flags);
+ }
diff --cc builtin/mailinfo.c
index 0000000000000000000000000000000000000000,a50ac2256cdbacd76ed44a50804212be07f949db..ce2ef6bede40fde8823336bc85762bd3e7cd4760
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
- strbuf_ltrim(line);
- if (!line->len)
+ /*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+ #include "cache.h"
+ #include "builtin.h"
+ #include "utf8.h"
+ #include "strbuf.h"
+
+ static FILE *cmitmsg, *patchfile, *fin, *fout;
+
+ static int keep_subject;
+ static int keep_non_patch_brackets_in_subject;
+ static const char *metainfo_charset;
+ static struct strbuf line = STRBUF_INIT;
+ static struct strbuf name = STRBUF_INIT;
+ static struct strbuf email = STRBUF_INIT;
+
+ static enum {
+ TE_DONTCARE, TE_QP, TE_BASE64,
+ } transfer_encoding;
+ static enum {
+ TYPE_TEXT, TYPE_OTHER,
+ } message_type;
+
+ static struct strbuf charset = STRBUF_INIT;
+ static int patch_lines;
+ static struct strbuf **p_hdr_data, **s_hdr_data;
+ static int use_scissors;
+ static int use_inbody_headers = 1;
+
+ #define MAX_HDR_PARSED 10
+ #define MAX_BOUNDARIES 5
+
+ static void cleanup_space(struct strbuf *sb);
+
+
+ static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+ {
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+ }
+
+ static void parse_bogus_from(const struct strbuf *line)
+ {
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&email);
+ strbuf_add(&email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&name);
+ strbuf_add(&name, line->buf, bra - line->buf);
+ strbuf_trim(&name);
+ get_sane_name(&name, &name, &email);
+ }
+
+ static void handle_from(const struct strbuf *from)
+ {
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(from);
+ return;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (email.len && strchr(at + 1, '@')) {
+ strbuf_release(&f);
+ return;
+ }
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&email);
+ strbuf_add(&email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&name, &f, &email);
+ strbuf_release(&f);
+ }
+
+ static void handle_header(struct strbuf **out, const struct strbuf *line)
+ {
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+ }
+
+ /* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+ static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+ {
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ if (!ap) {
+ strbuf_setlen(attr, 0);
+ return 0;
+ }
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+ }
+
+ static struct strbuf *content[MAX_BOUNDARIES];
+
+ static struct strbuf **content_top = content;
+
+ static void handle_content_type(struct strbuf *line)
+ {
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ if (!strcasestr(line->buf, "text/"))
+ message_type = TYPE_OTHER;
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++content_top > &content[MAX_BOUNDARIES]) {
+ fprintf(stderr, "Too many boundaries to handle\n");
+ exit(1);
+ }
+ *content_top = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+ }
+
+ static void handle_content_transfer_encoding(const struct strbuf *line)
+ {
+ if (strcasestr(line->buf, "base64"))
+ transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ transfer_encoding = TE_QP;
+ else
+ transfer_encoding = TE_DONTCARE;
+ }
+
+ static int is_multipart_boundary(const struct strbuf *line)
+ {
+ return (((*content_top)->len <= line->len) &&
+ !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
+ }
+
+ static void cleanup_subject(struct strbuf *subject)
+ {
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else
+ at += remove;
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+ }
+
+ static void cleanup_space(struct strbuf *sb)
+ {
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+ }
+
+ static void decode_header(struct strbuf *line);
+ static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+ };
+
+ static inline int cmp_header(const struct strbuf *line, const char *hdr)
+ {
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+ }
+
+ static int check_header(const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+ {
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(&sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ handle_content_transfer_encoding(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
+
+ /* for inbody stuff */
+ if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
+ ret = 1; /* Should this return 0? */
+ goto check_header_out;
+ }
+ if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ for (i = 0; header[i]; i++) {
+ if (!memcmp("Subject", header[i], 7)) {
+ handle_header(&hdr_data[i], line);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+ }
+
+ check_header_out:
+ strbuf_release(&sb);
+ return ret;
+ }
+
+ static int is_rfc2822_header(const struct strbuf *line)
+ {
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+ }
+
+ static int read_one_header_line(struct strbuf *line, FILE *in)
+ {
+ /* Get the first part of the line. */
+ if (strbuf_getline(line, in, '\n'))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+ struct strbuf continuation = STRBUF_INIT;
+
+ peek = fgetc(in); ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline(&continuation, in, '\n'))
+ break;
+ continuation.buf[0] = '\n';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+
+ return 1;
+ }
+
+ static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+ {
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int d = *in++;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
+ continue;
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+ }
+
+ static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+ {
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+ }
+
+ /*
+ * When there is no known charset, guess.
+ *
+ * Right now we assume that if the target is UTF-8 (the default),
+ * and it already looks like UTF-8 (which includes US-ASCII as its
+ * subset, of course) then that is what it is and there is nothing
+ * to do.
+ *
+ * Otherwise, we default to assuming it is Latin1 for historical
+ * reasons.
+ */
+ static const char *guess_charset(const struct strbuf *line, const char *target_charset)
+ {
+ if (is_encoding_utf8(target_charset)) {
+ if (is_utf8(line->buf))
+ return NULL;
+ }
+ return "ISO8859-1";
+ }
+
+ static void convert_to_utf8(struct strbuf *line, const char *charset)
+ {
+ char *out;
+
+ if (!charset || !*charset) {
+ charset = guess_charset(line, metainfo_charset);
+ if (!charset)
+ return;
+ }
+
+ if (!strcasecmp(metainfo_charset, charset))
+ return;
+ out = reencode_string(line->buf, metainfo_charset, charset);
+ if (!out)
+ die("cannot convert from %s to %s",
+ charset, metainfo_charset);
+ strbuf_attach(line, out, strlen(out), strlen(out));
+ }
+
+ static int decode_header_bq(struct strbuf *it)
+ {
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int rfc2047 = 0;
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+ rfc2047 = 1;
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto decode_header_bq_out;
+
+ if (cp + 3 - it->buf > it->len)
+ goto decode_header_bq_out;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto decode_header_bq_out;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto decode_header_bq_out;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto decode_header_bq_out;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (metainfo_charset)
+ convert_to_utf8(dec, charset_q.buf);
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+ decode_header_bq_out:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+ return rfc2047;
+ }
+
+ static void decode_header(struct strbuf *it)
+ {
+ if (decode_header_bq(it))
+ return;
+ /* otherwise "it" is a straight copy of the input.
+ * This can be binary guck but there is no charset specified.
+ */
+ if (metainfo_charset)
+ convert_to_utf8(it, "");
+ }
+
+ static void decode_transfer_encoding(struct strbuf *line)
+ {
+ struct strbuf *ret;
+
+ switch (transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+ }
+
+ static void handle_filter(struct strbuf *line);
+
+ static int find_boundary(void)
+ {
+ while (!strbuf_getline(&line, fin, '\n')) {
+ if (*content_top && is_multipart_boundary(&line))
+ return 1;
+ }
+ return 0;
+ }
+
+ static int handle_boundary(void)
+ {
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+ again:
+ if (line.len >= (*content_top)->len + 2 &&
+ !memcmp(line.buf + (*content_top)->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*content_top);
+ free(*content_top);
+ *content_top = NULL;
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--content_top < content) {
+ fprintf(stderr, "Detected mismatched boundaries, "
+ "can't recover\n");
+ exit(1);
+ }
+ handle_filter(&newline);
+ strbuf_release(&newline);
+
+ /* skip to the next boundary */
+ if (!find_boundary())
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&charset);
+ message_type = TYPE_TEXT;
+
+ /* slurp in this section's info */
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline(&line, fin, '\n'))
+ return 0;
+ strbuf_addch(&line, '\n');
+ return 1;
+ }
+
+ static inline int patchbreak(const struct strbuf *line)
+ {
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (!prefixcmp(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (!prefixcmp(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (!prefixcmp(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+ }
+
+ static int is_scissors_line(const struct strbuf *line)
+ {
+ size_t i, len = line->len;
+ int scissors = 0, gap = 0;
+ int first_nonblank = -1;
+ int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
+ const char *buf = line->buf;
+
+ for (i = 0; i < len; i++) {
+ if (isspace(buf[i])) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = i;
+ if (first_nonblank < 0)
+ first_nonblank = i;
+ if (buf[i] == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if (i + 1 < len &&
+ (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ i++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ visible = last_nonblank - first_nonblank + 1;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+ }
+
+ static int handle_commit_msg(struct strbuf *line)
+ {
+ static int still_looking = 1;
+
+ if (!cmitmsg)
+ return 0;
+
+ if (still_looking) {
++ if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
+ return 0;
+ }
+
+ if (use_inbody_headers && still_looking) {
+ still_looking = check_header(line, s_hdr_data, 0);
+ if (still_looking)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ still_looking = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (metainfo_charset)
+ convert_to_utf8(line, charset.buf);
+
+ if (use_scissors && is_scissors_line(line)) {
+ int i;
+ if (fseek(cmitmsg, 0L, SEEK_SET))
+ die_errno("Could not rewind output message file");
+ if (ftruncate(fileno(cmitmsg), 0))
+ die_errno("Could not truncate output message file at scissors");
+ still_looking = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (s_hdr_data[i])
+ strbuf_release(s_hdr_data[i]);
+ s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ fclose(cmitmsg);
+ cmitmsg = NULL;
+ return 1;
+ }
+
+ fputs(line->buf, cmitmsg);
+ return 0;
+ }
+
+ static void handle_patch(const struct strbuf *line)
+ {
+ fwrite(line->buf, 1, line->len, patchfile);
+ patch_lines++;
+ }
+
+ static void handle_filter(struct strbuf *line)
+ {
+ static int filter = 0;
+
+ /* filter tells us which part we left off on */
+ switch (filter) {
+ case 0:
+ if (!handle_commit_msg(line))
+ break;
+ filter++;
+ case 1:
+ handle_patch(line);
+ break;
+ }
+ }
+
+ static void handle_body(void)
+ {
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*content_top) {
+ if (!find_boundary())
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*content_top && is_multipart_boundary(&line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(&prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary())
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(&line);
+
+ switch (transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(&line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /* binary data most likely doesn't have newlines */
+ if (message_type != TYPE_TEXT) {
+ handle_filter(&line);
+ break;
+ }
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(&line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter(sb);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter(&line);
+ }
+
+ } while (!strbuf_getwholeline(&line, fin, '\n'));
+
+ handle_body_out:
+ strbuf_release(&prev);
+ }
+
+ static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+ {
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+ }
+
+ static void handle_info(void)
+ {
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (patch_lines && s_hdr_data[i])
+ hdr = s_hdr_data[i];
+ else if (p_hdr_data[i])
+ hdr = p_hdr_data[i];
+ else
+ continue;
+
+ if (!memcmp(header[i], "Subject", 7)) {
+ if (!keep_subject) {
+ cleanup_subject(hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(fout, "Subject", hdr);
+ } else if (!memcmp(header[i], "From", 4)) {
+ cleanup_space(hdr);
+ handle_from(hdr);
+ fprintf(fout, "Author: %s\n", name.buf);
+ fprintf(fout, "Email: %s\n", email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(fout, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(fout, "\n");
+ }
+
+ static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
+ {
+ int peek;
+ fin = in;
+ fout = out;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ patchfile = fopen(patch, "w");
+ if (!patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
+ s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
+
+ do {
+ peek = fgetc(in);
+ } while (isspace(peek));
+ ungetc(peek, in);
+
+ /* process the email header */
+ while (read_one_header_line(&line, fin))
+ check_header(&line, p_hdr_data, 1);
+
+ handle_body();
+ handle_info();
+
+ return 0;
+ }
+
+ static int git_mailinfo_config(const char *var, const char *value, void *unused)
+ {
+ if (prefixcmp(var, "mailinfo."))
+ return git_default_config(var, value, unused);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+ }
+
+ static const char mailinfo_usage[] =
+ "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+
+ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
+ {
+ const char *def_charset;
+
+ /* NEEDSWORK: might want to do the optional .git/ directory
+ * discovery
+ */
+ git_config(git_mailinfo_config, NULL);
+
+ def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+ metainfo_charset = def_charset;
+
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "-k"))
+ keep_subject = 1;
+ else if (!strcmp(argv[1], "-b"))
+ keep_non_patch_brackets_in_subject = 1;
+ else if (!strcmp(argv[1], "-u"))
+ metainfo_charset = def_charset;
+ else if (!strcmp(argv[1], "-n"))
+ metainfo_charset = NULL;
+ else if (!prefixcmp(argv[1], "--encoding="))
+ metainfo_charset = argv[1] + 11;
+ else if (!strcmp(argv[1], "--scissors"))
+ use_scissors = 1;
+ else if (!strcmp(argv[1], "--no-scissors"))
+ use_scissors = 0;
+ else if (!strcmp(argv[1], "--no-inbody-headers"))
+ use_inbody_headers = 0;
+ else
+ usage(mailinfo_usage);
+ argc--; argv++;
+ }
+
+ if (argc != 3)
+ usage(mailinfo_usage);
+
+ return !!mailinfo(stdin, stdout, argv[1], argv[2]);
+ }
diff --cc builtin/pack-objects.c
index 0000000000000000000000000000000000000000,e1d3adf405bb6ac842a3415e0461b4772396060d..97802585ea3ac69ac6ed2e7995605bdcae84558e
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
+++ b/builtin/pack-objects.c
-/*
- * The per-object header is a pretty dense thing, which is
- * - first byte: low four bits are "size", then three bits of "type",
- * and the high bit is "size continues".
- * - each byte afterwards: low seven bits are size continuation,
- * with the high bit being "size continues"
- */
-static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
-{
- int n = 1;
- unsigned char c;
-
- if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
- die("bad type %d", type);
-
- c = (type << 4) | (size & 15);
- size >>= 4;
- while (size) {
- *hdr++ = c | 0x80;
- c = size & 0x7f;
- size >>= 7;
- n++;
- }
- *hdr = c;
- return n;
-}
-
+ #include "builtin.h"
+ #include "cache.h"
+ #include "attr.h"
+ #include "object.h"
+ #include "blob.h"
+ #include "commit.h"
+ #include "tag.h"
+ #include "tree.h"
+ #include "delta.h"
+ #include "pack.h"
+ #include "pack-revindex.h"
+ #include "csum-file.h"
+ #include "tree-walk.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "list-objects.h"
+ #include "progress.h"
+ #include "refs.h"
+
+ #ifndef NO_PTHREADS
+ #include "thread-utils.h"
+ #include <pthread.h>
+ #endif
+
+ static const char pack_usage[] =
+ "git pack-objects [{ -q | --progress | --all-progress }]\n"
+ " [--all-progress-implied]\n"
+ " [--max-pack-size=N] [--local] [--incremental]\n"
+ " [--window=N] [--window-memory=N] [--depth=N]\n"
+ " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
+ " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n"
+ " [--reflog] [--stdout | base-name] [--include-tag]\n"
+ " [--keep-unreachable | --unpack-unreachable \n"
+ " [<ref-list | <object-list]";
+
+ struct object_entry {
+ struct pack_idx_entry idx;
+ unsigned long size; /* uncompressed size */
+ struct packed_git *in_pack; /* already in pack */
+ off_t in_pack_offset;
+ struct object_entry *delta; /* delta base object */
+ struct object_entry *delta_child; /* deltified objects who bases me */
+ struct object_entry *delta_sibling; /* other deltified objects who
+ * uses the same base as me
+ */
+ void *delta_data; /* cached delta (uncompressed) */
+ unsigned long delta_size; /* delta data size (uncompressed) */
+ unsigned long z_delta_size; /* delta data size (compressed) */
+ unsigned int hash; /* name hint hash */
+ enum object_type type;
+ enum object_type in_pack_type; /* could be delta */
+ unsigned char in_pack_header_size;
+ unsigned char preferred_base; /* we do not pack this, but is available
+ * to be used as the base object to delta
+ * objects against.
+ */
+ unsigned char no_try_delta;
+ };
+
+ /*
+ * Objects we are going to pack are collected in objects array (dynamically
+ * expanded). nr_objects & nr_alloc controls this array. They are stored
+ * in the order we see -- typically rev-list --objects order that gives us
+ * nice "minimum seek" order.
+ */
+ static struct object_entry *objects;
+ static struct pack_idx_entry **written_list;
+ static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
+
+ static int non_empty;
+ static int reuse_delta = 1, reuse_object = 1;
+ static int keep_unreachable, unpack_unreachable, include_tag;
+ static int local;
+ static int incremental;
+ static int ignore_packed_keep;
+ static int allow_ofs_delta;
+ static const char *base_name;
+ static int progress = 1;
+ static int window = 10;
+ static unsigned long pack_size_limit, pack_size_limit_cfg;
+ static int depth = 50;
+ static int delta_search_threads;
+ static int pack_to_stdout;
+ static int num_preferred_base;
+ static struct progress *progress_state;
+ static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+ static int pack_compression_seen;
+
+ static unsigned long delta_cache_size = 0;
+ static unsigned long max_delta_cache_size = 256 * 1024 * 1024;
+ static unsigned long cache_max_small_delta_size = 1000;
+
+ static unsigned long window_memory_limit = 0;
+
+ /*
+ * The object names in objects array are hashed with this hashtable,
+ * to help looking up the entry by object name.
+ * This hashtable is built after all the objects are seen.
+ */
+ static int *object_ix;
+ static int object_ix_hashsz;
+
+ /*
+ * stats
+ */
+ static uint32_t written, written_delta;
+ static uint32_t reused, reused_delta;
+
+
+ static void *get_delta(struct object_entry *entry)
+ {
+ unsigned long size, base_size, delta_size;
+ void *buf, *base_buf, *delta_buf;
+ enum object_type type;
+
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+ base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
+ if (!base_buf)
+ die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
+ delta_buf = diff_delta(base_buf, base_size,
+ buf, size, &delta_size, 0);
+ if (!delta_buf || delta_size != entry->delta_size)
+ die("delta size changed");
+ free(buf);
+ free(base_buf);
+ return delta_buf;
+ }
+
+ static unsigned long do_compress(void **pptr, unsigned long size)
+ {
+ z_stream stream;
+ void *in, *out;
+ unsigned long maxsize;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, pack_compression_level);
+ maxsize = deflateBound(&stream, size);
+
+ in = *pptr;
+ out = xmalloc(maxsize);
+ *pptr = out;
+
+ stream.next_in = in;
+ stream.avail_in = size;
+ stream.next_out = out;
+ stream.avail_out = maxsize;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ deflateEnd(&stream);
+
+ free(in);
+ return stream.total_out;
+ }
+
- hdrlen = encode_header(type, size, header);
+ /*
+ * we are going to reuse the existing object data as is. make
+ * sure it is not corrupt.
+ */
+ static int check_pack_inflate(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t offset,
+ off_t len,
+ unsigned long expect)
+ {
+ z_stream stream;
+ unsigned char fakebuf[4096], *in;
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+ git_inflate_init(&stream);
+ do {
+ in = use_pack(p, w_curs, offset, &stream.avail_in);
+ stream.next_in = in;
+ stream.next_out = fakebuf;
+ stream.avail_out = sizeof(fakebuf);
+ st = git_inflate(&stream, Z_FINISH);
+ offset += stream.next_in - in;
+ } while (st == Z_OK || st == Z_BUF_ERROR);
+ git_inflate_end(&stream);
+ return (st == Z_STREAM_END &&
+ stream.total_out == expect &&
+ stream.total_in == len) ? 0 : -1;
+ }
+
+ static void copy_pack_data(struct sha1file *f,
+ struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t offset,
+ off_t len)
+ {
+ unsigned char *in;
+ unsigned int avail;
+
+ while (len) {
+ in = use_pack(p, w_curs, offset, &avail);
+ if (avail > len)
+ avail = (unsigned int)len;
+ sha1write(f, in, avail);
+ offset += avail;
+ len -= avail;
+ }
+ }
+
+ static unsigned long write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
+ {
+ unsigned long size, limit, datalen;
+ void *buf;
+ unsigned char header[10], dheader[10];
+ unsigned hdrlen;
+ enum object_type type;
+ int usable_delta, to_reuse;
+
+ if (!pack_to_stdout)
+ crc32_begin(f);
+
+ type = entry->type;
+
+ /* apply size limit if limited packsize and not first object */
+ if (!pack_size_limit || !nr_written)
+ limit = 0;
+ else if (pack_size_limit <= write_offset)
+ /*
+ * the earlier object did not fit the limit; avoid
+ * mistaking this with unlimited (i.e. limit = 0).
+ */
+ limit = 1;
+ else
+ limit = pack_size_limit - write_offset;
+
+ if (!entry->delta)
+ usable_delta = 0; /* no delta */
+ else if (!pack_size_limit)
+ usable_delta = 1; /* unlimited packfile */
+ else if (entry->delta->idx.offset == (off_t)-1)
+ usable_delta = 0; /* base was written to another pack */
+ else if (entry->delta->idx.offset)
+ usable_delta = 1; /* base already exists in this pack */
+ else
+ usable_delta = 0; /* base could end up in another pack */
+
+ if (!reuse_object)
+ to_reuse = 0; /* explicit */
+ else if (!entry->in_pack)
+ to_reuse = 0; /* can't reuse what we don't have */
+ else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
+ /* check_object() decided it for us ... */
+ to_reuse = usable_delta;
+ /* ... but pack split may override that */
+ else if (type != entry->in_pack_type)
+ to_reuse = 0; /* pack has delta which is unusable */
+ else if (entry->delta)
+ to_reuse = 0; /* we want to pack afresh */
+ else
+ to_reuse = 1; /* we have it in-pack undeltified,
+ * and we do not need to deltify it.
+ */
+
+ if (!to_reuse) {
+ no_reuse:
+ if (!usable_delta) {
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+ /*
+ * make sure no cached delta data remains from a
+ * previous attempt before a pack split occurred.
+ */
+ free(entry->delta_data);
+ entry->delta_data = NULL;
+ entry->z_delta_size = 0;
+ } else if (entry->delta_data) {
+ size = entry->delta_size;
+ buf = entry->delta_data;
+ entry->delta_data = NULL;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ } else {
+ buf = get_delta(entry);
+ size = entry->delta_size;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ }
+
+ if (entry->z_delta_size)
+ datalen = entry->z_delta_size;
+ else
+ datalen = do_compress(&buf, size);
+
+ /*
+ * The object header is a byte of 'type' followed by zero or
+ * more bytes of length.
+ */
- hdrlen = encode_header(type, entry->size, header);
++ hdrlen = encode_in_pack_object_header(type, size, header);
+
+ if (type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ } else if (type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ sha1write(f, buf, datalen);
+ free(buf);
+ }
+ else {
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ struct revindex_entry *revidx;
+ off_t offset;
+
+ if (entry->delta)
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
-/* forward declaration for write_pack_file */
-static int adjust_perm(const char *path, mode_t mode);
-
++ hdrlen = encode_in_pack_object_header(type, entry->size, header);
+
+ offset = entry->in_pack_offset;
+ revidx = find_pack_revindex(p, offset);
+ datalen = revidx[1].offset - offset;
+ if (!pack_to_stdout && p->index_version > 1 &&
+ check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
+ error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ goto no_reuse;
+ }
+
+ offset += entry->in_pack_header_size;
+ datalen -= entry->in_pack_header_size;
+ if (!pack_to_stdout && p->index_version == 1 &&
+ check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+ error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ goto no_reuse;
+ }
+
+ if (type == OBJ_OFS_DELTA) {
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ reused_delta++;
+ } else if (type == OBJ_REF_DELTA) {
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ reused_delta++;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ copy_pack_data(f, p, &w_curs, offset, datalen);
+ unuse_pack(&w_curs);
+ reused++;
+ }
+ if (usable_delta)
+ written_delta++;
+ written++;
+ if (!pack_to_stdout)
+ entry->idx.crc32 = crc32_end(f);
+ return hdrlen + datalen;
+ }
+
+ static int write_one(struct sha1file *f,
+ struct object_entry *e,
+ off_t *offset)
+ {
+ unsigned long size;
+
+ /* offset is non zero if object is written already. */
+ if (e->idx.offset || e->preferred_base)
+ return -1;
+
+ /* if we are deltified, write out base object first. */
+ if (e->delta && !write_one(f, e->delta, offset))
+ return 0;
+
+ e->idx.offset = *offset;
+ size = write_object(f, e, *offset);
+ if (!size) {
+ e->idx.offset = 0;
+ return 0;
+ }
+ written_list[nr_written++] = &e->idx;
+
+ /* make sure off_t is sufficiently large not to wrap */
+ if (*offset > *offset + size)
+ die("pack too large for current definition of off_t");
+ *offset += size;
+ return 1;
+ }
+
- mode_t mode = umask(0);
+ static void write_pack_file(void)
+ {
+ uint32_t i = 0, j;
+ struct sha1file *f;
+ off_t offset;
+ struct pack_header hdr;
+ uint32_t nr_remaining = nr_result;
+ time_t last_mtime = 0;
+
+ if (progress > pack_to_stdout)
+ progress_state = start_progress("Writing objects", nr_result);
+ written_list = xmalloc(nr_objects * sizeof(*written_list));
+
+ do {
+ unsigned char sha1[20];
+ char *pack_tmp_name = NULL;
+
+ if (pack_to_stdout) {
+ f = sha1fd_throughput(1, "<stdout>", progress_state);
+ } else {
+ char tmpname[PATH_MAX];
+ int fd;
+ fd = odb_mkstemp(tmpname, sizeof(tmpname),
+ "pack/tmp_pack_XXXXXX");
+ pack_tmp_name = xstrdup(tmpname);
+ f = sha1fd(fd, pack_tmp_name);
+ }
+
+ hdr.hdr_signature = htonl(PACK_SIGNATURE);
+ hdr.hdr_version = htonl(PACK_VERSION);
+ hdr.hdr_entries = htonl(nr_remaining);
+ sha1write(f, &hdr, sizeof(hdr));
+ offset = sizeof(hdr);
+ nr_written = 0;
+ for (; i < nr_objects; i++) {
+ if (!write_one(f, objects + i, &offset))
+ break;
+ display_progress(progress_state, written);
+ }
+
+ /*
+ * Did we write the wrong # entries in the header?
+ * If so, rewrite it like in fast-import
+ */
+ if (pack_to_stdout) {
+ sha1close(f, sha1, CSUM_CLOSE);
+ } else if (nr_written == nr_remaining) {
+ sha1close(f, sha1, CSUM_FSYNC);
+ } else {
+ int fd = sha1close(f, sha1, 0);
+ fixup_pack_header_footer(fd, sha1, pack_tmp_name,
+ nr_written, sha1, offset);
+ close(fd);
+ }
+
+ if (!pack_to_stdout) {
- umask(mode);
- mode = 0444 & ~mode;
-
+ struct stat st;
+ const char *idx_tmp_name;
+ char tmpname[PATH_MAX];
+
- if (adjust_perm(pack_tmp_name, mode))
+ idx_tmp_name = write_idx_file(NULL, written_list,
+ nr_written, sha1);
+
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+ base_name, sha1_to_hex(sha1));
+ free_pack_by_name(tmpname);
- if (adjust_perm(idx_tmp_name, mode))
++ if (adjust_shared_perm(pack_tmp_name))
+ die_errno("unable to make temporary pack file readable");
+ if (rename(pack_tmp_name, tmpname))
+ die_errno("unable to rename temporary pack file");
+
+ /*
+ * Packs are runtime accessed in their mtime
+ * order since newer packs are more likely to contain
+ * younger objects. So if we are creating multiple
+ * packs then we should modify the mtime of later ones
+ * to preserve this property.
+ */
+ if (stat(tmpname, &st) < 0) {
+ warning("failed to stat %s: %s",
+ tmpname, strerror(errno));
+ } else if (!last_mtime) {
+ last_mtime = st.st_mtime;
+ } else {
+ struct utimbuf utb;
+ utb.actime = st.st_atime;
+ utb.modtime = --last_mtime;
+ if (utime(tmpname, &utb) < 0)
+ warning("failed utime() on %s: %s",
+ tmpname, strerror(errno));
+ }
+
+ snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+ base_name, sha1_to_hex(sha1));
-static int adjust_perm(const char *path, mode_t mode)
-{
- if (chmod(path, mode))
- return -1;
- return adjust_shared_perm(path);
-}
-
++ if (adjust_shared_perm(idx_tmp_name))
+ die_errno("unable to make temporary index file readable");
+ if (rename(idx_tmp_name, tmpname))
+ die_errno("unable to rename temporary index file");
+
+ free((void *) idx_tmp_name);
+ free(pack_tmp_name);
+ puts(sha1_to_hex(sha1));
+ }
+
+ /* mark written objects as written to previous pack */
+ for (j = 0; j < nr_written; j++) {
+ written_list[j]->offset = (off_t)-1;
+ }
+ nr_remaining -= nr_written;
+ } while (nr_remaining && i < nr_objects);
+
+ free(written_list);
+ stop_progress(&progress_state);
+ if (written != nr_result)
+ die("wrote %"PRIu32" objects while expecting %"PRIu32,
+ written, nr_result);
+ }
+
+ static int locate_object_entry_hash(const unsigned char *sha1)
+ {
+ int i;
+ unsigned int ui;
+ memcpy(&ui, sha1, sizeof(unsigned int));
+ i = ui % object_ix_hashsz;
+ while (0 < object_ix[i]) {
+ if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
+ return i;
+ if (++i == object_ix_hashsz)
+ i = 0;
+ }
+ return -1 - i;
+ }
+
+ static struct object_entry *locate_object_entry(const unsigned char *sha1)
+ {
+ int i;
+
+ if (!object_ix_hashsz)
+ return NULL;
+
+ i = locate_object_entry_hash(sha1);
+ if (0 <= i)
+ return &objects[object_ix[i]-1];
+ return NULL;
+ }
+
+ static void rehash_objects(void)
+ {
+ uint32_t i;
+ struct object_entry *oe;
+
+ object_ix_hashsz = nr_objects * 3;
+ if (object_ix_hashsz < 1024)
+ object_ix_hashsz = 1024;
+ object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
+ memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
+ for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
+ int ix = locate_object_entry_hash(oe->idx.sha1);
+ if (0 <= ix)
+ continue;
+ ix = -1 - ix;
+ object_ix[ix] = i + 1;
+ }
+ }
+
+ static unsigned name_hash(const char *name)
+ {
+ unsigned c, hash = 0;
+
+ if (!name)
+ return 0;
+
+ /*
+ * This effectively just creates a sortable number from the
+ * last sixteen non-whitespace characters. Last characters
+ * count "most", so things that end in ".c" sort together.
+ */
+ while ((c = *name++) != 0) {
+ if (isspace(c))
+ continue;
+ hash = (hash >> 2) + (c << 24);
+ }
+ return hash;
+ }
+
+ static void setup_delta_attr_check(struct git_attr_check *check)
+ {
+ static struct git_attr *attr_delta;
+
+ if (!attr_delta)
+ attr_delta = git_attr("delta");
+
+ check[0].attr = attr_delta;
+ }
+
+ static int no_try_delta(const char *path)
+ {
+ struct git_attr_check check[1];
+
+ setup_delta_attr_check(check);
+ if (git_checkattr(path, ARRAY_SIZE(check), check))
+ return 0;
+ if (ATTR_FALSE(check->value))
+ return 1;
+ return 0;
+ }
+
+ static int add_object_entry(const unsigned char *sha1, enum object_type type,
+ const char *name, int exclude)
+ {
+ struct object_entry *entry;
+ struct packed_git *p, *found_pack = NULL;
+ off_t found_offset = 0;
+ int ix;
+ unsigned hash = name_hash(name);
+
+ ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
+ if (ix >= 0) {
+ if (exclude) {
+ entry = objects + object_ix[ix] - 1;
+ if (!entry->preferred_base)
+ nr_result--;
+ entry->preferred_base = 1;
+ }
+ return 0;
+ }
+
+ if (!exclude && local && has_loose_object_nonlocal(sha1))
+ return 0;
+
+ for (p = packed_git; p; p = p->next) {
+ off_t offset = find_pack_entry_one(sha1, p);
+ if (offset) {
+ if (!found_pack) {
+ found_offset = offset;
+ found_pack = p;
+ }
+ if (exclude)
+ break;
+ if (incremental)
+ return 0;
+ if (local && !p->pack_local)
+ return 0;
+ if (ignore_packed_keep && p->pack_local && p->pack_keep)
+ return 0;
+ }
+ }
+
+ if (nr_objects >= nr_alloc) {
+ nr_alloc = (nr_alloc + 1024) * 3 / 2;
+ objects = xrealloc(objects, nr_alloc * sizeof(*entry));
+ }
+
+ entry = objects + nr_objects++;
+ memset(entry, 0, sizeof(*entry));
+ hashcpy(entry->idx.sha1, sha1);
+ entry->hash = hash;
+ if (type)
+ entry->type = type;
+ if (exclude)
+ entry->preferred_base = 1;
+ else
+ nr_result++;
+ if (found_pack) {
+ entry->in_pack = found_pack;
+ entry->in_pack_offset = found_offset;
+ }
+
+ if (object_ix_hashsz * 3 <= nr_objects * 4)
+ rehash_objects();
+ else
+ object_ix[-1 - ix] = nr_objects;
+
+ display_progress(progress_state, nr_objects);
+
+ if (name && no_try_delta(name))
+ entry->no_try_delta = 1;
+
+ return 1;
+ }
+
+ struct pbase_tree_cache {
+ unsigned char sha1[20];
+ int ref;
+ int temporary;
+ void *tree_data;
+ unsigned long tree_size;
+ };
+
+ static struct pbase_tree_cache *(pbase_tree_cache[256]);
+ static int pbase_tree_cache_ix(const unsigned char *sha1)
+ {
+ return sha1[0] % ARRAY_SIZE(pbase_tree_cache);
+ }
+ static int pbase_tree_cache_ix_incr(int ix)
+ {
+ return (ix+1) % ARRAY_SIZE(pbase_tree_cache);
+ }
+
+ static struct pbase_tree {
+ struct pbase_tree *next;
+ /* This is a phony "cache" entry; we are not
+ * going to evict it nor find it through _get()
+ * mechanism -- this is for the toplevel node that
+ * would almost always change with any commit.
+ */
+ struct pbase_tree_cache pcache;
+ } *pbase_tree;
+
+ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
+ {
+ struct pbase_tree_cache *ent, *nent;
+ void *data;
+ unsigned long size;
+ enum object_type type;
+ int neigh;
+ int my_ix = pbase_tree_cache_ix(sha1);
+ int available_ix = -1;
+
+ /* pbase-tree-cache acts as a limited hashtable.
+ * your object will be found at your index or within a few
+ * slots after that slot if it is cached.
+ */
+ for (neigh = 0; neigh < 8; neigh++) {
+ ent = pbase_tree_cache[my_ix];
+ if (ent && !hashcmp(ent->sha1, sha1)) {
+ ent->ref++;
+ return ent;
+ }
+ else if (((available_ix < 0) && (!ent || !ent->ref)) ||
+ ((0 <= available_ix) &&
+ (!ent && pbase_tree_cache[available_ix])))
+ available_ix = my_ix;
+ if (!ent)
+ break;
+ my_ix = pbase_tree_cache_ix_incr(my_ix);
+ }
+
+ /* Did not find one. Either we got a bogus request or
+ * we need to read and perhaps cache.
+ */
+ data = read_sha1_file(sha1, &type, &size);
+ if (!data)
+ return NULL;
+ if (type != OBJ_TREE) {
+ free(data);
+ return NULL;
+ }
+
+ /* We need to either cache or return a throwaway copy */
+
+ if (available_ix < 0)
+ ent = NULL;
+ else {
+ ent = pbase_tree_cache[available_ix];
+ my_ix = available_ix;
+ }
+
+ if (!ent) {
+ nent = xmalloc(sizeof(*nent));
+ nent->temporary = (available_ix < 0);
+ }
+ else {
+ /* evict and reuse */
+ free(ent->tree_data);
+ nent = ent;
+ }
+ hashcpy(nent->sha1, sha1);
+ nent->tree_data = data;
+ nent->tree_size = size;
+ nent->ref = 1;
+ if (!nent->temporary)
+ pbase_tree_cache[my_ix] = nent;
+ return nent;
+ }
+
+ static void pbase_tree_put(struct pbase_tree_cache *cache)
+ {
+ if (!cache->temporary) {
+ cache->ref--;
+ return;
+ }
+ free(cache->tree_data);
+ free(cache);
+ }
+
+ static int name_cmp_len(const char *name)
+ {
+ int i;
+ for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++)
+ ;
+ return i;
+ }
+
+ static void add_pbase_object(struct tree_desc *tree,
+ const char *name,
+ int cmplen,
+ const char *fullname)
+ {
+ struct name_entry entry;
+ int cmp;
+
+ while (tree_entry(tree,&entry)) {
+ if (S_ISGITLINK(entry.mode))
+ continue;
+ cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+ memcmp(name, entry.path, cmplen);
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ return;
+ if (name[cmplen] != '/') {
+ add_object_entry(entry.sha1,
+ object_type(entry.mode),
+ fullname, 1);
+ return;
+ }
+ if (S_ISDIR(entry.mode)) {
+ struct tree_desc sub;
+ struct pbase_tree_cache *tree;
+ const char *down = name+cmplen+1;
+ int downlen = name_cmp_len(down);
+
+ tree = pbase_tree_get(entry.sha1);
+ if (!tree)
+ return;
+ init_tree_desc(&sub, tree->tree_data, tree->tree_size);
+
+ add_pbase_object(&sub, down, downlen, fullname);
+ pbase_tree_put(tree);
+ }
+ }
+ }
+
+ static unsigned *done_pbase_paths;
+ static int done_pbase_paths_num;
+ static int done_pbase_paths_alloc;
+ static int done_pbase_path_pos(unsigned hash)
+ {
+ int lo = 0;
+ int hi = done_pbase_paths_num;
+ while (lo < hi) {
+ int mi = (hi + lo) / 2;
+ if (done_pbase_paths[mi] == hash)
+ return mi;
+ if (done_pbase_paths[mi] < hash)
+ hi = mi;
+ else
+ lo = mi + 1;
+ }
+ return -lo-1;
+ }
+
+ static int check_pbase_path(unsigned hash)
+ {
+ int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash);
+ if (0 <= pos)
+ return 1;
+ pos = -pos - 1;
+ if (done_pbase_paths_alloc <= done_pbase_paths_num) {
+ done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
+ done_pbase_paths = xrealloc(done_pbase_paths,
+ done_pbase_paths_alloc *
+ sizeof(unsigned));
+ }
+ done_pbase_paths_num++;
+ if (pos < done_pbase_paths_num)
+ memmove(done_pbase_paths + pos + 1,
+ done_pbase_paths + pos,
+ (done_pbase_paths_num - pos - 1) * sizeof(unsigned));
+ done_pbase_paths[pos] = hash;
+ return 0;
+ }
+
+ static void add_preferred_base_object(const char *name)
+ {
+ struct pbase_tree *it;
+ int cmplen;
+ unsigned hash = name_hash(name);
+
+ if (!num_preferred_base || check_pbase_path(hash))
+ return;
+
+ cmplen = name_cmp_len(name);
+ for (it = pbase_tree; it; it = it->next) {
+ if (cmplen == 0) {
+ add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
+ }
+ else {
+ struct tree_desc tree;
+ init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size);
+ add_pbase_object(&tree, name, cmplen, name);
+ }
+ }
+ }
+
+ static void add_preferred_base(unsigned char *sha1)
+ {
+ struct pbase_tree *it;
+ void *data;
+ unsigned long size;
+ unsigned char tree_sha1[20];
+
+ if (window <= num_preferred_base++)
+ return;
+
+ data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
+ if (!data)
+ return;
+
+ for (it = pbase_tree; it; it = it->next) {
+ if (!hashcmp(it->pcache.sha1, tree_sha1)) {
+ free(data);
+ return;
+ }
+ }
+
+ it = xcalloc(1, sizeof(*it));
+ it->next = pbase_tree;
+ pbase_tree = it;
+
+ hashcpy(it->pcache.sha1, tree_sha1);
+ it->pcache.tree_data = data;
+ it->pcache.tree_size = size;
+ }
+
+ static void cleanup_preferred_base(void)
+ {
+ struct pbase_tree *it;
+ unsigned i;
+
+ it = pbase_tree;
+ pbase_tree = NULL;
+ while (it) {
+ struct pbase_tree *this = it;
+ it = this->next;
+ free(this->pcache.tree_data);
+ free(this);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) {
+ if (!pbase_tree_cache[i])
+ continue;
+ free(pbase_tree_cache[i]->tree_data);
+ free(pbase_tree_cache[i]);
+ pbase_tree_cache[i] = NULL;
+ }
+
+ free(done_pbase_paths);
+ done_pbase_paths = NULL;
+ done_pbase_paths_num = done_pbase_paths_alloc = 0;
+ }
+
+ static void check_object(struct object_entry *entry)
+ {
+ if (entry->in_pack) {
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ const unsigned char *base_ref = NULL;
+ struct object_entry *base_entry;
+ unsigned long used, used_0;
+ unsigned int avail;
+ off_t ofs;
+ unsigned char *buf, c;
+
+ buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
+
+ /*
+ * We want in_pack_type even if we do not reuse delta
+ * since non-delta representations could still be reused.
+ */
+ used = unpack_object_header_buffer(buf, avail,
+ &entry->in_pack_type,
+ &entry->size);
+ if (used == 0)
+ goto give_up;
+
+ /*
+ * Determine if this is a delta and if so whether we can
+ * reuse it or not. Otherwise let's find out as cheaply as
+ * possible what the actual type and size for this object is.
+ */
+ switch (entry->in_pack_type) {
+ default:
+ /* Not a delta hence we've already got all we need. */
+ entry->type = entry->in_pack_type;
+ entry->in_pack_header_size = used;
+ if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB)
+ goto give_up;
+ unuse_pack(&w_curs);
+ return;
+ case OBJ_REF_DELTA:
+ if (reuse_delta && !entry->preferred_base)
+ base_ref = use_pack(p, &w_curs,
+ entry->in_pack_offset + used, NULL);
+ entry->in_pack_header_size = used + 20;
+ break;
+ case OBJ_OFS_DELTA:
+ buf = use_pack(p, &w_curs,
+ entry->in_pack_offset + used, NULL);
+ used_0 = 0;
+ c = buf[used_0++];
+ ofs = c & 127;
+ while (c & 128) {
+ ofs += 1;
+ if (!ofs || MSB(ofs, 7)) {
+ error("delta base offset overflow in pack for %s",
+ sha1_to_hex(entry->idx.sha1));
+ goto give_up;
+ }
+ c = buf[used_0++];
+ ofs = (ofs << 7) + (c & 127);
+ }
+ ofs = entry->in_pack_offset - ofs;
+ if (ofs <= 0 || ofs >= entry->in_pack_offset) {
+ error("delta base offset out of bound for %s",
+ sha1_to_hex(entry->idx.sha1));
+ goto give_up;
+ }
+ if (reuse_delta && !entry->preferred_base) {
+ struct revindex_entry *revidx;
+ revidx = find_pack_revindex(p, ofs);
+ if (!revidx)
+ goto give_up;
+ base_ref = nth_packed_object_sha1(p, revidx->nr);
+ }
+ entry->in_pack_header_size = used + used_0;
+ break;
+ }
+
+ if (base_ref && (base_entry = locate_object_entry(base_ref))) {
+ /*
+ * If base_ref was set above that means we wish to
+ * reuse delta data, and we even found that base
+ * in the list of objects we want to pack. Goodie!
+ *
+ * Depth value does not matter - find_deltas() will
+ * never consider reused delta as the base object to
+ * deltify other objects against, in order to avoid
+ * circular deltas.
+ */
+ entry->type = entry->in_pack_type;
+ entry->delta = base_entry;
+ entry->delta_size = entry->size;
+ entry->delta_sibling = base_entry->delta_child;
+ base_entry->delta_child = entry;
+ unuse_pack(&w_curs);
+ return;
+ }
+
+ if (entry->type) {
+ /*
+ * This must be a delta and we already know what the
+ * final object type is. Let's extract the actual
+ * object size from the delta header.
+ */
+ entry->size = get_size_from_delta(p, &w_curs,
+ entry->in_pack_offset + entry->in_pack_header_size);
+ if (entry->size == 0)
+ goto give_up;
+ unuse_pack(&w_curs);
+ return;
+ }
+
+ /*
+ * No choice but to fall back to the recursive delta walk
+ * with sha1_object_info() to find about the object type
+ * at this point...
+ */
+ give_up:
+ unuse_pack(&w_curs);
+ }
+
+ entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
+ /*
+ * The error condition is checked in prepare_pack(). This is
+ * to permit a missing preferred base object to be ignored
+ * as a preferred base. Doing so can result in a larger
+ * pack file, but the transfer will still take place.
+ */
+ }
+
+ static int pack_offset_sort(const void *_a, const void *_b)
+ {
+ const struct object_entry *a = *(struct object_entry **)_a;
+ const struct object_entry *b = *(struct object_entry **)_b;
+
+ /* avoid filesystem trashing with loose objects */
+ if (!a->in_pack && !b->in_pack)
+ return hashcmp(a->idx.sha1, b->idx.sha1);
+
+ if (a->in_pack < b->in_pack)
+ return -1;
+ if (a->in_pack > b->in_pack)
+ return 1;
+ return a->in_pack_offset < b->in_pack_offset ? -1 :
+ (a->in_pack_offset > b->in_pack_offset);
+ }
+
+ static void get_object_details(void)
+ {
+ uint32_t i;
+ struct object_entry **sorted_by_offset;
+
+ sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
+ for (i = 0; i < nr_objects; i++)
+ sorted_by_offset[i] = objects + i;
+ qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
+
+ for (i = 0; i < nr_objects; i++)
+ check_object(sorted_by_offset[i]);
+
+ free(sorted_by_offset);
+ }
+
+ /*
+ * We search for deltas in a list sorted by type, by filename hash, and then
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller -- deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one. The deepest deltas are therefore the oldest objects which are
+ * less susceptible to be accessed often.
+ */
+ static int type_size_sort(const void *_a, const void *_b)
+ {
+ const struct object_entry *a = *(struct object_entry **)_a;
+ const struct object_entry *b = *(struct object_entry **)_b;
+
+ if (a->type > b->type)
+ return -1;
+ if (a->type < b->type)
+ return 1;
+ if (a->hash > b->hash)
+ return -1;
+ if (a->hash < b->hash)
+ return 1;
+ if (a->preferred_base > b->preferred_base)
+ return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
+ if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
+ return 1;
+ return a < b ? -1 : (a > b); /* newest first */
+ }
+
+ struct unpacked {
+ struct object_entry *entry;
+ void *data;
+ struct delta_index *index;
+ unsigned depth;
+ };
+
+ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
+ unsigned long delta_size)
+ {
+ if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
+ return 0;
+
+ if (delta_size < cache_max_small_delta_size)
+ return 1;
+
+ /* cache delta, if objects are large enough compared to delta size */
+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+ return 1;
+
+ return 0;
+ }
+
+ #ifndef NO_PTHREADS
+
+ static pthread_mutex_t read_mutex;
+ #define read_lock() pthread_mutex_lock(&read_mutex)
+ #define read_unlock() pthread_mutex_unlock(&read_mutex)
+
+ static pthread_mutex_t cache_mutex;
+ #define cache_lock() pthread_mutex_lock(&cache_mutex)
+ #define cache_unlock() pthread_mutex_unlock(&cache_mutex)
+
+ static pthread_mutex_t progress_mutex;
+ #define progress_lock() pthread_mutex_lock(&progress_mutex)
+ #define progress_unlock() pthread_mutex_unlock(&progress_mutex)
+
+ #else
+
+ #define read_lock() (void)0
+ #define read_unlock() (void)0
+ #define cache_lock() (void)0
+ #define cache_unlock() (void)0
+ #define progress_lock() (void)0
+ #define progress_unlock() (void)0
+
+ #endif
+
+ static int try_delta(struct unpacked *trg, struct unpacked *src,
+ unsigned max_depth, unsigned long *mem_usage)
+ {
+ struct object_entry *trg_entry = trg->entry;
+ struct object_entry *src_entry = src->entry;
+ unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+ unsigned ref_depth;
+ enum object_type type;
+ void *delta_buf;
+
+ /* Don't bother doing diffs between different types */
+ if (trg_entry->type != src_entry->type)
+ return -1;
+
+ /*
+ * We do not bother to try a delta that we discarded
+ * on an earlier try, but only when reusing delta data.
+ */
+ if (reuse_delta && trg_entry->in_pack &&
+ trg_entry->in_pack == src_entry->in_pack &&
+ trg_entry->in_pack_type != OBJ_REF_DELTA &&
+ trg_entry->in_pack_type != OBJ_OFS_DELTA)
+ return 0;
+
+ /* Let's not bust the allowed depth. */
+ if (src->depth >= max_depth)
+ return 0;
+
+ /* Now some size filtering heuristics. */
+ trg_size = trg_entry->size;
+ if (!trg_entry->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_entry->delta_size;
+ ref_depth = trg->depth;
+ }
+ max_size = (uint64_t)max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
+ if (max_size == 0)
+ return 0;
+ src_size = src_entry->size;
+ sizediff = src_size < trg_size ? trg_size - src_size : 0;
+ if (sizediff >= max_size)
+ return 0;
+ if (trg_size < src_size / 32)
+ return 0;
+
+ /* Load data if not already done */
+ if (!trg->data) {
+ read_lock();
+ trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
+ read_unlock();
+ if (!trg->data)
+ die("object %s cannot be read",
+ sha1_to_hex(trg_entry->idx.sha1));
+ if (sz != trg_size)
+ die("object %s inconsistent object length (%lu vs %lu)",
+ sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+ *mem_usage += sz;
+ }
+ if (!src->data) {
+ read_lock();
+ src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
+ read_unlock();
+ if (!src->data)
+ die("object %s cannot be read",
+ sha1_to_hex(src_entry->idx.sha1));
+ if (sz != src_size)
+ die("object %s inconsistent object length (%lu vs %lu)",
+ sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+ *mem_usage += sz;
+ }
+ if (!src->index) {
+ src->index = create_delta_index(src->data, src_size);
+ if (!src->index) {
+ static int warned = 0;
+ if (!warned++)
+ warning("suboptimal pack - out of memory");
+ return 0;
+ }
+ *mem_usage += sizeof_delta_index(src->index);
+ }
+
+ delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
+ if (!delta_buf)
+ return 0;
+
+ if (trg_entry->delta) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_entry->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ free(delta_buf);
+ return 0;
+ }
+ }
+
+ /*
+ * Handle memory allocation outside of the cache
+ * accounting lock. Compiler will optimize the strangeness
+ * away when NO_PTHREADS is defined.
+ */
+ free(trg_entry->delta_data);
+ cache_lock();
+ if (trg_entry->delta_data) {
+ delta_cache_size -= trg_entry->delta_size;
+ trg_entry->delta_data = NULL;
+ }
+ if (delta_cacheable(src_size, trg_size, delta_size)) {
+ delta_cache_size += delta_size;
+ cache_unlock();
+ trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+ } else {
+ cache_unlock();
+ free(delta_buf);
+ }
+
+ trg_entry->delta = src_entry;
+ trg_entry->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
+ return 1;
+ }
+
+ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
+ {
+ struct object_entry *child = me->delta_child;
+ unsigned int m = n;
+ while (child) {
+ unsigned int c = check_delta_limit(child, n + 1);
+ if (m < c)
+ m = c;
+ child = child->delta_sibling;
+ }
+ return m;
+ }
+
+ static unsigned long free_unpacked(struct unpacked *n)
+ {
+ unsigned long freed_mem = sizeof_delta_index(n->index);
+ free_delta_index(n->index);
+ n->index = NULL;
+ if (n->data) {
+ freed_mem += n->entry->size;
+ free(n->data);
+ n->data = NULL;
+ }
+ n->entry = NULL;
+ n->depth = 0;
+ return freed_mem;
+ }
+
+ static void find_deltas(struct object_entry **list, unsigned *list_size,
+ int window, int depth, unsigned *processed)
+ {
+ uint32_t i, idx = 0, count = 0;
+ struct unpacked *array;
+ unsigned long mem_usage = 0;
+
+ array = xcalloc(window, sizeof(struct unpacked));
+
+ for (;;) {
+ struct object_entry *entry;
+ struct unpacked *n = array + idx;
+ int j, max_depth, best_base = -1;
+
+ progress_lock();
+ if (!*list_size) {
+ progress_unlock();
+ break;
+ }
+ entry = *list++;
+ (*list_size)--;
+ if (!entry->preferred_base) {
+ (*processed)++;
+ display_progress(progress_state, *processed);
+ }
+ progress_unlock();
+
+ mem_usage -= free_unpacked(n);
+ n->entry = entry;
+
+ while (window_memory_limit &&
+ mem_usage > window_memory_limit &&
+ count > 1) {
+ uint32_t tail = (idx + window - count) % window;
+ mem_usage -= free_unpacked(array + tail);
+ count--;
+ }
+
+ /* We do not compute delta to *create* objects we are not
+ * going to pack.
+ */
+ if (entry->preferred_base)
+ goto next;
+
+ /*
+ * If the current object is at pack edge, take the depth the
+ * objects that depend on the current object into account
+ * otherwise they would become too deep.
+ */
+ max_depth = depth;
+ if (entry->delta_child) {
+ max_depth -= check_delta_limit(entry, 0);
+ if (max_depth <= 0)
+ goto next;
+ }
+
+ j = window;
+ while (--j > 0) {
+ int ret;
+ uint32_t other_idx = idx + j;
+ struct unpacked *m;
+ if (other_idx >= window)
+ other_idx -= window;
+ m = array + other_idx;
+ if (!m->entry)
+ break;
+ ret = try_delta(n, m, max_depth, &mem_usage);
+ if (ret < 0)
+ break;
+ else if (ret > 0)
+ best_base = other_idx;
+ }
+
+ /*
+ * If we decided to cache the delta data, then it is best
+ * to compress it right away. First because we have to do
+ * it anyway, and doing it here while we're threaded will
+ * save a lot of time in the non threaded write phase,
+ * as well as allow for caching more deltas within
+ * the same cache size limit.
+ * ...
+ * But only if not writing to stdout, since in that case
+ * the network is most likely throttling writes anyway,
+ * and therefore it is best to go to the write phase ASAP
+ * instead, as we can afford spending more time compressing
+ * between writes at that moment.
+ */
+ if (entry->delta_data && !pack_to_stdout) {
+ entry->z_delta_size = do_compress(&entry->delta_data,
+ entry->delta_size);
+ cache_lock();
+ delta_cache_size -= entry->delta_size;
+ delta_cache_size += entry->z_delta_size;
+ cache_unlock();
+ }
+
+ /* if we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ */
+ if (entry->delta && max_depth <= n->depth)
+ continue;
+
+ /*
+ * Move the best delta base up in the window, after the
+ * currently deltified object, to keep it longer. It will
+ * be the first base object to be attempted next.
+ */
+ if (entry->delta) {
+ struct unpacked swap = array[best_base];
+ int dist = (window + idx - best_base) % window;
+ int dst = best_base;
+ while (dist--) {
+ int src = (dst + 1) % window;
+ array[dst] = array[src];
+ dst = src;
+ }
+ array[dst] = swap;
+ }
+
+ next:
+ idx++;
+ if (count + 1 < window)
+ count++;
+ if (idx >= window)
+ idx = 0;
+ }
+
+ for (i = 0; i < window; ++i) {
+ free_delta_index(array[i].index);
+ free(array[i].data);
+ }
+ free(array);
+ }
+
+ #ifndef NO_PTHREADS
+
+ /*
+ * The main thread waits on the condition that (at least) one of the workers
+ * has stopped working (which is indicated in the .working member of
+ * struct thread_params).
+ * When a work thread has completed its work, it sets .working to 0 and
+ * signals the main thread and waits on the condition that .data_ready
+ * becomes 1.
+ */
+
+ struct thread_params {
+ pthread_t thread;
+ struct object_entry **list;
+ unsigned list_size;
+ unsigned remaining;
+ int window;
+ int depth;
+ int working;
+ int data_ready;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ unsigned *processed;
+ };
+
+ static pthread_cond_t progress_cond;
+
+ /*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+ static void init_threaded_search(void)
+ {
+ pthread_mutex_init(&read_mutex, NULL);
+ pthread_mutex_init(&cache_mutex, NULL);
+ pthread_mutex_init(&progress_mutex, NULL);
+ pthread_cond_init(&progress_cond, NULL);
+ }
+
+ static void cleanup_threaded_search(void)
+ {
+ pthread_cond_destroy(&progress_cond);
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&cache_mutex);
+ pthread_mutex_destroy(&progress_mutex);
+ }
+
+ static void *threaded_find_deltas(void *arg)
+ {
+ struct thread_params *me = arg;
+
+ while (me->remaining) {
+ find_deltas(me->list, &me->remaining,
+ me->window, me->depth, me->processed);
+
+ progress_lock();
+ me->working = 0;
+ pthread_cond_signal(&progress_cond);
+ progress_unlock();
+
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ pthread_mutex_lock(&me->mutex);
+ while (!me->data_ready)
+ pthread_cond_wait(&me->cond, &me->mutex);
+ me->data_ready = 0;
+ pthread_mutex_unlock(&me->mutex);
+ }
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
+ }
+
+ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
+ int window, int depth, unsigned *processed)
+ {
+ struct thread_params *p;
+ int i, ret, active_threads = 0;
+
+ init_threaded_search();
+
+ if (!delta_search_threads) /* --threads=0 means autodetect */
+ delta_search_threads = online_cpus();
+ if (delta_search_threads <= 1) {
+ find_deltas(list, &list_size, window, depth, processed);
+ cleanup_threaded_search();
+ return;
+ }
+ if (progress > pack_to_stdout)
+ fprintf(stderr, "Delta compression using up to %d threads.\n",
+ delta_search_threads);
+ p = xcalloc(delta_search_threads, sizeof(*p));
+
+ /* Partition the work amongst work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ unsigned sub_size = list_size / (delta_search_threads - i);
+
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < delta_search_threads)
+ sub_size = 0;
+
+ p[i].window = window;
+ p[i].depth = depth;
+ p[i].processed = processed;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads. */
+ for (i = 0; i < delta_search_threads; i++) {
+ if (!p[i].list_size)
+ continue;
+ pthread_mutex_init(&p[i].mutex, NULL);
+ pthread_cond_init(&p[i].cond, NULL);
+ ret = pthread_create(&p[i].thread, NULL,
+ threaded_find_deltas, &p[i]);
+ if (ret)
+ die("unable to create thread: %s", strerror(ret));
+ active_threads++;
+ }
+
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
+
+ progress_lock();
+ for (;;) {
+ for (i = 0; !target && i < delta_search_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ pthread_cond_wait(&progress_cond, &progress_mutex);
+ }
+
+ for (i = 0; i < delta_search_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ progress_unlock();
+
+ pthread_mutex_lock(&target->mutex);
+ target->data_ready = 1;
+ pthread_cond_signal(&target->cond);
+ pthread_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
+ pthread_join(target->thread, NULL);
+ pthread_cond_destroy(&target->cond);
+ pthread_mutex_destroy(&target->mutex);
+ active_threads--;
+ }
+ }
+ cleanup_threaded_search();
+ free(p);
+ }
+
+ #else
+ #define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
+ #endif
+
+ static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ unsigned char peeled[20];
+
+ if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
+ !peel_ref(path, peeled) && /* peelable? */
+ !is_null_sha1(peeled) && /* annotated tag? */
+ locate_object_entry(peeled)) /* object packed? */
+ add_object_entry(sha1, OBJ_TAG, NULL, 0);
+ return 0;
+ }
+
+ static void prepare_pack(int window, int depth)
+ {
+ struct object_entry **delta_list;
+ uint32_t i, nr_deltas;
+ unsigned n;
+
+ get_object_details();
+
+ /*
+ * If we're locally repacking then we need to be doubly careful
+ * from now on in order to make sure no stealth corruption gets
+ * propagated to the new pack. Clients receiving streamed packs
+ * should validate everything they get anyway so no need to incur
+ * the additional cost here in that case.
+ */
+ if (!pack_to_stdout)
+ do_check_packed_object_crc = 1;
+
+ if (!nr_objects || !window || !depth)
+ return;
+
+ delta_list = xmalloc(nr_objects * sizeof(*delta_list));
+ nr_deltas = n = 0;
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *entry = objects + i;
+
+ if (entry->delta)
+ /* This happens if we decided to reuse existing
+ * delta from a pack. "reuse_delta &&" is implied.
+ */
+ continue;
+
+ if (entry->size < 50)
+ continue;
+
+ if (entry->no_try_delta)
+ continue;
+
+ if (!entry->preferred_base) {
+ nr_deltas++;
+ if (entry->type < 0)
+ die("unable to get type of object %s",
+ sha1_to_hex(entry->idx.sha1));
+ } else {
+ if (entry->type < 0) {
+ /*
+ * This object is not found, but we
+ * don't have to include it anyway.
+ */
+ continue;
+ }
+ }
+
+ delta_list[n++] = entry;
+ }
+
+ if (nr_deltas && n > 1) {
+ unsigned nr_done = 0;
+ if (progress)
+ progress_state = start_progress("Compressing objects",
+ nr_deltas);
+ qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
+ ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
+ stop_progress(&progress_state);
+ if (nr_done != nr_deltas)
+ die("inconsistency with delta count");
+ }
+ free(delta_list);
+ }
+
+ static int git_pack_config(const char *k, const char *v, void *cb)
+ {
+ if (!strcmp(k, "pack.window")) {
+ window = git_config_int(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.windowmemory")) {
+ window_memory_limit = git_config_ulong(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.depth")) {
+ depth = git_config_int(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.compression")) {
+ int level = git_config_int(k, v);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", level);
+ pack_compression_level = level;
+ pack_compression_seen = 1;
+ return 0;
+ }
+ if (!strcmp(k, "pack.deltacachesize")) {
+ max_delta_cache_size = git_config_int(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.deltacachelimit")) {
+ cache_max_small_delta_size = git_config_int(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.threads")) {
+ delta_search_threads = git_config_int(k, v);
+ if (delta_search_threads < 0)
+ die("invalid number of threads specified (%d)",
+ delta_search_threads);
+ #ifdef NO_PTHREADS
+ if (delta_search_threads != 1)
+ warning("no threads support, ignoring %s", k);
+ #endif
+ return 0;
+ }
+ if (!strcmp(k, "pack.indexversion")) {
+ pack_idx_default_version = git_config_int(k, v);
+ if (pack_idx_default_version > 2)
+ die("bad pack.indexversion=%"PRIu32,
+ pack_idx_default_version);
+ return 0;
+ }
+ if (!strcmp(k, "pack.packsizelimit")) {
+ pack_size_limit_cfg = git_config_ulong(k, v);
+ return 0;
+ }
+ return git_default_config(k, v, cb);
+ }
+
+ static void read_object_list_from_stdin(void)
+ {
+ char line[40 + 1 + PATH_MAX + 2];
+ unsigned char sha1[20];
+
+ for (;;) {
+ if (!fgets(line, sizeof(line), stdin)) {
+ if (feof(stdin))
+ break;
+ if (!ferror(stdin))
+ die("fgets returned NULL, not EOF, not error!");
+ if (errno != EINTR)
+ die_errno("fgets");
+ clearerr(stdin);
+ continue;
+ }
+ if (line[0] == '-') {
+ if (get_sha1_hex(line+1, sha1))
+ die("expected edge sha1, got garbage:\n %s",
+ line);
+ add_preferred_base(sha1);
+ continue;
+ }
+ if (get_sha1_hex(line, sha1))
+ die("expected sha1, got garbage:\n %s", line);
+
+ add_preferred_base_object(line+41);
+ add_object_entry(sha1, 0, line+41, 0);
+ }
+ }
+
+ #define OBJECT_ADDED (1u<<20)
+
+ static void show_commit(struct commit *commit, void *data)
+ {
+ add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
+ commit->object.flags |= OBJECT_ADDED;
+ }
+
+ static void show_object(struct object *obj, const struct name_path *path, const char *last)
+ {
+ char *name = path_name(path, last);
+
+ add_preferred_base_object(name);
+ add_object_entry(obj->sha1, obj->type, name, 0);
+ obj->flags |= OBJECT_ADDED;
+
+ /*
+ * We will have generated the hash from the name,
+ * but not saved a pointer to it - we can free it
+ */
+ free((char *)name);
+ }
+
+ static void show_edge(struct commit *commit)
+ {
+ add_preferred_base(commit->object.sha1);
+ }
+
+ struct in_pack_object {
+ off_t offset;
+ struct object *object;
+ };
+
+ struct in_pack {
+ int alloc;
+ int nr;
+ struct in_pack_object *array;
+ };
+
+ static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+ {
+ in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
+ in_pack->array[in_pack->nr].object = object;
+ in_pack->nr++;
+ }
+
+ /*
+ * Compare the objects in the offset order, in order to emulate the
+ * "git rev-list --objects" output that produced the pack originally.
+ */
+ static int ofscmp(const void *a_, const void *b_)
+ {
+ struct in_pack_object *a = (struct in_pack_object *)a_;
+ struct in_pack_object *b = (struct in_pack_object *)b_;
+
+ if (a->offset < b->offset)
+ return -1;
+ else if (a->offset > b->offset)
+ return 1;
+ else
+ return hashcmp(a->object->sha1, b->object->sha1);
+ }
+
+ static void add_objects_in_unpacked_packs(struct rev_info *revs)
+ {
+ struct packed_git *p;
+ struct in_pack in_pack;
+ uint32_t i;
+
+ memset(&in_pack, 0, sizeof(in_pack));
+
+ for (p = packed_git; p; p = p->next) {
+ const unsigned char *sha1;
+ struct object *o;
+
+ if (!p->pack_local || p->pack_keep)
+ continue;
+ if (open_pack_index(p))
+ die("cannot open pack index");
+
+ ALLOC_GROW(in_pack.array,
+ in_pack.nr + p->num_objects,
+ in_pack.alloc);
+
+ for (i = 0; i < p->num_objects; i++) {
+ sha1 = nth_packed_object_sha1(p, i);
+ o = lookup_unknown_object(sha1);
+ if (!(o->flags & OBJECT_ADDED))
+ mark_in_pack_object(o, p, &in_pack);
+ o->flags |= OBJECT_ADDED;
+ }
+ }
+
+ if (in_pack.nr) {
+ qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
+ ofscmp);
+ for (i = 0; i < in_pack.nr; i++) {
+ struct object *o = in_pack.array[i].object;
+ add_object_entry(o->sha1, o->type, "", 0);
+ }
+ }
+ free(in_pack.array);
+ }
+
+ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
+ {
+ static struct packed_git *last_found = (void *)1;
+ struct packed_git *p;
+
+ p = (last_found != (void *)1) ? last_found : packed_git;
+
+ while (p) {
+ if ((!p->pack_local || p->pack_keep) &&
+ find_pack_entry_one(sha1, p)) {
+ last_found = p;
+ return 1;
+ }
+ if (p == last_found)
+ p = packed_git;
+ else
+ p = p->next;
+ if (p == last_found)
+ p = p->next;
+ }
+ return 0;
+ }
+
+ static void loosen_unused_packed_objects(struct rev_info *revs)
+ {
+ struct packed_git *p;
+ uint32_t i;
+ const unsigned char *sha1;
+
+ for (p = packed_git; p; p = p->next) {
+ if (!p->pack_local || p->pack_keep)
+ continue;
+
+ if (open_pack_index(p))
+ die("cannot open pack index");
+
+ for (i = 0; i < p->num_objects; i++) {
+ sha1 = nth_packed_object_sha1(p, i);
+ if (!locate_object_entry(sha1) &&
+ !has_sha1_pack_kept_or_nonlocal(sha1))
+ if (force_object_loose(sha1, p->mtime))
+ die("unable to force loose object");
+ }
+ }
+ }
+
+ static void get_object_list(int ac, const char **av)
+ {
+ struct rev_info revs;
+ char line[1000];
+ int flags = 0;
+
+ init_revisions(&revs, NULL);
+ save_commit_buffer = 0;
+ setup_revisions(ac, av, &revs, NULL);
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int len = strlen(line);
+ if (len && line[len - 1] == '\n')
+ line[--len] = 0;
+ if (!len)
+ break;
+ if (*line == '-') {
+ if (!strcmp(line, "--not")) {
+ flags ^= UNINTERESTING;
+ continue;
+ }
+ die("not a rev '%s'", line);
+ }
+ if (handle_revision_arg(line, &revs, flags, 1))
+ die("bad revision '%s'", line);
+ }
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ traverse_commit_list(&revs, show_commit, show_object, NULL);
+
+ if (keep_unreachable)
+ add_objects_in_unpacked_packs(&revs);
+ if (unpack_unreachable)
+ loosen_unused_packed_objects(&revs);
+ }
+
+ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
+ {
+ int use_internal_rev_list = 0;
+ int thin = 0;
+ int all_progress_implied = 0;
+ uint32_t i;
+ const char **rp_av;
+ int rp_ac_alloc = 64;
+ int rp_ac;
+
+ read_replace_refs = 0;
+
+ rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
+
+ rp_av[0] = "pack-objects";
+ rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
+ rp_ac = 2;
+
+ git_config(git_pack_config, NULL);
+ if (!pack_compression_seen && core_compression_seen)
+ pack_compression_level = core_compression_level;
+
+ progress = isatty(2);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg != '-')
+ break;
+
+ if (!strcmp("--non-empty", arg)) {
+ non_empty = 1;
+ continue;
+ }
+ if (!strcmp("--local", arg)) {
+ local = 1;
+ continue;
+ }
+ if (!strcmp("--incremental", arg)) {
+ incremental = 1;
+ continue;
+ }
+ if (!strcmp("--honor-pack-keep", arg)) {
+ ignore_packed_keep = 1;
+ continue;
+ }
+ if (!prefixcmp(arg, "--compression=")) {
+ char *end;
+ int level = strtoul(arg+14, &end, 0);
+ if (!arg[14] || *end)
+ usage(pack_usage);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", level);
+ pack_compression_level = level;
+ continue;
+ }
+ if (!prefixcmp(arg, "--max-pack-size=")) {
+ pack_size_limit_cfg = 0;
+ if (!git_parse_ulong(arg+16, &pack_size_limit))
+ usage(pack_usage);
+ continue;
+ }
+ if (!prefixcmp(arg, "--window=")) {
+ char *end;
+ window = strtoul(arg+9, &end, 0);
+ if (!arg[9] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!prefixcmp(arg, "--window-memory=")) {
+ if (!git_parse_ulong(arg+16, &window_memory_limit))
+ usage(pack_usage);
+ continue;
+ }
+ if (!prefixcmp(arg, "--threads=")) {
+ char *end;
+ delta_search_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || delta_search_threads < 0)
+ usage(pack_usage);
+ #ifdef NO_PTHREADS
+ if (delta_search_threads != 1)
+ warning("no threads support, "
+ "ignoring %s", arg);
+ #endif
+ continue;
+ }
+ if (!prefixcmp(arg, "--depth=")) {
+ char *end;
+ depth = strtoul(arg+8, &end, 0);
+ if (!arg[8] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strcmp("--progress", arg)) {
+ progress = 1;
+ continue;
+ }
+ if (!strcmp("--all-progress", arg)) {
+ progress = 2;
+ continue;
+ }
+ if (!strcmp("--all-progress-implied", arg)) {
+ all_progress_implied = 1;
+ continue;
+ }
+ if (!strcmp("-q", arg)) {
+ progress = 0;
+ continue;
+ }
+ if (!strcmp("--no-reuse-delta", arg)) {
+ reuse_delta = 0;
+ continue;
+ }
+ if (!strcmp("--no-reuse-object", arg)) {
+ reuse_object = reuse_delta = 0;
+ continue;
+ }
+ if (!strcmp("--delta-base-offset", arg)) {
+ allow_ofs_delta = 1;
+ continue;
+ }
+ if (!strcmp("--stdout", arg)) {
+ pack_to_stdout = 1;
+ continue;
+ }
+ if (!strcmp("--revs", arg)) {
+ use_internal_rev_list = 1;
+ continue;
+ }
+ if (!strcmp("--keep-unreachable", arg)) {
+ keep_unreachable = 1;
+ continue;
+ }
+ if (!strcmp("--unpack-unreachable", arg)) {
+ unpack_unreachable = 1;
+ continue;
+ }
+ if (!strcmp("--include-tag", arg)) {
+ include_tag = 1;
+ continue;
+ }
+ if (!strcmp("--unpacked", arg) ||
+ !strcmp("--reflog", arg) ||
+ !strcmp("--all", arg)) {
+ use_internal_rev_list = 1;
+ if (rp_ac >= rp_ac_alloc - 1) {
+ rp_ac_alloc = alloc_nr(rp_ac_alloc);
+ rp_av = xrealloc(rp_av,
+ rp_ac_alloc * sizeof(*rp_av));
+ }
+ rp_av[rp_ac++] = arg;
+ continue;
+ }
+ if (!strcmp("--thin", arg)) {
+ use_internal_rev_list = 1;
+ thin = 1;
+ rp_av[1] = "--objects-edge";
+ continue;
+ }
+ if (!prefixcmp(arg, "--index-version=")) {
+ char *c;
+ pack_idx_default_version = strtoul(arg + 16, &c, 10);
+ if (pack_idx_default_version > 2)
+ die("bad %s", arg);
+ if (*c == ',')
+ pack_idx_off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_off32_limit & 0x80000000)
+ die("bad %s", arg);
+ continue;
+ }
+ if (!strcmp(arg, "--keep-true-parents")) {
+ grafts_replace_parents = 0;
+ continue;
+ }
+ usage(pack_usage);
+ }
+
+ /* Traditionally "pack-objects [options] base extra" failed;
+ * we would however want to take refs parameter that would
+ * have been given to upstream rev-list ourselves, which means
+ * we somehow want to say what the base name is. So the
+ * syntax would be:
+ *
+ * pack-objects [options] base <refs...>
+ *
+ * in other words, we would treat the first non-option as the
+ * base_name and send everything else to the internal revision
+ * walker.
+ */
+
+ if (!pack_to_stdout)
+ base_name = argv[i++];
+
+ if (pack_to_stdout != !base_name)
+ usage(pack_usage);
+
+ if (!pack_to_stdout && !pack_size_limit)
+ pack_size_limit = pack_size_limit_cfg;
+ if (pack_to_stdout && pack_size_limit)
+ die("--max-pack-size cannot be used to build a pack for transfer.");
+ if (pack_size_limit && pack_size_limit < 1024*1024) {
+ warning("minimum pack size limit is 1 MiB");
+ pack_size_limit = 1024*1024;
+ }
+
+ if (!pack_to_stdout && thin)
+ die("--thin cannot be used to build an indexable pack.");
+
+ if (keep_unreachable && unpack_unreachable)
+ die("--keep-unreachable and --unpack-unreachable are incompatible.");
+
+ if (progress && all_progress_implied)
+ progress = 2;
+
+ prepare_packed_git();
+
+ if (progress)
+ progress_state = start_progress("Counting objects", 0);
+ if (!use_internal_rev_list)
+ read_object_list_from_stdin();
+ else {
+ rp_av[rp_ac] = NULL;
+ get_object_list(rp_ac, rp_av);
+ }
+ cleanup_preferred_base();
+ if (include_tag && nr_result)
+ for_each_ref(add_ref_tag, NULL);
+ stop_progress(&progress_state);
+
+ if (non_empty && !nr_result)
+ return 0;
+ if (nr_result)
+ prepare_pack(window, depth);
+ write_pack_file();
+ if (progress)
+ fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
+ " reused %"PRIu32" (delta %"PRIu32")\n",
+ written, written_delta, reused, reused_delta);
+ return 0;
+ }
diff --cc builtin/prune.c
index 0000000000000000000000000000000000000000,4675f6054fd646622443fc86908c15412d8afd80..81f915ec315eb75e529fed8f2f6bab436755f598
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/prune.c
+++ b/builtin/prune.c
- if (expire) {
- struct stat st;
- if (lstat(fullpath, &st))
- return error("Could not stat '%s'", fullpath);
- if (st.st_mtime > expire)
- return 0;
- }
+ #include "cache.h"
+ #include "commit.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "builtin.h"
+ #include "reachable.h"
+ #include "parse-options.h"
+ #include "dir.h"
+
+ static const char * const prune_usage[] = {
+ "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
+ NULL
+ };
+ static int show_only;
+ static int verbose;
+ static unsigned long expire;
+
+ static int prune_tmp_object(const char *path, const char *filename)
+ {
+ const char *fullpath = mkpath("%s/%s", path, filename);
- if (expire) {
- struct stat st;
- if (lstat(fullpath, &st))
- return error("Could not stat '%s'", fullpath);
- if (st.st_mtime > expire)
- return 0;
- }
++ struct stat st;
++ if (lstat(fullpath, &st))
++ return error("Could not stat '%s'", fullpath);
++ if (st.st_mtime > expire)
++ return 0;
+ printf("Removing stale temporary file %s\n", fullpath);
+ if (!show_only)
+ unlink_or_warn(fullpath);
+ return 0;
+ }
+
+ static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+ {
+ const char *fullpath = mkpath("%s/%s", path, filename);
++ struct stat st;
++ if (lstat(fullpath, &st))
++ return error("Could not stat '%s'", fullpath);
++ if (st.st_mtime > expire)
++ return 0;
+ if (show_only || verbose) {
+ enum object_type type = sha1_object_info(sha1, NULL);
+ printf("%s %s\n", sha1_to_hex(sha1),
+ (type > 0) ? typename(type) : "unknown");
+ }
+ if (!show_only)
+ unlink_or_warn(fullpath);
+ return 0;
+ }
+
+ static int prune_dir(int i, char *path)
+ {
+ DIR *dir = opendir(path);
+ struct dirent *de;
+
+ if (!dir)
+ return 0;
+
+ while ((de = readdir(dir)) != NULL) {
+ char name[100];
+ unsigned char sha1[20];
+
+ if (is_dot_or_dotdot(de->d_name))
+ continue;
+ if (strlen(de->d_name) == 38) {
+ sprintf(name, "%02x", i);
+ memcpy(name+2, de->d_name, 39);
+ if (get_sha1_hex(name, sha1) < 0)
+ break;
+
+ /*
+ * Do we know about this object?
+ * It must have been reachable
+ */
+ if (lookup_object(sha1))
+ continue;
+
+ prune_object(path, de->d_name, sha1);
+ continue;
+ }
+ if (!prefixcmp(de->d_name, "tmp_obj_")) {
+ prune_tmp_object(path, de->d_name);
+ continue;
+ }
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ }
+ if (!show_only)
+ rmdir(path);
+ closedir(dir);
+ return 0;
+ }
+
+ static void prune_object_dir(const char *path)
+ {
+ int i;
+ for (i = 0; i < 256; i++) {
+ static char dir[4096];
+ sprintf(dir, "%s/%02x", path, i);
+ prune_dir(i, dir);
+ }
+ }
+
+ /*
+ * Write errors (particularly out of space) can result in
+ * failed temporary packs (and more rarely indexes and other
+ * files beginning with "tmp_") accumulating in the object
+ * and the pack directories.
+ */
+ static void remove_temporary_files(const char *path)
+ {
+ DIR *dir;
+ struct dirent *de;
+
+ dir = opendir(path);
+ if (!dir) {
+ fprintf(stderr, "Unable to open directory %s\n", path);
+ return;
+ }
+ while ((de = readdir(dir)) != NULL)
+ if (!prefixcmp(de->d_name, "tmp_"))
+ prune_tmp_object(path, de->d_name);
+ closedir(dir);
+ }
+
+ int cmd_prune(int argc, const char **argv, const char *prefix)
+ {
+ struct rev_info revs;
+ const struct option options[] = {
+ OPT_BOOLEAN('n', NULL, &show_only,
+ "do not remove, show only"),
+ OPT_BOOLEAN('v', NULL, &verbose,
+ "report pruned objects"),
+ OPT_DATE(0, "expire", &expire,
+ "expire objects older than <time>"),
+ OPT_END()
+ };
+ char *s;
+
++ expire = ULONG_MAX;
+ save_commit_buffer = 0;
+ read_replace_refs = 0;
+ init_revisions(&revs, prefix);
+
+ argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
+ while (argc--) {
+ unsigned char sha1[20];
+ const char *name = *argv++;
+
+ if (!get_sha1(name, sha1)) {
+ struct object *object = parse_object(sha1);
+ if (!object)
+ die("bad object: %s", name);
+ add_pending_object(&revs, object, "");
+ }
+ else
+ die("unrecognized argument: %s", name);
+ }
+ mark_reachable_objects(&revs, 1);
+ prune_object_dir(get_object_directory());
+
+ prune_packed_objects(show_only);
+ remove_temporary_files(get_object_directory());
+ s = xstrdup(mkpath("%s/pack", get_object_directory()));
+ remove_temporary_files(s);
+ free(s);
+ return 0;
+ }
diff --cc builtin/push.c
index 0000000000000000000000000000000000000000,5633f0ade49f7c845665b7aab202be18a8cc9d8d..f7bc2b292fb85725d9cc26ce09f2302aaa7167fe
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/push.c
+++ b/builtin/push.c
- if (!branch->merge_nr)
+ /*
+ * "git push"
+ */
+ #include "cache.h"
+ #include "refs.h"
+ #include "run-command.h"
+ #include "builtin.h"
+ #include "remote.h"
+ #include "transport.h"
+ #include "parse-options.h"
+
+ static const char * const push_usage[] = {
+ "git push [<options>] [<repository> <refspec>...]",
+ NULL,
+ };
+
+ static int thin;
+ static int deleterefs;
+ static const char *receivepack;
+
+ static const char **refspec;
+ static int refspec_nr;
+
+ static void add_refspec(const char *ref)
+ {
+ int nr = refspec_nr + 1;
+ refspec = xrealloc(refspec, nr * sizeof(char *));
+ refspec[nr-1] = ref;
+ refspec_nr = nr;
+ }
+
+ static void set_refspecs(const char **refs, int nr)
+ {
+ int i;
+ for (i = 0; i < nr; i++) {
+ const char *ref = refs[i];
+ if (!strcmp("tag", ref)) {
+ char *tag;
+ int len;
+ if (nr <= ++i)
+ die("tag shorthand without <tag>");
+ len = strlen(refs[i]) + 11;
+ if (deleterefs) {
+ tag = xmalloc(len+1);
+ strcpy(tag, ":refs/tags/");
+ } else {
+ tag = xmalloc(len);
+ strcpy(tag, "refs/tags/");
+ }
+ strcat(tag, refs[i]);
+ ref = tag;
+ } else if (deleterefs && !strchr(ref, ':')) {
+ char *delref;
+ int len = strlen(ref)+1;
+ delref = xmalloc(len+1);
+ strcpy(delref, ":");
+ strcat(delref, ref);
+ ref = delref;
+ } else if (deleterefs)
+ die("--delete only accepts plain target ref names");
+ add_refspec(ref);
+ }
+ }
+
+ static void setup_push_tracking(void)
+ {
+ struct strbuf refspec = STRBUF_INIT;
+ struct branch *branch = branch_get(NULL);
+ if (!branch)
+ die("You are not currently on a branch.");
++ if (!branch->merge_nr || !branch->merge)
+ die("The current branch %s is not tracking anything.",
+ branch->name);
+ if (branch->merge_nr != 1)
+ die("The current branch %s is tracking multiple branches, "
+ "refusing to push.", branch->name);
+ strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
+ add_refspec(refspec.buf);
+ }
+
+ static void setup_default_push_refspecs(void)
+ {
+ switch (push_default) {
+ default:
+ case PUSH_DEFAULT_MATCHING:
+ add_refspec(":");
+ break;
+
+ case PUSH_DEFAULT_TRACKING:
+ setup_push_tracking();
+ break;
+
+ case PUSH_DEFAULT_CURRENT:
+ add_refspec("HEAD");
+ break;
+
+ case PUSH_DEFAULT_NOTHING:
+ die("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\".");
+ break;
+ }
+ }
+
+ static int push_with_options(struct transport *transport, int flags)
+ {
+ int err;
+ int nonfastforward;
+ if (receivepack)
+ transport_set_option(transport,
+ TRANS_OPT_RECEIVEPACK, receivepack);
+ if (thin)
+ transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
+ if (flags & TRANSPORT_PUSH_VERBOSE)
+ fprintf(stderr, "Pushing to %s\n", transport->url);
+ err = transport_push(transport, refspec_nr, refspec, flags,
+ &nonfastforward);
+ if (err != 0)
+ error("failed to push some refs to '%s'", transport->url);
+
+ err |= transport_disconnect(transport);
+
+ if (!err)
+ return 0;
+
+ if (nonfastforward && advice_push_nonfastforward) {
+ printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
+ "Merge the remote changes before pushing again. See the 'Note about\n"
+ "fast-forwards' section of 'git push --help' for details.\n");
+ }
+
+ return 1;
+ }
+
+ static int do_push(const char *repo, int flags)
+ {
+ int i, errs;
+ struct remote *remote = remote_get(repo);
+ const char **url;
+ int url_nr;
+
+ if (!remote) {
+ if (repo)
+ die("bad repository '%s'", repo);
+ die("No destination configured to push to.");
+ }
+
+ if (remote->mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--all and --tags are incompatible");
+ return error("--all can't be combined with refspecs");
+ }
+
+ if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) {
+ if (!strcmp(*refspec, "refs/tags/*"))
+ return error("--mirror and --tags are incompatible");
+ return error("--mirror can't be combined with refspecs");
+ }
+
+ if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+ (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+ return error("--all and --mirror are incompatible");
+ }
+
+ if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
+ if (remote->push_refspec_nr) {
+ refspec = remote->push_refspec;
+ refspec_nr = remote->push_refspec_nr;
+ } else if (!(flags & TRANSPORT_PUSH_MIRROR))
+ setup_default_push_refspecs();
+ }
+ errs = 0;
+ if (remote->pushurl_nr) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ } else {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+ if (url_nr) {
+ for (i = 0; i < url_nr; i++) {
+ struct transport *transport =
+ transport_get(remote, url[i]);
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ } else {
+ struct transport *transport =
+ transport_get(remote, NULL);
+
+ if (push_with_options(transport, flags))
+ errs++;
+ }
+ return !!errs;
+ }
+
+ int cmd_push(int argc, const char **argv, const char *prefix)
+ {
+ int flags = 0;
+ int tags = 0;
+ int rc;
+ const char *repo = NULL; /* default repository */
+ struct option options[] = {
+ OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET),
+ OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE),
+ OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
+ OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
+ OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
+ (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
+ OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"),
+ OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
+ OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
+ OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
+ OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
+ OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
+ OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
+ OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
+ OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+ TRANSPORT_PUSH_SET_UPSTREAM),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+
+ if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
+ die("--delete is incompatible with --all, --mirror and --tags");
+ if (deleterefs && argc < 2)
+ die("--delete doesn't make sense without any refs");
+
+ if (tags)
+ add_refspec("refs/tags/*");
+
+ if (argc > 0) {
+ repo = argv[0];
+ set_refspecs(argv + 1, argc - 1);
+ }
+
+ rc = do_push(repo, flags);
+ if (rc == -1)
+ usage_with_options(push_usage, options);
+ else
+ return rc;
+ }
diff --cc builtin/reflog.c
index 0000000000000000000000000000000000000000,749821078df129cf13de34ebbd40a8cb7a38e00e..64e45bd8137bef2b6cfe1f8a3da79e2ff6f8fc47
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/reflog.c
+++ b/builtin/reflog.c
- if (!default_reflog_expire_unreachable)
- default_reflog_expire_unreachable = now - 30 * 24 * 3600;
- if (!default_reflog_expire)
- default_reflog_expire = now - 90 * 24 * 3600;
+ #include "cache.h"
+ #include "builtin.h"
+ #include "commit.h"
+ #include "refs.h"
+ #include "dir.h"
+ #include "tree-walk.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "reachable.h"
+
+ /*
+ * reflog expire
+ */
+
+ static const char reflog_expire_usage[] =
+ "git reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+ static const char reflog_delete_usage[] =
+ "git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+
+ static unsigned long default_reflog_expire;
+ static unsigned long default_reflog_expire_unreachable;
+
+ struct cmd_reflog_expire_cb {
+ struct rev_info revs;
+ int dry_run;
+ int stalefix;
+ int rewrite;
+ int updateref;
+ int verbose;
+ unsigned long expire_total;
+ unsigned long expire_unreachable;
+ int recno;
+ };
+
+ struct expire_reflog_cb {
+ FILE *newlog;
+ const char *ref;
+ struct commit *ref_commit;
+ struct cmd_reflog_expire_cb *cmd;
+ unsigned char last_kept_sha1[20];
+ };
+
+ struct collected_reflog {
+ unsigned char sha1[20];
+ char reflog[FLEX_ARRAY];
+ };
+ struct collect_reflog_cb {
+ struct collected_reflog **e;
+ int alloc;
+ int nr;
+ };
+
+ #define INCOMPLETE (1u<<10)
+ #define STUDYING (1u<<11)
+ #define REACHABLE (1u<<12)
+
+ static int tree_is_complete(const unsigned char *sha1)
+ {
+ struct tree_desc desc;
+ struct name_entry entry;
+ int complete;
+ struct tree *tree;
+
+ tree = lookup_tree(sha1);
+ if (!tree)
+ return 0;
+ if (tree->object.flags & SEEN)
+ return 1;
+ if (tree->object.flags & INCOMPLETE)
+ return 0;
+
+ if (!tree->buffer) {
+ enum object_type type;
+ unsigned long size;
+ void *data = read_sha1_file(sha1, &type, &size);
+ if (!data) {
+ tree->object.flags |= INCOMPLETE;
+ return 0;
+ }
+ tree->buffer = data;
+ tree->size = size;
+ }
+ init_tree_desc(&desc, tree->buffer, tree->size);
+ complete = 1;
+ while (tree_entry(&desc, &entry)) {
+ if (!has_sha1_file(entry.sha1) ||
+ (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
+ tree->object.flags |= INCOMPLETE;
+ complete = 0;
+ }
+ }
+ free(tree->buffer);
+ tree->buffer = NULL;
+
+ if (complete)
+ tree->object.flags |= SEEN;
+ return complete;
+ }
+
+ static int commit_is_complete(struct commit *commit)
+ {
+ struct object_array study;
+ struct object_array found;
+ int is_incomplete = 0;
+ int i;
+
+ /* early return */
+ if (commit->object.flags & SEEN)
+ return 1;
+ if (commit->object.flags & INCOMPLETE)
+ return 0;
+ /*
+ * Find all commits that are reachable and are not marked as
+ * SEEN. Then make sure the trees and blobs contained are
+ * complete. After that, mark these commits also as SEEN.
+ * If some of the objects that are needed to complete this
+ * commit are missing, mark this commit as INCOMPLETE.
+ */
+ memset(&study, 0, sizeof(study));
+ memset(&found, 0, sizeof(found));
+ add_object_array(&commit->object, NULL, &study);
+ add_object_array(&commit->object, NULL, &found);
+ commit->object.flags |= STUDYING;
+ while (study.nr) {
+ struct commit *c;
+ struct commit_list *parent;
+
+ c = (struct commit *)study.objects[--study.nr].item;
+ if (!c->object.parsed && !parse_object(c->object.sha1))
+ c->object.flags |= INCOMPLETE;
+
+ if (c->object.flags & INCOMPLETE) {
+ is_incomplete = 1;
+ break;
+ }
+ else if (c->object.flags & SEEN)
+ continue;
+ for (parent = c->parents; parent; parent = parent->next) {
+ struct commit *p = parent->item;
+ if (p->object.flags & STUDYING)
+ continue;
+ p->object.flags |= STUDYING;
+ add_object_array(&p->object, NULL, &study);
+ add_object_array(&p->object, NULL, &found);
+ }
+ }
+ if (!is_incomplete) {
+ /*
+ * make sure all commits in "found" array have all the
+ * necessary objects.
+ */
+ for (i = 0; i < found.nr; i++) {
+ struct commit *c =
+ (struct commit *)found.objects[i].item;
+ if (!tree_is_complete(c->tree->object.sha1)) {
+ is_incomplete = 1;
+ c->object.flags |= INCOMPLETE;
+ }
+ }
+ if (!is_incomplete) {
+ /* mark all found commits as complete, iow SEEN */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags |= SEEN;
+ }
+ }
+ /* clear flags from the objects we traversed */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags &= ~STUDYING;
+ if (is_incomplete)
+ commit->object.flags |= INCOMPLETE;
+ else {
+ /*
+ * If we come here, we have (1) traversed the ancestry chain
+ * from the "commit" until we reach SEEN commits (which are
+ * known to be complete), and (2) made sure that the commits
+ * encountered during the above traversal refer to trees that
+ * are complete. Which means that we know *all* the commits
+ * we have seen during this process are complete.
+ */
+ for (i = 0; i < found.nr; i++)
+ found.objects[i].item->flags |= SEEN;
+ }
+ /* free object arrays */
+ free(study.objects);
+ free(found.objects);
+ return !is_incomplete;
+ }
+
+ static int keep_entry(struct commit **it, unsigned char *sha1)
+ {
+ struct commit *commit;
+
+ if (is_null_sha1(sha1))
+ return 1;
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+
+ /*
+ * Make sure everything in this commit exists.
+ *
+ * We have walked all the objects reachable from the refs
+ * and cache earlier. The commits reachable by this commit
+ * must meet SEEN commits -- and then we should mark them as
+ * SEEN as well.
+ */
+ if (!commit_is_complete(commit))
+ return 0;
+ *it = commit;
+ return 1;
+ }
+
+ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+ {
+ /*
+ * We may or may not have the commit yet - if not, look it
+ * up using the supplied sha1.
+ */
+ if (!commit) {
+ if (is_null_sha1(sha1))
+ return 0;
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+
+ /* Not a commit -- keep it */
+ if (!commit)
+ return 0;
+ }
+
+ /* Reachable from the current ref? Don't prune. */
+ if (commit->object.flags & REACHABLE)
+ return 0;
+ if (in_merge_bases(commit, &cb->ref_commit, 1))
+ return 0;
+
+ /* We can't reach it - prune it. */
+ return 1;
+ }
+
+ static void mark_reachable(struct commit *commit, unsigned long expire_limit)
+ {
+ /*
+ * We need to compute whether the commit on either side of a reflog
+ * entry is reachable from the tip of the ref for all entries.
+ * Mark commits that are reachable from the tip down to the
+ * time threshold first; we know a commit marked thusly is
+ * reachable from the tip without running in_merge_bases()
+ * at all.
+ */
+ struct commit_list *pending = NULL;
+
+ commit_list_insert(commit, &pending);
+ while (pending) {
+ struct commit_list *entry = pending;
+ struct commit_list *parent;
+ pending = entry->next;
+ commit = entry->item;
+ free(entry);
+ if (commit->object.flags & REACHABLE)
+ continue;
+ if (parse_commit(commit))
+ continue;
+ commit->object.flags |= REACHABLE;
+ if (commit->date < expire_limit)
+ continue;
+ parent = commit->parents;
+ while (parent) {
+ commit = parent->item;
+ parent = parent->next;
+ if (commit->object.flags & REACHABLE)
+ continue;
+ commit_list_insert(commit, &pending);
+ }
+ }
+ }
+
+ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+ {
+ struct expire_reflog_cb *cb = cb_data;
+ struct commit *old, *new;
+
+ if (timestamp < cb->cmd->expire_total)
+ goto prune;
+
+ if (cb->cmd->rewrite)
+ osha1 = cb->last_kept_sha1;
+
+ old = new = NULL;
+ if (cb->cmd->stalefix &&
+ (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
+ goto prune;
+
+ if (timestamp < cb->cmd->expire_unreachable) {
+ if (!cb->ref_commit)
+ goto prune;
+ if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
+ goto prune;
+ }
+
+ if (cb->cmd->recno && --(cb->cmd->recno) == 0)
+ goto prune;
+
+ if (cb->newlog) {
+ char sign = (tz < 0) ? '-' : '+';
+ int zone = (tz < 0) ? (-tz) : tz;
+ fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
+ sha1_to_hex(osha1), sha1_to_hex(nsha1),
+ email, timestamp, sign, zone,
+ message);
+ hashcpy(cb->last_kept_sha1, nsha1);
+ }
+ if (cb->cmd->verbose)
+ printf("keep %s", message);
+ return 0;
+ prune:
+ if (!cb->newlog || cb->cmd->verbose)
+ printf("%sprune %s", cb->newlog ? "" : "would ", message);
+ return 0;
+ }
+
+ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+ {
+ struct cmd_reflog_expire_cb *cmd = cb_data;
+ struct expire_reflog_cb cb;
+ struct ref_lock *lock;
+ char *log_file, *newlog_path = NULL;
+ int status = 0;
+
+ memset(&cb, 0, sizeof(cb));
+
+ /*
+ * we take the lock for the ref itself to prevent it from
+ * getting updated.
+ */
+ lock = lock_any_ref_for_update(ref, sha1, 0);
+ if (!lock)
+ return error("cannot lock ref '%s'", ref);
+ log_file = git_pathdup("logs/%s", ref);
+ if (!file_exists(log_file))
+ goto finish;
+ if (!cmd->dry_run) {
+ newlog_path = git_pathdup("logs/%s.lock", ref);
+ cb.newlog = fopen(newlog_path, "w");
+ }
+
+ cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
+ cb.ref = ref;
+ cb.cmd = cmd;
+ if (cb.ref_commit)
+ mark_reachable(cb.ref_commit, cmd->expire_total);
+ for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+ if (cb.ref_commit)
+ clear_commit_marks(cb.ref_commit, REACHABLE);
+ finish:
+ if (cb.newlog) {
+ if (fclose(cb.newlog)) {
+ status |= error("%s: %s", strerror(errno),
+ newlog_path);
+ unlink(newlog_path);
+ } else if (cmd->updateref &&
+ (write_in_full(lock->lock_fd,
+ sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+ write_str_in_full(lock->lock_fd, "\n") != 1 ||
+ close_ref(lock) < 0)) {
+ status |= error("Couldn't write %s",
+ lock->lk->filename);
+ unlink(newlog_path);
+ } else if (rename(newlog_path, log_file)) {
+ status |= error("cannot rename %s to %s",
+ newlog_path, log_file);
+ unlink(newlog_path);
+ } else if (cmd->updateref && commit_ref(lock)) {
+ status |= error("Couldn't set %s", lock->ref_name);
+ } else {
+ adjust_shared_perm(log_file);
+ }
+ }
+ free(newlog_path);
+ free(log_file);
+ unlock_ref(lock);
+ return status;
+ }
+
+ static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+ {
+ struct collected_reflog *e;
+ struct collect_reflog_cb *cb = cb_data;
+ size_t namelen = strlen(ref);
+
+ e = xmalloc(sizeof(*e) + namelen + 1);
+ hashcpy(e->sha1, sha1);
+ memcpy(e->reflog, ref, namelen + 1);
+ ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
+ cb->e[cb->nr++] = e;
+ return 0;
+ }
+
+ static struct reflog_expire_cfg {
+ struct reflog_expire_cfg *next;
+ unsigned long expire_total;
+ unsigned long expire_unreachable;
+ size_t len;
+ char pattern[FLEX_ARRAY];
+ } *reflog_expire_cfg, **reflog_expire_cfg_tail;
+
+ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
+ {
+ struct reflog_expire_cfg *ent;
+
+ if (!reflog_expire_cfg_tail)
+ reflog_expire_cfg_tail = &reflog_expire_cfg;
+
+ for (ent = reflog_expire_cfg; ent; ent = ent->next)
+ if (ent->len == len &&
+ !memcmp(ent->pattern, pattern, len))
+ return ent;
+
+ ent = xcalloc(1, (sizeof(*ent) + len));
+ memcpy(ent->pattern, pattern, len);
+ ent->len = len;
+ *reflog_expire_cfg_tail = ent;
+ reflog_expire_cfg_tail = &(ent->next);
+ return ent;
+ }
+
+ static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
+ {
+ if (!value)
+ return config_error_nonbool(var);
+ if (!strcmp(value, "never") || !strcmp(value, "false")) {
+ *expire = 0;
+ return 0;
+ }
+ *expire = approxidate(value);
+ return 0;
+ }
+
+ /* expiry timer slot */
+ #define EXPIRE_TOTAL 01
+ #define EXPIRE_UNREACH 02
+
+ static int reflog_expire_config(const char *var, const char *value, void *cb)
+ {
+ const char *lastdot = strrchr(var, '.');
+ unsigned long expire;
+ int slot;
+ struct reflog_expire_cfg *ent;
+
+ if (!lastdot || prefixcmp(var, "gc."))
+ return git_default_config(var, value, cb);
+
+ if (!strcmp(lastdot, ".reflogexpire")) {
+ slot = EXPIRE_TOTAL;
+ if (parse_expire_cfg_value(var, value, &expire))
+ return -1;
+ } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
+ slot = EXPIRE_UNREACH;
+ if (parse_expire_cfg_value(var, value, &expire))
+ return -1;
+ } else
+ return git_default_config(var, value, cb);
+
+ if (lastdot == var + 2) {
+ switch (slot) {
+ case EXPIRE_TOTAL:
+ default_reflog_expire = expire;
+ break;
+ case EXPIRE_UNREACH:
+ default_reflog_expire_unreachable = expire;
+ break;
+ }
+ return 0;
+ }
+
+ ent = find_cfg_ent(var + 3, lastdot - (var+3));
+ if (!ent)
+ return -1;
+ switch (slot) {
+ case EXPIRE_TOTAL:
+ ent->expire_total = expire;
+ break;
+ case EXPIRE_UNREACH:
+ ent->expire_unreachable = expire;
+ break;
+ }
+ return 0;
+ }
+
+ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
+ {
+ struct reflog_expire_cfg *ent;
+
+ if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
+ return; /* both given explicitly -- nothing to tweak */
+
+ for (ent = reflog_expire_cfg; ent; ent = ent->next) {
+ if (!fnmatch(ent->pattern, ref, 0)) {
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = ent->expire_total;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = ent->expire_unreachable;
+ return;
+ }
+ }
+
+ /*
+ * If unconfigured, make stash never expire
+ */
+ if (!strcmp(ref, "refs/stash")) {
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = 0;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = 0;
+ return;
+ }
+
+ /* Nothing matched -- use the default value */
+ if (!(slot & EXPIRE_TOTAL))
+ cb->expire_total = default_reflog_expire;
+ if (!(slot & EXPIRE_UNREACH))
+ cb->expire_unreachable = default_reflog_expire_unreachable;
+ }
+
+ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
+ {
+ struct cmd_reflog_expire_cb cb;
+ unsigned long now = time(NULL);
+ int i, status, do_all;
+ int explicit_expiry = 0;
+
++ default_reflog_expire_unreachable = now - 30 * 24 * 3600;
++ default_reflog_expire = now - 90 * 24 * 3600;
+ git_config(reflog_expire_config, NULL);
+
+ save_commit_buffer = 0;
+ do_all = status = 0;
+ memset(&cb, 0, sizeof(cb));
+
+ cb.expire_total = default_reflog_expire;
+ cb.expire_unreachable = default_reflog_expire_unreachable;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+ cb.dry_run = 1;
+ else if (!prefixcmp(arg, "--expire=")) {
+ cb.expire_total = approxidate(arg + 9);
+ explicit_expiry |= EXPIRE_TOTAL;
+ }
+ else if (!prefixcmp(arg, "--expire-unreachable=")) {
+ cb.expire_unreachable = approxidate(arg + 21);
+ explicit_expiry |= EXPIRE_UNREACH;
+ }
+ else if (!strcmp(arg, "--stale-fix"))
+ cb.stalefix = 1;
+ else if (!strcmp(arg, "--rewrite"))
+ cb.rewrite = 1;
+ else if (!strcmp(arg, "--updateref"))
+ cb.updateref = 1;
+ else if (!strcmp(arg, "--all"))
+ do_all = 1;
+ else if (!strcmp(arg, "--verbose"))
+ cb.verbose = 1;
+ else if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ else if (arg[0] == '-')
+ usage(reflog_expire_usage);
+ else
+ break;
+ }
+
+ /*
+ * We can trust the commits and objects reachable from refs
+ * even in older repository. We cannot trust what's reachable
+ * from reflog if the repository was pruned with older git.
+ */
+ if (cb.stalefix) {
+ init_revisions(&cb.revs, prefix);
+ if (cb.verbose)
+ printf("Marking reachable objects...");
+ mark_reachable_objects(&cb.revs, 0);
+ if (cb.verbose)
+ putchar('\n');
+ }
+
+ if (do_all) {
+ struct collect_reflog_cb collected;
+ int i;
+
+ memset(&collected, 0, sizeof(collected));
+ for_each_reflog(collect_reflog, &collected);
+ for (i = 0; i < collected.nr; i++) {
+ struct collected_reflog *e = collected.e[i];
+ set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
+ status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+ free(e);
+ }
+ free(collected.e);
+ }
+
+ for (; i < argc; i++) {
+ char *ref;
+ unsigned char sha1[20];
+ if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
+ status |= error("%s points nowhere!", argv[i]);
+ continue;
+ }
+ set_reflog_expiry_param(&cb, explicit_expiry, ref);
+ status |= expire_reflog(ref, sha1, 0, &cb);
+ }
+ return status;
+ }
+
+ static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+ {
+ struct cmd_reflog_expire_cb *cb = cb_data;
+ if (!cb->expire_total || timestamp < cb->expire_total)
+ cb->recno++;
+ return 0;
+ }
+
+ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
+ {
+ struct cmd_reflog_expire_cb cb;
+ int i, status = 0;
+
+ memset(&cb, 0, sizeof(cb));
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+ cb.dry_run = 1;
+ else if (!strcmp(arg, "--rewrite"))
+ cb.rewrite = 1;
+ else if (!strcmp(arg, "--updateref"))
+ cb.updateref = 1;
+ else if (!strcmp(arg, "--verbose"))
+ cb.verbose = 1;
+ else if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ else if (arg[0] == '-')
+ usage(reflog_delete_usage);
+ else
+ break;
+ }
+
+ if (argc - i < 1)
+ return error("Nothing to delete?");
+
+ for ( ; i < argc; i++) {
+ const char *spec = strstr(argv[i], "@{");
+ unsigned char sha1[20];
+ char *ep, *ref;
+ int recno;
+
+ if (!spec) {
+ status |= error("Not a reflog: %s", argv[i]);
+ continue;
+ }
+
+ if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
+ status |= error("no reflog for '%s'", argv[i]);
+ continue;
+ }
+
+ recno = strtoul(spec + 2, &ep, 10);
+ if (*ep == '}') {
+ cb.recno = -recno;
+ for_each_reflog_ent(ref, count_reflog_ent, &cb);
+ } else {
+ cb.expire_total = approxidate(spec + 2);
+ for_each_reflog_ent(ref, count_reflog_ent, &cb);
+ cb.expire_total = 0;
+ }
+
+ status |= expire_reflog(ref, sha1, 0, &cb);
+ free(ref);
+ }
+ return status;
+ }
+
+ /*
+ * main "reflog"
+ */
+
+ static const char reflog_usage[] =
+ "git reflog [ show | expire | delete ]";
+
+ int cmd_reflog(int argc, const char **argv, const char *prefix)
+ {
+ if (argc > 1 && !strcmp(argv[1], "-h"))
+ usage(reflog_usage);
+
+ /* With no command, we default to showing it. */
+ if (argc < 2 || *argv[1] == '-')
+ return cmd_log_reflog(argc, argv, prefix);
+
+ if (!strcmp(argv[1], "show"))
+ return cmd_log_reflog(argc - 1, argv + 1, prefix);
+
+ if (!strcmp(argv[1], "expire"))
+ return cmd_reflog_expire(argc - 1, argv + 1, prefix);
+
+ if (!strcmp(argv[1], "delete"))
+ return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+
+ /* Not a recognized reflog command..*/
+ usage(reflog_usage);
+ }
diff --cc builtin/rev-list.c
index 0000000000000000000000000000000000000000,c924b3a2c76c1f9a7f5531504825ed1b5456d41a..5679170e82ed644d4c3eb4f71f26aa0ac9acce24
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/rev-list.c
+++ b/builtin/rev-list.c
- save_commit_buffer = revs.verbose_header ||
- revs.grep_filter.pattern_list;
+ #include "cache.h"
+ #include "commit.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "list-objects.h"
+ #include "builtin.h"
+ #include "log-tree.h"
+ #include "graph.h"
+ #include "bisect.h"
+
+ static const char rev_list_usage[] =
+ "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+ " limiting output:\n"
+ " --max-count=nr\n"
+ " --max-age=epoch\n"
+ " --min-age=epoch\n"
+ " --sparse\n"
+ " --no-merges\n"
+ " --remove-empty\n"
+ " --all\n"
+ " --branches\n"
+ " --tags\n"
+ " --remotes\n"
+ " --stdin\n"
+ " --quiet\n"
+ " ordering output:\n"
+ " --topo-order\n"
+ " --date-order\n"
+ " --reverse\n"
+ " formatting output:\n"
+ " --parents\n"
+ " --children\n"
+ " --objects | --objects-edge\n"
+ " --unpacked\n"
+ " --header | --pretty\n"
+ " --abbrev=nr | --no-abbrev\n"
+ " --abbrev-commit\n"
+ " --left-right\n"
+ " special purpose:\n"
+ " --bisect\n"
+ " --bisect-vars\n"
+ " --bisect-all"
+ ;
+
+ static void finish_commit(struct commit *commit, void *data);
+ static void show_commit(struct commit *commit, void *data)
+ {
+ struct rev_list_info *info = data;
+ struct rev_info *revs = info->revs;
+
+ graph_show_commit(revs->graph);
+
+ if (info->show_timestamp)
+ printf("%lu ", commit->date);
+ if (info->header_prefix)
+ fputs(info->header_prefix, stdout);
+
+ if (!revs->graph) {
+ if (commit->object.flags & BOUNDARY)
+ putchar('-');
+ else if (commit->object.flags & UNINTERESTING)
+ putchar('^');
+ else if (revs->left_right) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
+ putchar('<');
+ else
+ putchar('>');
+ }
+ }
+ if (revs->abbrev_commit && revs->abbrev)
+ fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
+ stdout);
+ else
+ fputs(sha1_to_hex(commit->object.sha1), stdout);
+ if (revs->print_parents) {
+ struct commit_list *parents = commit->parents;
+ while (parents) {
+ printf(" %s", sha1_to_hex(parents->item->object.sha1));
+ parents = parents->next;
+ }
+ }
+ if (revs->children.name) {
+ struct commit_list *children;
+
+ children = lookup_decoration(&revs->children, &commit->object);
+ while (children) {
+ printf(" %s", sha1_to_hex(children->item->object.sha1));
+ children = children->next;
+ }
+ }
+ show_decorations(revs, commit);
+ if (revs->commit_format == CMIT_FMT_ONELINE)
+ putchar(' ');
+ else
+ putchar('\n');
+
+ if (revs->verbose_header && commit->buffer) {
+ struct strbuf buf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.abbrev = revs->abbrev;
+ ctx.date_mode = revs->date_mode;
+ pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
+ if (revs->graph) {
+ if (buf.len) {
+ if (revs->commit_format != CMIT_FMT_ONELINE)
+ graph_show_oneline(revs->graph);
+
+ graph_show_commit_msg(revs->graph, &buf);
+
+ /*
+ * Add a newline after the commit message.
+ *
+ * Usually, this newline produces a blank
+ * padding line between entries, in which case
+ * we need to add graph padding on this line.
+ *
+ * However, the commit message may not end in a
+ * newline. In this case the newline simply
+ * ends the last line of the commit message,
+ * and we don't need any graph output. (This
+ * always happens with CMIT_FMT_ONELINE, and it
+ * happens with CMIT_FMT_USERFORMAT when the
+ * format doesn't explicitly end in a newline.)
+ */
+ if (buf.len && buf.buf[buf.len - 1] == '\n')
+ graph_show_padding(revs->graph);
+ putchar('\n');
+ } else {
+ /*
+ * If the message buffer is empty, just show
+ * the rest of the graph output for this
+ * commit.
+ */
+ if (graph_show_remainder(revs->graph))
+ putchar('\n');
+ }
+ } else {
+ if (buf.len)
+ printf("%s%c", buf.buf, info->hdr_termination);
+ }
+ strbuf_release(&buf);
+ } else {
+ if (graph_show_remainder(revs->graph))
+ putchar('\n');
+ }
+ maybe_flush_or_die(stdout, "stdout");
+ finish_commit(commit, data);
+ }
+
+ static void finish_commit(struct commit *commit, void *data)
+ {
+ if (commit->parents) {
+ free_commit_list(commit->parents);
+ commit->parents = NULL;
+ }
+ free(commit->buffer);
+ commit->buffer = NULL;
+ }
+
+ static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+ {
+ if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
+ die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+ }
+
+ static void show_object(struct object *obj, const struct name_path *path, const char *component)
+ {
+ char *name = path_name(path, component);
+ /* An object with name "foo\n0000000..." can be used to
+ * confuse downstream "git pack-objects" very badly.
+ */
+ const char *ep = strchr(name, '\n');
+
+ finish_object(obj, path, name);
+ if (ep) {
+ printf("%s %.*s\n", sha1_to_hex(obj->sha1),
+ (int) (ep - name),
+ name);
+ }
+ else
+ printf("%s %s\n", sha1_to_hex(obj->sha1), name);
+ free(name);
+ }
+
+ static void show_edge(struct commit *commit)
+ {
+ printf("-%s\n", sha1_to_hex(commit->object.sha1));
+ }
+
+ static inline int log2i(int n)
+ {
+ int log2 = 0;
+
+ for (; n > 1; n >>= 1)
+ log2++;
+
+ return log2;
+ }
+
+ static inline int exp2i(int n)
+ {
+ return 1 << n;
+ }
+
+ /*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+ int estimate_bisect_steps(int all)
+ {
+ int n, x, e;
+
+ if (all < 3)
+ return 0;
+
+ n = log2i(all);
+ e = exp2i(n);
+ x = all - e;
+
+ return (e < 3 * x) ? n : n - 1;
+ }
+
+ void print_commit_list(struct commit_list *list,
+ const char *format_cur,
+ const char *format_last)
+ {
+ for ( ; list; list = list->next) {
+ const char *format = list->next ? format_cur : format_last;
+ printf(format, sha1_to_hex(list->item->object.sha1));
+ }
+ }
+
+ static void show_tried_revs(struct commit_list *tried)
+ {
+ printf("bisect_tried='");
+ print_commit_list(tried, "%s|", "%s");
+ printf("'\n");
+ }
+
+ static void print_var_str(const char *var, const char *val)
+ {
+ printf("%s='%s'\n", var, val);
+ }
+
+ static void print_var_int(const char *var, int val)
+ {
+ printf("%s=%d\n", var, val);
+ }
+
+ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+ {
+ int cnt, flags = info->bisect_show_flags;
+ char hex[41] = "";
+ struct commit_list *tried;
+ struct rev_info *revs = info->revs;
+
+ if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+ return 1;
+
+ revs->commits = filter_skipped(revs->commits, &tried,
+ flags & BISECT_SHOW_ALL,
+ NULL, NULL);
+
+ /*
+ * revs->commits can reach "reaches" commits among
+ * "all" commits. If it is good, then there are
+ * (all-reaches) commits left to be bisected.
+ * On the other hand, if it is bad, then the set
+ * to bisect is "reaches".
+ * A bisect set of size N has (N-1) commits further
+ * to test, as we already know one bad one.
+ */
+ cnt = all - reaches;
+ if (cnt < reaches)
+ cnt = reaches;
+
+ if (revs->commits)
+ strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+
+ if (flags & BISECT_SHOW_ALL) {
+ traverse_commit_list(revs, show_commit, show_object, info);
+ printf("------\n");
+ }
+
+ if (flags & BISECT_SHOW_TRIED)
+ show_tried_revs(tried);
+
+ print_var_str("bisect_rev", hex);
+ print_var_int("bisect_nr", cnt - 1);
+ print_var_int("bisect_good", all - reaches - 1);
+ print_var_int("bisect_bad", reaches - 1);
+ print_var_int("bisect_all", all);
+ print_var_int("bisect_steps", estimate_bisect_steps(all));
+
+ return 0;
+ }
+
+ int cmd_rev_list(int argc, const char **argv, const char *prefix)
+ {
+ struct rev_info revs;
+ struct rev_list_info info;
+ int i;
+ int bisect_list = 0;
+ int bisect_show_vars = 0;
+ int bisect_find_all = 0;
+ int quiet = 0;
+
+ git_config(git_default_config, NULL);
+ init_revisions(&revs, prefix);
+ revs.abbrev = 0;
+ revs.commit_format = CMIT_FMT_UNSPECIFIED;
+ argc = setup_revisions(argc, argv, &revs, NULL);
+
+ memset(&info, 0, sizeof(info));
+ info.revs = &revs;
+ if (revs.bisect)
+ bisect_list = 1;
+
+ quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+ for (i = 1 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--header")) {
+ revs.verbose_header = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--timestamp")) {
+ info.show_timestamp = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect")) {
+ bisect_list = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect-all")) {
+ bisect_list = 1;
+ bisect_find_all = 1;
+ info.bisect_show_flags = BISECT_SHOW_ALL;
+ revs.show_decorations = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect-vars")) {
+ bisect_list = 1;
+ bisect_show_vars = 1;
+ continue;
+ }
+ usage(rev_list_usage);
+
+ }
+ if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+ /* The command line has a --pretty */
+ info.hdr_termination = '\n';
+ if (revs.commit_format == CMIT_FMT_ONELINE)
+ info.header_prefix = "";
+ else
+ info.header_prefix = "commit ";
+ }
+ else if (revs.verbose_header)
+ /* Only --header was specified */
+ revs.commit_format = CMIT_FMT_RAW;
+
+ if ((!revs.commits &&
+ (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+ !revs.pending.nr)) ||
+ revs.diff)
+ usage(rev_list_usage);
+
++ save_commit_buffer = (revs.verbose_header ||
++ revs.grep_filter.pattern_list ||
++ revs.grep_filter.header_list);
+ if (bisect_list)
+ revs.limited = 1;
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ if (revs.tree_objects)
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+
+ if (bisect_list) {
+ int reaches = reaches, all = all;
+
+ revs.commits = find_bisection(revs.commits, &reaches, &all,
+ bisect_find_all);
+
+ if (bisect_show_vars)
+ return show_bisect_vars(&info, reaches, all);
+ }
+
+ traverse_commit_list(&revs,
+ quiet ? finish_commit : show_commit,
+ quiet ? finish_object : show_object,
+ &info);
+
+ return 0;
+ }
diff --cc builtin/rev-parse.c
index 0000000000000000000000000000000000000000,a8c5043dedd785b8fc43c0921edd01e2adac65e1..8fbf9d0db6f40aa8c7cb61d72d0f44446de46826
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
- printf("%s/.git\n", cwd);
+ /*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+ #include "cache.h"
+ #include "commit.h"
+ #include "refs.h"
+ #include "quote.h"
+ #include "builtin.h"
+ #include "parse-options.h"
+
+ #define DO_REVS 1
+ #define DO_NOREV 2
+ #define DO_FLAGS 4
+ #define DO_NONFLAGS 8
+ static int filter = ~0;
+
+ static const char *def;
+
+ #define NORMAL 0
+ #define REVERSED 1
+ static int show_type = NORMAL;
+
+ #define SHOW_SYMBOLIC_ASIS 1
+ #define SHOW_SYMBOLIC_FULL 2
+ static int symbolic;
+ static int abbrev;
+ static int abbrev_ref;
+ static int abbrev_ref_strict;
+ static int output_sq;
+
+ /*
+ * Some arguments are relevant "revision" arguments,
+ * others are about output format or other details.
+ * This sorts it all out.
+ */
+ static int is_rev_argument(const char *arg)
+ {
+ static const char *rev_args[] = {
+ "--all",
+ "--bisect",
+ "--dense",
+ "--branches=",
+ "--branches",
+ "--header",
+ "--max-age=",
+ "--max-count=",
+ "--min-age=",
+ "--no-merges",
+ "--objects",
+ "--objects-edge",
+ "--parents",
+ "--pretty",
+ "--remotes=",
+ "--remotes",
+ "--glob=",
+ "--sparse",
+ "--tags=",
+ "--tags",
+ "--topo-order",
+ "--date-order",
+ "--unpacked",
+ NULL
+ };
+ const char **p = rev_args;
+
+ /* accept -<digit>, like traditional "head" */
+ if ((*arg == '-') && isdigit(arg[1]))
+ return 1;
+
+ for (;;) {
+ const char *str = *p++;
+ int len;
+ if (!str)
+ return 0;
+ len = strlen(str);
+ if (!strcmp(arg, str) ||
+ (str[len-1] == '=' && !strncmp(arg, str, len)))
+ return 1;
+ }
+ }
+
+ /* Output argument as a string, either SQ or normal */
+ static void show(const char *arg)
+ {
+ if (output_sq) {
+ int sq = '\'', ch;
+
+ putchar(sq);
+ while ((ch = *arg++)) {
+ if (ch == sq)
+ fputs("'\\'", stdout);
+ putchar(ch);
+ }
+ putchar(sq);
+ putchar(' ');
+ }
+ else
+ puts(arg);
+ }
+
+ /* Like show(), but with a negation prefix according to type */
+ static void show_with_type(int type, const char *arg)
+ {
+ if (type != show_type)
+ putchar('^');
+ show(arg);
+ }
+
+ /* Output a revision, only if filter allows it */
+ static void show_rev(int type, const unsigned char *sha1, const char *name)
+ {
+ if (!(filter & DO_REVS))
+ return;
+ def = NULL;
+
+ if ((symbolic || abbrev_ref) && name) {
+ if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
+ unsigned char discard[20];
+ char *full;
+
+ switch (dwim_ref(name, strlen(name), discard, &full)) {
+ case 0:
+ /*
+ * Not found -- not a ref. We could
+ * emit "name" here, but symbolic-full
+ * users are interested in finding the
+ * refs spelled in full, and they would
+ * need to filter non-refs if we did so.
+ */
+ break;
+ case 1: /* happy */
+ if (abbrev_ref)
+ full = shorten_unambiguous_ref(full,
+ abbrev_ref_strict);
+ show_with_type(type, full);
+ break;
+ default: /* ambiguous */
+ error("refname '%s' is ambiguous", name);
+ break;
+ }
+ } else {
+ show_with_type(type, name);
+ }
+ }
+ else if (abbrev)
+ show_with_type(type, find_unique_abbrev(sha1, abbrev));
+ else
+ show_with_type(type, sha1_to_hex(sha1));
+ }
+
+ /* Output a flag, only if filter allows it. */
+ static int show_flag(const char *arg)
+ {
+ if (!(filter & DO_FLAGS))
+ return 0;
+ if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV)) {
+ show(arg);
+ return 1;
+ }
+ return 0;
+ }
+
+ static int show_default(void)
+ {
+ const char *s = def;
+
+ if (s) {
+ unsigned char sha1[20];
+
+ def = NULL;
+ if (!get_sha1(s, sha1)) {
+ show_rev(NORMAL, sha1, s);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ show_rev(NORMAL, sha1, refname);
+ return 0;
+ }
+
+ static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ show_rev(REVERSED, sha1, refname);
+ return 0;
+ }
+
+ static void show_datestring(const char *flag, const char *datestr)
+ {
+ static char buffer[100];
+
+ /* date handling requires both flags and revs */
+ if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
+ return;
+ snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
+ show(buffer);
+ }
+
+ static int show_file(const char *arg)
+ {
+ show_default();
+ if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
+ show(arg);
+ return 1;
+ }
+ return 0;
+ }
+
+ static int try_difference(const char *arg)
+ {
+ char *dotdot;
+ unsigned char sha1[20];
+ unsigned char end[20];
+ const char *next;
+ const char *this;
+ int symmetric;
+
+ if (!(dotdot = strstr(arg, "..")))
+ return 0;
+ next = dotdot + 2;
+ this = arg;
+ symmetric = (*next == '.');
+
+ *dotdot = 0;
+ next += symmetric;
+
+ if (!*next)
+ next = "HEAD";
+ if (dotdot == arg)
+ this = "HEAD";
+ if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+ show_rev(NORMAL, end, next);
+ show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
+ if (symmetric) {
+ struct commit_list *exclude;
+ struct commit *a, *b;
+ a = lookup_commit_reference(sha1);
+ b = lookup_commit_reference(end);
+ exclude = get_merge_bases(a, b, 1);
+ while (exclude) {
+ struct commit_list *n = exclude->next;
+ show_rev(REVERSED,
+ exclude->item->object.sha1,NULL);
+ free(exclude);
+ exclude = n;
+ }
+ }
+ return 1;
+ }
+ *dotdot = '.';
+ return 0;
+ }
+
+ static int try_parent_shorthands(const char *arg)
+ {
+ char *dotdot;
+ unsigned char sha1[20];
+ struct commit *commit;
+ struct commit_list *parents;
+ int parents_only;
+
+ if ((dotdot = strstr(arg, "^!")))
+ parents_only = 0;
+ else if ((dotdot = strstr(arg, "^@")))
+ parents_only = 1;
+
+ if (!dotdot || dotdot[2])
+ return 0;
+
+ *dotdot = 0;
+ if (get_sha1(arg, sha1))
+ return 0;
+
+ if (!parents_only)
+ show_rev(NORMAL, sha1, arg);
+ commit = lookup_commit_reference(sha1);
+ for (parents = commit->parents; parents; parents = parents->next)
+ show_rev(parents_only ? NORMAL : REVERSED,
+ parents->item->object.sha1, arg);
+
+ return 1;
+ }
+
+ static int parseopt_dump(const struct option *o, const char *arg, int unset)
+ {
+ struct strbuf *parsed = o->value;
+ if (unset)
+ strbuf_addf(parsed, " --no-%s", o->long_name);
+ else if (o->short_name)
+ strbuf_addf(parsed, " -%c", o->short_name);
+ else
+ strbuf_addf(parsed, " --%s", o->long_name);
+ if (arg) {
+ strbuf_addch(parsed, ' ');
+ sq_quote_buf(parsed, arg);
+ }
+ return 0;
+ }
+
+ static const char *skipspaces(const char *s)
+ {
+ while (isspace(*s))
+ s++;
+ return s;
+ }
+
+ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
+ {
+ static int keep_dashdash = 0, stop_at_non_option = 0;
+ static char const * const parseopt_usage[] = {
+ "git rev-parse --parseopt [options] -- [<args>...]",
+ NULL
+ };
+ static struct option parseopt_opts[] = {
+ OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
+ "keep the `--` passed as an arg"),
+ OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
+ "stop parsing after the "
+ "first non-option argument"),
+ OPT_END(),
+ };
+
+ struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
+ const char **usage = NULL;
+ struct option *opts = NULL;
+ int onb = 0, osz = 0, unb = 0, usz = 0;
+
+ strbuf_addstr(&parsed, "set --");
+ argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ if (argc < 1 || strcmp(argv[0], "--"))
+ usage_with_options(parseopt_usage, parseopt_opts);
+
+ /* get the usage up to the first line with a -- on it */
+ for (;;) {
+ if (strbuf_getline(&sb, stdin, '\n') == EOF)
+ die("premature end of input");
+ ALLOC_GROW(usage, unb + 1, usz);
+ if (!strcmp("--", sb.buf)) {
+ if (unb < 1)
+ die("no usage string given before the `--' separator");
+ usage[unb] = NULL;
+ break;
+ }
+ usage[unb++] = strbuf_detach(&sb, NULL);
+ }
+
+ /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
+ while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+ const char *s;
+ struct option *o;
+
+ if (!sb.len)
+ continue;
+
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+
+ o = &opts[onb++];
+ s = strchr(sb.buf, ' ');
+ if (!s || *sb.buf == ' ') {
+ o->type = OPTION_GROUP;
+ o->help = xstrdup(skipspaces(sb.buf));
+ continue;
+ }
+
+ o->type = OPTION_CALLBACK;
+ o->help = xstrdup(skipspaces(s));
+ o->value = &parsed;
+ o->flags = PARSE_OPT_NOARG;
+ o->callback = &parseopt_dump;
+ while (s > sb.buf && strchr("*=?!", s[-1])) {
+ switch (*--s) {
+ case '=':
+ o->flags &= ~PARSE_OPT_NOARG;
+ break;
+ case '?':
+ o->flags &= ~PARSE_OPT_NOARG;
+ o->flags |= PARSE_OPT_OPTARG;
+ break;
+ case '!':
+ o->flags |= PARSE_OPT_NONEG;
+ break;
+ case '*':
+ o->flags |= PARSE_OPT_HIDDEN;
+ break;
+ }
+ }
+
+ if (s - sb.buf == 1) /* short option only */
+ o->short_name = *sb.buf;
+ else if (sb.buf[1] != ',') /* long option only */
+ o->long_name = xmemdupz(sb.buf, s - sb.buf);
+ else {
+ o->short_name = *sb.buf;
+ o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+ }
+ }
+ strbuf_release(&sb);
+
+ /* put an OPT_END() */
+ ALLOC_GROW(opts, onb + 1, osz);
+ memset(opts + onb, 0, sizeof(opts[onb]));
+ argc = parse_options(argc, argv, prefix, opts, usage,
+ keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0 |
+ stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0);
+
+ strbuf_addf(&parsed, " --");
+ sq_quote_argv(&parsed, argv, 0);
+ puts(parsed.buf);
+ return 0;
+ }
+
+ static int cmd_sq_quote(int argc, const char **argv)
+ {
+ struct strbuf buf = STRBUF_INIT;
+
+ if (argc)
+ sq_quote_argv(&buf, argv, 0);
+ printf("%s\n", buf.buf);
+ strbuf_release(&buf);
+
+ return 0;
+ }
+
+ static void die_no_single_rev(int quiet)
+ {
+ if (quiet)
+ exit(1);
+ else
+ die("Needed a single revision");
+ }
+
+ static const char builtin_rev_parse_usage[] =
+ "git rev-parse --parseopt [options] -- [<args>...]\n"
+ " or: git rev-parse --sq-quote [<arg>...]\n"
+ " or: git rev-parse [options] [<arg>...]\n"
+ "\n"
+ "Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
+
+ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
+ {
+ int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+ unsigned char sha1[20];
+ const char *name = NULL;
+
+ if (argc > 1 && !strcmp("--parseopt", argv[1]))
+ return cmd_parseopt(argc - 1, argv + 1, prefix);
+
+ if (argc > 1 && !strcmp("--sq-quote", argv[1]))
+ return cmd_sq_quote(argc - 2, argv + 2);
+
++ if (argc == 2 && !strcmp("--local-env-vars", argv[1])) {
++ int i;
++ for (i = 0; local_repo_env[i]; i++)
++ printf("%s\n", local_repo_env[i]);
++ return 0;
++ }
++
+ if (argc > 1 && !strcmp("-h", argv[1]))
+ usage(builtin_rev_parse_usage);
+
+ prefix = setup_git_directory();
+ git_config(git_default_config, NULL);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (as_is) {
+ if (show_file(arg) && as_is < 2)
+ verify_filename(prefix, arg);
+ continue;
+ }
+ if (!strcmp(arg,"-n")) {
+ if (++i >= argc)
+ die("-n requires an argument");
+ if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
+ show(arg);
+ show(argv[i]);
+ }
+ continue;
+ }
+ if (!prefixcmp(arg, "-n")) {
+ if ((filter & DO_FLAGS) && (filter & DO_REVS))
+ show(arg);
+ continue;
+ }
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "--")) {
+ as_is = 2;
+ /* Pass on the "--" if we show anything but files.. */
+ if (filter & (DO_FLAGS | DO_REVS))
+ show_file(arg);
+ continue;
+ }
+ if (!strcmp(arg, "--default")) {
+ def = argv[i+1];
+ i++;
+ continue;
+ }
+ if (!strcmp(arg, "--revs-only")) {
+ filter &= ~DO_NOREV;
+ continue;
+ }
+ if (!strcmp(arg, "--no-revs")) {
+ filter &= ~DO_REVS;
+ continue;
+ }
+ if (!strcmp(arg, "--flags")) {
+ filter &= ~DO_NONFLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--no-flags")) {
+ filter &= ~DO_FLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--verify")) {
+ filter &= ~(DO_FLAGS|DO_NOREV);
+ verify = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--short") ||
+ !prefixcmp(arg, "--short=")) {
+ filter &= ~(DO_FLAGS|DO_NOREV);
+ verify = 1;
+ abbrev = DEFAULT_ABBREV;
+ if (arg[7] == '=')
+ abbrev = strtoul(arg + 8, NULL, 10);
+ if (abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ else if (40 <= abbrev)
+ abbrev = 40;
+ continue;
+ }
+ if (!strcmp(arg, "--sq")) {
+ output_sq = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--not")) {
+ show_type ^= REVERSED;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic")) {
+ symbolic = SHOW_SYMBOLIC_ASIS;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic-full-name")) {
+ symbolic = SHOW_SYMBOLIC_FULL;
+ continue;
+ }
+ if (!prefixcmp(arg, "--abbrev-ref") &&
+ (!arg[12] || arg[12] == '=')) {
+ abbrev_ref = 1;
+ abbrev_ref_strict = warn_ambiguous_refs;
+ if (arg[12] == '=') {
+ if (!strcmp(arg + 13, "strict"))
+ abbrev_ref_strict = 1;
+ else if (!strcmp(arg + 13, "loose"))
+ abbrev_ref_strict = 0;
+ else
+ die("unknown mode for %s", arg);
+ }
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ for_each_ref(show_reference, NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--bisect")) {
+ for_each_ref_in("refs/bisect/bad", show_reference, NULL);
+ for_each_ref_in("refs/bisect/good", anti_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--branches=")) {
+ for_each_glob_ref_in(show_reference, arg + 11,
+ "refs/heads/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--branches")) {
+ for_each_branch_ref(show_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--tags=")) {
+ for_each_glob_ref_in(show_reference, arg + 7,
+ "refs/tags/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ for_each_tag_ref(show_reference, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--glob=")) {
+ for_each_glob_ref(show_reference, arg + 7, NULL);
+ continue;
+ }
+ if (!prefixcmp(arg, "--remotes=")) {
+ for_each_glob_ref_in(show_reference, arg + 10,
+ "refs/remotes/", NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--remotes")) {
+ for_each_remote_ref(show_reference, NULL);
+ continue;
+ }
+ if (!strcmp(arg, "--show-toplevel")) {
+ const char *work_tree = get_git_work_tree();
+ if (work_tree)
+ puts(work_tree);
+ continue;
+ }
+ if (!strcmp(arg, "--show-prefix")) {
+ if (prefix)
+ puts(prefix);
+ continue;
+ }
+ if (!strcmp(arg, "--show-cdup")) {
+ const char *pfx = prefix;
+ if (!is_inside_work_tree()) {
+ const char *work_tree =
+ get_git_work_tree();
+ if (work_tree)
+ printf("%s\n", work_tree);
+ continue;
+ }
+ while (pfx) {
+ pfx = strchr(pfx, '/');
+ if (pfx) {
+ pfx++;
+ printf("../");
+ }
+ }
+ putchar('\n');
+ continue;
+ }
+ if (!strcmp(arg, "--git-dir")) {
+ const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
+ static char cwd[PATH_MAX];
++ int len;
+ if (gitdir) {
+ puts(gitdir);
+ continue;
+ }
+ if (!prefix) {
+ puts(".git");
+ continue;
+ }
+ if (!getcwd(cwd, PATH_MAX))
+ die_errno("unable to get current working directory");
++ len = strlen(cwd);
++ printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+ continue;
+ }
+ if (!strcmp(arg, "--is-inside-git-dir")) {
+ printf("%s\n", is_inside_git_dir() ? "true"
+ : "false");
+ continue;
+ }
+ if (!strcmp(arg, "--is-inside-work-tree")) {
+ printf("%s\n", is_inside_work_tree() ? "true"
+ : "false");
+ continue;
+ }
+ if (!strcmp(arg, "--is-bare-repository")) {
+ printf("%s\n", is_bare_repository() ? "true"
+ : "false");
+ continue;
+ }
+ if (!prefixcmp(arg, "--since=")) {
+ show_datestring("--max-age=", arg+8);
+ continue;
+ }
+ if (!prefixcmp(arg, "--after=")) {
+ show_datestring("--max-age=", arg+8);
+ continue;
+ }
+ if (!prefixcmp(arg, "--before=")) {
+ show_datestring("--min-age=", arg+9);
+ continue;
+ }
+ if (!prefixcmp(arg, "--until=")) {
+ show_datestring("--min-age=", arg+8);
+ continue;
+ }
+ if (show_flag(arg) && verify)
+ die_no_single_rev(quiet);
+ continue;
+ }
+
+ /* Not a flag argument */
+ if (try_difference(arg))
+ continue;
+ if (try_parent_shorthands(arg))
+ continue;
+ name = arg;
+ type = NORMAL;
+ if (*arg == '^') {
+ name++;
+ type = REVERSED;
+ }
+ if (!get_sha1(name, sha1)) {
+ if (verify)
+ revs_count++;
+ else
+ show_rev(type, sha1, name);
+ continue;
+ }
+ if (verify)
+ die_no_single_rev(quiet);
+ as_is = 1;
+ if (!show_file(arg))
+ continue;
+ verify_filename(prefix, arg);
+ }
+ if (verify) {
+ if (revs_count == 1) {
+ show_rev(type, sha1, name);
+ return 0;
+ } else if (revs_count == 0 && show_default())
+ return 0;
+ die_no_single_rev(quiet);
+ } else
+ show_default();
+ return 0;
+ }
diff --cc builtin/send-pack.c
index 0000000000000000000000000000000000000000,2183a470524048eabef1b0f31499c5d04aec5850..6019eac9182e22f2d485acdb83be209dbc49968a
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/send-pack.c
+++ b/builtin/send-pack.c
-static void update_tracking_ref(struct remote *remote, struct ref *ref)
-{
- struct refspec rs;
-
- if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
- return;
-
- rs.src = ref->name;
- rs.dst = NULL;
-
- if (!remote_find_tracking(remote, &rs)) {
- if (args.verbose)
- fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
- if (ref->deletion) {
- delete_ref(rs.dst, NULL, 0);
- } else
- update_ref("update by push", rs.dst,
- ref->new_sha1, NULL, 0, 0);
- free(rs.dst);
- }
-}
-
-#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
-
-static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
-{
- fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
- if (from)
- fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
- else
- fputs(prettify_refname(to->name), stderr);
- if (msg) {
- fputs(" (", stderr);
- fputs(msg, stderr);
- fputc(')', stderr);
- }
- fputc('\n', stderr);
-}
-
-static const char *status_abbrev(unsigned char sha1[20])
-{
- return find_unique_abbrev(sha1, DEFAULT_ABBREV);
-}
-
-static void print_ok_ref_status(struct ref *ref)
-{
- if (ref->deletion)
- print_ref_status('-', "[deleted]", ref, NULL, NULL);
- else if (is_null_sha1(ref->old_sha1))
- print_ref_status('*',
- (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
- "[new branch]"),
- ref, ref->peer_ref, NULL);
- else {
- char quickref[84];
- char type;
- const char *msg;
-
- strcpy(quickref, status_abbrev(ref->old_sha1));
- if (ref->nonfastforward) {
- strcat(quickref, "...");
- type = '+';
- msg = "forced update";
- } else {
- strcat(quickref, "..");
- type = ' ';
- msg = NULL;
- }
- strcat(quickref, status_abbrev(ref->new_sha1));
-
- print_ref_status(type, quickref, ref, ref->peer_ref, msg);
- }
-}
-
-static int print_one_push_status(struct ref *ref, const char *dest, int count)
-{
- if (!count)
- fprintf(stderr, "To %s\n", dest);
-
- switch(ref->status) {
- case REF_STATUS_NONE:
- print_ref_status('X', "[no match]", ref, NULL, NULL);
- break;
- case REF_STATUS_REJECT_NODELETE:
- print_ref_status('!', "[rejected]", ref, NULL,
- "remote does not support deleting refs");
- break;
- case REF_STATUS_UPTODATE:
- print_ref_status('=', "[up to date]", ref,
- ref->peer_ref, NULL);
- break;
- case REF_STATUS_REJECT_NONFASTFORWARD:
- print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "non-fast-forward");
- break;
- case REF_STATUS_REMOTE_REJECT:
- print_ref_status('!', "[remote rejected]", ref,
- ref->deletion ? NULL : ref->peer_ref,
- ref->remote_status);
- break;
- case REF_STATUS_EXPECTING_REPORT:
- print_ref_status('!', "[remote failure]", ref,
- ref->deletion ? NULL : ref->peer_ref,
- "remote failed to report status");
- break;
- case REF_STATUS_OK:
- print_ok_ref_status(ref);
- break;
- }
-
- return 1;
-}
-
-static void print_push_status(const char *dest, struct ref *refs)
-{
- struct ref *ref;
- int n = 0;
-
- if (args.verbose) {
- for (ref = refs; ref; ref = ref->next)
- if (ref->status == REF_STATUS_UPTODATE)
- n += print_one_push_status(ref, dest, n);
- }
-
- for (ref = refs; ref; ref = ref->next)
- if (ref->status == REF_STATUS_OK)
- n += print_one_push_status(ref, dest, n);
-
- for (ref = refs; ref; ref = ref->next) {
- if (ref->status != REF_STATUS_NONE &&
- ref->status != REF_STATUS_UPTODATE &&
- ref->status != REF_STATUS_OK)
- n += print_one_push_status(ref, dest, n);
- }
-}
-
-static int refs_pushed(struct ref *ref)
-{
- for (; ref; ref = ref->next) {
- switch(ref->status) {
- case REF_STATUS_NONE:
- case REF_STATUS_UPTODATE:
- break;
- default:
- return 1;
- }
- }
- return 0;
-}
-
+ #include "cache.h"
+ #include "commit.h"
+ #include "refs.h"
+ #include "pkt-line.h"
+ #include "sideband.h"
+ #include "run-command.h"
+ #include "remote.h"
+ #include "send-pack.h"
+ #include "quote.h"
++#include "transport.h"
+
+ static const char send_pack_usage[] =
+ "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+ " --all and explicit <ref> specification are mutually exclusive.";
+
+ static struct send_pack_args args;
+
+ static int feed_object(const unsigned char *sha1, int fd, int negative)
+ {
+ char buf[42];
+
+ if (negative && !has_sha1_file(sha1))
+ return 1;
+
+ memcpy(buf + negative, sha1_to_hex(sha1), 40);
+ if (negative)
+ buf[0] = '^';
+ buf[40 + negative] = '\n';
+ return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
+ }
+
+ /*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
+ {
+ /*
+ * The child becomes pack-objects --revs; we feed
+ * the revision parameters to it via its stdin and
+ * let its stdout go back to the other end.
+ */
+ const char *argv[] = {
+ "pack-objects",
+ "--all-progress-implied",
+ "--revs",
+ "--stdout",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ };
+ struct child_process po;
+ int i;
+
+ i = 4;
+ if (args->use_thin_pack)
+ argv[i++] = "--thin";
+ if (args->use_ofs_delta)
+ argv[i++] = "--delta-base-offset";
+ if (args->quiet)
+ argv[i++] = "-q";
+ memset(&po, 0, sizeof(po));
+ po.argv = argv;
+ po.in = -1;
+ po.out = args->stateless_rpc ? -1 : fd;
+ po.git_cmd = 1;
+ if (start_command(&po))
+ die_errno("git pack-objects failed");
+
+ /*
+ * We feed the pack-objects we just spawned with revision
+ * parameters by writing to the pipe.
+ */
+ for (i = 0; i < extra->nr; i++)
+ if (!feed_object(extra->array[i], po.in, 1))
+ break;
+
+ while (refs) {
+ if (!is_null_sha1(refs->old_sha1) &&
+ !feed_object(refs->old_sha1, po.in, 1))
+ break;
+ if (!is_null_sha1(refs->new_sha1) &&
+ !feed_object(refs->new_sha1, po.in, 0))
+ break;
+ refs = refs->next;
+ }
+
+ close(po.in);
+
+ if (args->stateless_rpc) {
+ char *buf = xmalloc(LARGE_PACKET_MAX);
+ while (1) {
+ ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+ if (n <= 0)
+ break;
+ send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+ }
+ free(buf);
+ close(po.out);
+ po.out = -1;
+ }
+
+ if (finish_command(&po))
+ return error("pack-objects died with strange error");
+ return 0;
+ }
+
+ static int receive_status(int in, struct ref *refs)
+ {
+ struct ref *hint;
+ char line[1000];
+ int ret = 0;
+ int len = packet_read_line(in, line, sizeof(line));
+ if (len < 10 || memcmp(line, "unpack ", 7))
+ return error("did not receive remote status");
+ if (memcmp(line, "unpack ok\n", 10)) {
+ char *p = line + strlen(line) - 1;
+ if (*p == '\n')
+ *p = '\0';
+ error("unpack failed: %s", line + 7);
+ ret = -1;
+ }
+ hint = NULL;
+ while (1) {
+ char *refname;
+ char *msg;
+ len = packet_read_line(in, line, sizeof(line));
+ if (!len)
+ break;
+ if (len < 3 ||
+ (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+ fprintf(stderr, "protocol error: %s\n", line);
+ ret = -1;
+ break;
+ }
+
+ line[strlen(line)-1] = '\0';
+ refname = line + 3;
+ msg = strchr(refname, ' ');
+ if (msg)
+ *msg++ = '\0';
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_ref_by_name(hint, refname);
+ if (!hint)
+ hint = find_ref_by_name(refs, refname);
+ if (!hint) {
+ warning("remote reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ warning("remote reported status on unexpected ref: %s",
+ refname);
+ continue;
+ }
+
+ if (line[0] == 'o' && line[1] == 'k')
+ hint->status = REF_STATUS_OK;
+ else {
+ hint->status = REF_STATUS_REMOTE_REJECT;
+ ret = -1;
+ }
+ if (msg)
+ hint->remote_status = xstrdup(msg);
+ /* start our next search from the next ref */
+ hint = hint->next;
+ }
+ return ret;
+ }
+
-static void verify_remote_names(int nr_heads, const char **heads)
-{
- int i;
-
- for (i = 0; i < nr_heads; i++) {
- const char *local = heads[i];
- const char *remote = strrchr(heads[i], ':');
-
- if (*local == '+')
- local++;
-
- /* A matching refspec is okay. */
- if (remote == local && remote[1] == '\0')
- continue;
-
- remote = remote ? (remote + 1) : local;
- switch (check_ref_format(remote)) {
- case 0: /* ok */
- case CHECK_REF_FORMAT_ONELEVEL:
- /* ok but a single level -- that is fine for
- * a match pattern.
- */
- case CHECK_REF_FORMAT_WILDCARD:
- /* ok but ends with a pattern-match character */
- continue;
- }
- die("remote part of refspec is not a valid name in %s",
- heads[i]);
- }
-}
-
+ static void print_helper_status(struct ref *ref)
+ {
+ struct strbuf buf = STRBUF_INIT;
+
+ for (; ref; ref = ref->next) {
+ const char *msg = NULL;
+ const char *res;
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ res = "error";
+ msg = "no match";
+ break;
+
+ case REF_STATUS_OK:
+ res = "ok";
+ break;
+
+ case REF_STATUS_UPTODATE:
+ res = "ok";
+ msg = "up to date";
+ break;
+
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ res = "error";
+ msg = "non-fast forward";
+ break;
+
+ case REF_STATUS_REJECT_NODELETE:
+ case REF_STATUS_REMOTE_REJECT:
+ res = "error";
+ break;
+
+ case REF_STATUS_EXPECTING_REPORT:
+ default:
+ continue;
+ }
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s %s", res, ref->name);
+ if (ref->remote_status)
+ msg = ref->remote_status;
+ if (msg) {
+ strbuf_addch(&buf, ' ');
+ quote_two_c_style(&buf, "", msg, 0);
+ }
+ strbuf_addch(&buf, '\n');
+
+ safe_write(1, buf.buf, buf.len);
+ }
+ strbuf_release(&buf);
+ }
+
+ static int sideband_demux(int in, int out, void *data)
+ {
+ int *fd = data;
+ int ret = recv_sideband("send-pack", fd[0], out);
+ close(out);
+ return ret;
+ }
+
+ int send_pack(struct send_pack_args *args,
+ int fd[], struct child_process *conn,
+ struct ref *remote_refs,
+ struct extra_have_objects *extra_have)
+ {
+ int in = fd[0];
+ int out = fd[1];
+ struct strbuf req_buf = STRBUF_INIT;
+ struct ref *ref;
+ int new_refs;
+ int allow_deleting_refs = 0;
+ int status_report = 0;
+ int use_sideband = 0;
+ unsigned cmds_sent = 0;
+ int ret;
+ struct async demux;
+
+ /* Does the other end support the reporting? */
+ if (server_supports("report-status"))
+ status_report = 1;
+ if (server_supports("delete-refs"))
+ allow_deleting_refs = 1;
+ if (server_supports("ofs-delta"))
+ args->use_ofs_delta = 1;
+ if (server_supports("side-band-64k"))
+ use_sideband = 1;
+
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+ "Perhaps you should specify a branch such as 'master'.\n");
+ return 0;
+ }
+
+ /*
+ * Finally, tell the other end!
+ */
+ new_refs = 0;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!ref->peer_ref && !args->send_mirror)
+ continue;
+
+ /* Check for statuses set by set_ref_status_for_push() */
+ switch (ref->status) {
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_UPTODATE:
+ continue;
+ default:
+ ; /* do nothing */
+ }
+
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
+ continue;
+ }
+
+ if (!ref->deletion)
+ new_refs++;
+
+ if (args->dry_run) {
+ ref->status = REF_STATUS_OK;
+ } else {
+ char *old_hex = sha1_to_hex(ref->old_sha1);
+ char *new_hex = sha1_to_hex(ref->new_sha1);
+
+ if (!cmds_sent && (status_report || use_sideband)) {
+ packet_buf_write(&req_buf, "%s %s %s%c%s%s",
+ old_hex, new_hex, ref->name, 0,
+ status_report ? " report-status" : "",
+ use_sideband ? " side-band-64k" : "");
+ }
+ else
+ packet_buf_write(&req_buf, "%s %s %s",
+ old_hex, new_hex, ref->name);
+ ref->status = status_report ?
+ REF_STATUS_EXPECTING_REPORT :
+ REF_STATUS_OK;
+ cmds_sent++;
+ }
+ }
+
+ if (args->stateless_rpc) {
+ if (!args->dry_run && cmds_sent) {
+ packet_buf_flush(&req_buf);
+ send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
+ }
+ } else {
+ safe_write(out, req_buf.buf, req_buf.len);
+ packet_flush(out);
+ }
+ strbuf_release(&req_buf);
+
+ if (use_sideband && cmds_sent) {
+ memset(&demux, 0, sizeof(demux));
+ demux.proc = sideband_demux;
+ demux.data = fd;
+ demux.out = -1;
+ if (start_async(&demux))
+ die("receive-pack: unable to fork off sideband demultiplexer");
+ in = demux.out;
+ }
+
+ if (new_refs && cmds_sent) {
+ if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+ for (ref = remote_refs; ref; ref = ref->next)
+ ref->status = REF_STATUS_NONE;
+ if (use_sideband)
+ finish_async(&demux);
+ return -1;
+ }
+ }
+ if (args->stateless_rpc && cmds_sent)
+ packet_flush(out);
+
+ if (status_report && cmds_sent)
+ ret = receive_status(in, remote_refs);
+ else
+ ret = 0;
+ if (args->stateless_rpc)
+ packet_flush(out);
+
+ if (use_sideband && cmds_sent) {
+ if (finish_async(&demux)) {
+ error("error in sideband demultiplexer");
+ ret = -1;
+ }
+ close(demux.out);
+ }
+
+ if (ret < 0)
+ return ret;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ case REF_STATUS_OK:
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+ }
+
- verify_remote_names(nr_refspecs, refspecs);
+ int cmd_send_pack(int argc, const char **argv, const char *prefix)
+ {
+ int i, nr_refspecs = 0;
+ const char **refspecs = NULL;
+ const char *remote_name = NULL;
+ struct remote *remote = NULL;
+ const char *dest = NULL;
+ int fd[2];
+ struct child_process *conn;
+ struct extra_have_objects extra_have;
+ struct ref *remote_refs, *local_refs;
+ int ret;
+ int helper_status = 0;
+ int send_all = 0;
+ const char *receivepack = "git-receive-pack";
+ int flags;
++ int nonfastforward = 0;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ const char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!prefixcmp(arg, "--receive-pack=")) {
+ receivepack = arg + 15;
+ continue;
+ }
+ if (!prefixcmp(arg, "--exec=")) {
+ receivepack = arg + 7;
+ continue;
+ }
+ if (!prefixcmp(arg, "--remote=")) {
+ remote_name = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ send_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dry-run")) {
+ args.dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--mirror")) {
+ args.send_mirror = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ args.force_update = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ args.verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--thin")) {
+ args.use_thin_pack = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ args.stateless_rpc = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--helper-status")) {
+ helper_status = 1;
+ continue;
+ }
+ usage(send_pack_usage);
+ }
+ if (!dest) {
+ dest = arg;
+ continue;
+ }
+ refspecs = (const char **) argv;
+ nr_refspecs = argc - i;
+ break;
+ }
+ if (!dest)
+ usage(send_pack_usage);
+ /*
+ * --all and --mirror are incompatible; neither makes sense
+ * with any refspecs.
+ */
+ if ((refspecs && (send_all || args.send_mirror)) ||
+ (send_all && args.send_mirror))
+ usage(send_pack_usage);
+
+ if (remote_name) {
+ remote = remote_get(remote_name);
+ if (!remote_has_url(remote, dest)) {
+ die("Destination %s is not a uri for %s",
+ dest, remote_name);
+ }
+ }
+
+ if (args.stateless_rpc) {
+ conn = NULL;
+ fd[0] = 0;
+ fd[1] = 1;
+ } else {
+ conn = git_connect(fd, dest, receivepack,
+ args.verbose ? CONNECT_VERBOSE : 0);
+ }
+
+ memset(&extra_have, 0, sizeof(extra_have));
+
+ get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
+ &extra_have);
+
- print_push_status(dest, remote_refs);
++ transport_verify_remote_names(nr_refspecs, refspecs);
+
+ local_refs = get_local_heads();
+
+ flags = MATCH_REFS_NONE;
+
+ if (send_all)
+ flags |= MATCH_REFS_ALL;
+ if (args.send_mirror)
+ flags |= MATCH_REFS_MIRROR;
+
+ /* match them up */
+ if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+ return -1;
+
+ set_ref_status_for_push(remote_refs, args.send_mirror,
+ args.force_update);
+
+ ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+
+ if (helper_status)
+ print_helper_status(remote_refs);
+
+ close(fd[1]);
+ close(fd[0]);
+
+ ret |= finish_connect(conn);
+
+ if (!helper_status)
- update_tracking_ref(remote, ref);
++ transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward);
+
+ if (!args.dry_run && remote) {
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next)
- if (!ret && !refs_pushed(remote_refs))
++ transport_update_tracking_ref(remote, ref, args.verbose);
+ }
+
++ if (!ret && !transport_refs_pushed(remote_refs))
+ fprintf(stderr, "Everything up-to-date\n");
+
+ return ret;
+ }
diff --cc builtin/shortlog.c
index 0000000000000000000000000000000000000000,b3b055f68ce59b6b91ef6949bd8c4bd0bed68b55..06320f5285988365b8340e110427866e68536b47
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/shortlog.c
+++ b/builtin/shortlog.c
- int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
- if (col != log->wrap)
- putchar('\n');
+ #include "builtin.h"
+ #include "cache.h"
+ #include "commit.h"
+ #include "diff.h"
+ #include "string-list.h"
+ #include "revision.h"
+ #include "utf8.h"
+ #include "mailmap.h"
+ #include "shortlog.h"
+ #include "parse-options.h"
+
+ static char const * const shortlog_usage[] = {
+ "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
+ "",
+ "[rev-opts] are documented in git-rev-list(1)",
+ NULL
+ };
+
+ static int compare_by_number(const void *a1, const void *a2)
+ {
+ const struct string_list_item *i1 = a1, *i2 = a2;
+ const struct string_list *l1 = i1->util, *l2 = i2->util;
+
+ if (l1->nr < l2->nr)
+ return 1;
+ else if (l1->nr == l2->nr)
+ return 0;
+ else
+ return -1;
+ }
+
+ const char *format_subject(struct strbuf *sb, const char *msg,
+ const char *line_separator);
+
+ static void insert_one_record(struct shortlog *log,
+ const char *author,
+ const char *oneline)
+ {
+ const char *dot3 = log->common_repo_prefix;
+ char *buffer, *p;
+ struct string_list_item *item;
+ char namebuf[1024];
+ char emailbuf[1024];
+ size_t len;
+ const char *eol;
+ const char *boemail, *eoemail;
+ struct strbuf subject = STRBUF_INIT;
+
+ boemail = strchr(author, '<');
+ if (!boemail)
+ return;
+ eoemail = strchr(boemail, '>');
+ if (!eoemail)
+ return;
+
+ /* copy author name to namebuf, to support matching on both name and email */
+ memcpy(namebuf, author, boemail - author);
+ len = boemail - author;
+ while (len > 0 && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = 0;
+
+ /* copy email name to emailbuf, to allow email replacement as well */
+ memcpy(emailbuf, boemail+1, eoemail - boemail);
+ emailbuf[eoemail - boemail - 1] = 0;
+
+ if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
+ while (author < boemail && isspace(*author))
+ author++;
+ for (len = 0;
+ len < sizeof(namebuf) - 1 && author + len < boemail;
+ len++)
+ namebuf[len] = author[len];
+ while (0 < len && isspace(namebuf[len-1]))
+ len--;
+ namebuf[len] = '\0';
+ }
+ else
+ len = strlen(namebuf);
+
+ if (log->email) {
+ size_t room = sizeof(namebuf) - len - 1;
+ int maillen = strlen(emailbuf);
+ snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
+ }
+
+ item = string_list_insert(namebuf, &log->list);
+ if (item->util == NULL)
+ item->util = xcalloc(1, sizeof(struct string_list));
+
+ /* Skip any leading whitespace, including any blank lines. */
+ while (*oneline && isspace(*oneline))
+ oneline++;
+ eol = strchr(oneline, '\n');
+ if (!eol)
+ eol = oneline + strlen(oneline);
+ if (!prefixcmp(oneline, "[PATCH")) {
+ char *eob = strchr(oneline, ']');
+ if (eob && (!eol || eob < eol))
+ oneline = eob + 1;
+ }
+ while (*oneline && isspace(*oneline) && *oneline != '\n')
+ oneline++;
+ format_subject(&subject, oneline, " ");
+ buffer = strbuf_detach(&subject, NULL);
+
+ if (dot3) {
+ int dot3len = strlen(dot3);
+ if (dot3len > 5) {
+ while ((p = strstr(buffer, dot3)) != NULL) {
+ int taillen = strlen(p) - dot3len;
+ memcpy(p, "/.../", 5);
+ memmove(p + 5, p + dot3len, taillen + 1);
+ }
+ }
+ }
+
+ string_list_append(buffer, item->util);
+ }
+
+ static void read_from_stdin(struct shortlog *log)
+ {
+ char author[1024], oneline[1024];
+
+ while (fgets(author, sizeof(author), stdin) != NULL) {
+ if (!(author[0] == 'A' || author[0] == 'a') ||
+ prefixcmp(author + 1, "uthor: "))
+ continue;
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] != '\n')
+ ; /* discard headers */
+ while (fgets(oneline, sizeof(oneline), stdin) &&
+ oneline[0] == '\n')
+ ; /* discard blanks */
+ insert_one_record(log, author + 8, oneline);
+ }
+ }
+
+ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
+ {
+ const char *author = NULL, *buffer;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf ufbuf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+
+ pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+ buffer = buf.buf;
+ while (*buffer && *buffer != '\n') {
+ const char *eol = strchr(buffer, '\n');
+
+ if (eol == NULL)
+ eol = buffer + strlen(buffer);
+ else
+ eol++;
+
+ if (!prefixcmp(buffer, "author "))
+ author = buffer + 7;
+ buffer = eol;
+ }
+ if (!author)
+ die("Missing author: %s",
+ sha1_to_hex(commit->object.sha1));
+ if (log->user_format) {
+ struct pretty_print_context ctx = {0};
+ ctx.abbrev = DEFAULT_ABBREV;
+ ctx.subject = "";
+ ctx.after_subject = "";
+ ctx.date_mode = DATE_NORMAL;
+ pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+ buffer = ufbuf.buf;
+ } else if (*buffer) {
+ buffer++;
+ }
+ insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+ strbuf_release(&ufbuf);
+ strbuf_release(&buf);
+ }
+
+ static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+ {
+ struct commit *commit;
+
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+ while ((commit = get_revision(rev)) != NULL)
+ shortlog_add_commit(log, commit);
+ }
+
+ static int parse_uint(char const **arg, int comma, int defval)
+ {
+ unsigned long ul;
+ int ret;
+ char *endp;
+
+ ul = strtoul(*arg, &endp, 10);
+ if (*endp && *endp != comma)
+ return -1;
+ if (ul > INT_MAX)
+ return -1;
+ ret = *arg == endp ? defval : (int)ul;
+ *arg = *endp ? endp + 1 : endp;
+ return ret;
+ }
+
+ static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
+ #define DEFAULT_WRAPLEN 76
+ #define DEFAULT_INDENT1 6
+ #define DEFAULT_INDENT2 9
+
+ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
+ {
+ struct shortlog *log = opt->value;
+
+ log->wrap_lines = !unset;
+ if (unset)
+ return 0;
+ if (!arg) {
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+ return 0;
+ }
+
+ log->wrap = parse_uint(&arg, ',', DEFAULT_WRAPLEN);
+ log->in1 = parse_uint(&arg, ',', DEFAULT_INDENT1);
+ log->in2 = parse_uint(&arg, '\0', DEFAULT_INDENT2);
+ if (log->wrap < 0 || log->in1 < 0 || log->in2 < 0)
+ return error(wrap_arg_usage);
+ if (log->wrap &&
+ ((log->in1 && log->wrap <= log->in1) ||
+ (log->in2 && log->wrap <= log->in2)))
+ return error(wrap_arg_usage);
+ return 0;
+ }
+
+ void shortlog_init(struct shortlog *log)
+ {
+ memset(log, 0, sizeof(*log));
+
+ read_mailmap(&log->mailmap, &log->common_repo_prefix);
+
+ log->list.strdup_strings = 1;
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+ }
+
+ int cmd_shortlog(int argc, const char **argv, const char *prefix)
+ {
+ static struct shortlog log;
+ static struct rev_info rev;
+ int nongit;
+
+ static const struct option options[] = {
+ OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
+ "sort output according to the number of commits per author"),
+ OPT_BOOLEAN('s', "summary", &log.summary,
+ "Suppress commit descriptions, only provides commit count"),
+ OPT_BOOLEAN('e', "email", &log.email,
+ "Show the email address of each author"),
+ { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
+ "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
+ OPT_END(),
+ };
+
+ struct parse_opt_ctx_t ctx;
+
+ prefix = setup_git_directory_gently(&nongit);
+ git_config(git_default_config, NULL);
+ shortlog_init(&log);
+ init_revisions(&rev, prefix);
+ parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
+ PARSE_OPT_KEEP_ARGV0);
+
+ for (;;) {
+ switch (parse_options_step(&ctx, options, shortlog_usage)) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_DONE:
+ goto parse_done;
+ }
+ parse_revision_opt(&rev, &ctx, options, shortlog_usage);
+ }
+ parse_done:
+ argc = parse_options_end(&ctx);
+
+ if (setup_revisions(argc, argv, &rev, NULL) != 1) {
+ error("unrecognized argument: %s", argv[1]);
+ usage_with_options(shortlog_usage, options);
+ }
+
+ log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
+
+ /* assume HEAD if from a tty */
+ if (!nongit && !rev.pending.nr && isatty(0))
+ add_head_to_pending(&rev);
+ if (rev.pending.nr == 0) {
++ if (isatty(0))
++ fprintf(stderr, "(reading log message from standard input)\n");
+ read_from_stdin(&log);
+ }
+ else
+ get_from_rev(&rev, &log);
+
+ shortlog_output(&log);
+ return 0;
+ }
+
++static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
++ const struct shortlog *log)
++{
++ int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
++ if (col != log->wrap)
++ strbuf_addch(sb, '\n');
++}
++
+ void shortlog_output(struct shortlog *log)
+ {
+ int i, j;
++ struct strbuf sb = STRBUF_INIT;
++
+ if (log->sort_by_number)
+ qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
+ compare_by_number);
+ for (i = 0; i < log->list.nr; i++) {
+ struct string_list *onelines = log->list.items[i].util;
+
+ if (log->summary) {
+ printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
+ } else {
+ printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
+ for (j = onelines->nr - 1; j >= 0; j--) {
+ const char *msg = onelines->items[j].string;
+
+ if (log->wrap_lines) {
++ strbuf_reset(&sb);
++ add_wrapped_shortlog_msg(&sb, msg, log);
++ fwrite(sb.buf, sb.len, 1, stdout);
+ }
+ else
+ printf(" %s\n", msg);
+ }
+ putchar('\n');
+ }
+
+ onelines->strdup_strings = 1;
+ string_list_clear(onelines, 0);
+ free(onelines);
+ log->list.items[i].util = NULL;
+ }
+
++ strbuf_release(&sb);
+ log->list.strdup_strings = 1;
+ string_list_clear(&log->list, 1);
+ clear_mailmap(&log->mailmap);
+ }
diff --cc builtin/show-branch.c
index 0000000000000000000000000000000000000000,35a709e63066ad8bebc8a96dee0563bad43348b6..e20fcf3e935dfafb4e30f24990aa974c8b2f5927
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
+++ b/builtin/show-branch.c
- "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
+ #include "cache.h"
+ #include "commit.h"
+ #include "refs.h"
+ #include "builtin.h"
+ #include "color.h"
+ #include "parse-options.h"
+
+ static const char* show_branch_usage[] = {
- OPT_BOOLEAN(0, "color", &showbranch_use_color,
++ "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...",
+ "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
+ NULL
+ };
+
+ static int showbranch_use_color = -1;
+ static char column_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RED,
+ GIT_COLOR_GREEN,
+ GIT_COLOR_YELLOW,
+ GIT_COLOR_BLUE,
+ GIT_COLOR_MAGENTA,
+ GIT_COLOR_CYAN,
+ };
+
+ #define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+
+ static int default_num;
+ static int default_alloc;
+ static const char **default_arg;
+
+ #define UNINTERESTING 01
+
+ #define REV_SHIFT 2
+ #define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
+
+ #define DEFAULT_REFLOG 4
+
+ static const char *get_color_code(int idx)
+ {
+ if (showbranch_use_color)
+ return column_colors[idx];
+ return "";
+ }
+
+ static const char *get_color_reset_code(void)
+ {
+ if (showbranch_use_color)
+ return GIT_COLOR_RESET;
+ return "";
+ }
+
+ static struct commit *interesting(struct commit_list *list)
+ {
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return commit;
+ }
+ return NULL;
+ }
+
+ static struct commit *pop_one_commit(struct commit_list **list_p)
+ {
+ struct commit *commit;
+ struct commit_list *list;
+ list = *list_p;
+ commit = list->item;
+ *list_p = list->next;
+ free(list);
+ return commit;
+ }
+
+ struct commit_name {
+ const char *head_name; /* which head's ancestor? */
+ int generation; /* how many parents away from head_name */
+ };
+
+ /* Name the commit as nth generation ancestor of head_name;
+ * we count only the first-parent relationship for naming purposes.
+ */
+ static void name_commit(struct commit *commit, const char *head_name, int nth)
+ {
+ struct commit_name *name;
+ if (!commit->util)
+ commit->util = xmalloc(sizeof(struct commit_name));
+ name = commit->util;
+ name->head_name = head_name;
+ name->generation = nth;
+ }
+
+ /* Parent is the first parent of the commit. We may name it
+ * as (n+1)th generation ancestor of the same head_name as
+ * commit is nth generation ancestor of, if that generation
+ * number is better than the name it already has.
+ */
+ static void name_parent(struct commit *commit, struct commit *parent)
+ {
+ struct commit_name *commit_name = commit->util;
+ struct commit_name *parent_name = parent->util;
+ if (!commit_name)
+ return;
+ if (!parent_name ||
+ commit_name->generation + 1 < parent_name->generation)
+ name_commit(parent, commit_name->head_name,
+ commit_name->generation + 1);
+ }
+
+ static int name_first_parent_chain(struct commit *c)
+ {
+ int i = 0;
+ while (c) {
+ struct commit *p;
+ if (!c->util)
+ break;
+ if (!c->parents)
+ break;
+ p = c->parents->item;
+ if (!p->util) {
+ name_parent(c, p);
+ i++;
+ }
+ else
+ break;
+ c = p;
+ }
+ return i;
+ }
+
+ static void name_commits(struct commit_list *list,
+ struct commit **rev,
+ char **ref_name,
+ int num_rev)
+ {
+ struct commit_list *cl;
+ struct commit *c;
+ int i;
+
+ /* First give names to the given heads */
+ for (cl = list; cl; cl = cl->next) {
+ c = cl->item;
+ if (c->util)
+ continue;
+ for (i = 0; i < num_rev; i++) {
+ if (rev[i] == c) {
+ name_commit(c, ref_name[i], 0);
+ break;
+ }
+ }
+ }
+
+ /* Then commits on the first parent ancestry chain */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ i += name_first_parent_chain(cl->item);
+ }
+ } while (i);
+
+ /* Finally, any unnamed commits */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ struct commit_list *parents;
+ struct commit_name *n;
+ int nth;
+ c = cl->item;
+ if (!c->util)
+ continue;
+ n = c->util;
+ parents = c->parents;
+ nth = 0;
+ while (parents) {
+ struct commit *p = parents->item;
+ char newname[1000], *en;
+ parents = parents->next;
+ nth++;
+ if (p->util)
+ continue;
+ en = newname;
+ switch (n->generation) {
+ case 0:
+ en += sprintf(en, "%s", n->head_name);
+ break;
+ case 1:
+ en += sprintf(en, "%s^", n->head_name);
+ break;
+ default:
+ en += sprintf(en, "%s~%d",
+ n->head_name, n->generation);
+ break;
+ }
+ if (nth == 1)
+ en += sprintf(en, "^");
+ else
+ en += sprintf(en, "^%d", nth);
+ name_commit(p, xstrdup(newname), 0);
+ i++;
+ name_first_parent_chain(p);
+ }
+ }
+ } while (i);
+ }
+
+ static int mark_seen(struct commit *commit, struct commit_list **seen_p)
+ {
+ if (!commit->object.flags) {
+ commit_list_insert(commit, seen_p);
+ return 1;
+ }
+ return 0;
+ }
+
+ static void join_revs(struct commit_list **list_p,
+ struct commit_list **seen_p,
+ int num_rev, int extra)
+ {
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (*list_p) {
+ struct commit_list *parents;
+ int still_interesting = !!interesting(*list_p);
+ struct commit *commit = pop_one_commit(list_p);
+ int flags = commit->object.flags & all_mask;
+
+ if (!still_interesting && extra <= 0)
+ break;
+
+ mark_seen(commit, seen_p);
+ if ((flags & all_revs) == all_revs)
+ flags |= UNINTERESTING;
+ parents = commit->parents;
+
+ while (parents) {
+ struct commit *p = parents->item;
+ int this_flag = p->object.flags;
+ parents = parents->next;
+ if ((this_flag & flags) == flags)
+ continue;
+ if (!p->object.parsed)
+ parse_commit(p);
+ if (mark_seen(p, seen_p) && !still_interesting)
+ extra--;
+ p->object.flags |= flags;
+ insert_by_date(p, list_p);
+ }
+ }
+
+ /*
+ * Postprocess to complete well-poisoning.
+ *
+ * At this point we have all the commits we have seen in
+ * seen_p list. Mark anything that can be reached from
+ * uninteresting commits not interesting.
+ */
+ for (;;) {
+ int changed = 0;
+ struct commit_list *s;
+ for (s = *seen_p; s; s = s->next) {
+ struct commit *c = s->item;
+ struct commit_list *parents;
+
+ if (((c->object.flags & all_revs) != all_revs) &&
+ !(c->object.flags & UNINTERESTING))
+ continue;
+
+ /* The current commit is either a merge base or
+ * already uninteresting one. Mark its parents
+ * as uninteresting commits _only_ if they are
+ * already parsed. No reason to find new ones
+ * here.
+ */
+ parents = c->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if (!(p->object.flags & UNINTERESTING)) {
+ p->object.flags |= UNINTERESTING;
+ changed = 1;
+ }
+ }
+ }
+ if (!changed)
+ break;
+ }
+ }
+
+ static void show_one_commit(struct commit *commit, int no_name)
+ {
+ struct strbuf pretty = STRBUF_INIT;
+ const char *pretty_str = "(unavailable)";
+ struct commit_name *name = commit->util;
+
+ if (commit->object.parsed) {
+ struct pretty_print_context ctx = {0};
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx);
+ pretty_str = pretty.buf;
+ }
+ if (!prefixcmp(pretty_str, "[PATCH] "))
+ pretty_str += 8;
+
+ if (!no_name) {
+ if (name && name->head_name) {
+ printf("[%s", name->head_name);
+ if (name->generation) {
+ if (name->generation == 1)
+ printf("^");
+ else
+ printf("~%d", name->generation);
+ }
+ printf("] ");
+ }
+ else
+ printf("[%s] ",
+ find_unique_abbrev(commit->object.sha1, 7));
+ }
+ puts(pretty_str);
+ strbuf_release(&pretty);
+ }
+
+ static char *ref_name[MAX_REVS + 1];
+ static int ref_name_cnt;
+
+ static const char *find_digit_prefix(const char *s, int *v)
+ {
+ const char *p;
+ int ver;
+ char ch;
+
+ for (p = s, ver = 0;
+ '0' <= (ch = *p) && ch <= '9';
+ p++)
+ ver = ver * 10 + ch - '0';
+ *v = ver;
+ return p;
+ }
+
+
+ static int version_cmp(const char *a, const char *b)
+ {
+ while (1) {
+ int va, vb;
+
+ a = find_digit_prefix(a, &va);
+ b = find_digit_prefix(b, &vb);
+ if (va != vb)
+ return va - vb;
+
+ while (1) {
+ int ca = *a;
+ int cb = *b;
+ if ('0' <= ca && ca <= '9')
+ ca = 0;
+ if ('0' <= cb && cb <= '9')
+ cb = 0;
+ if (ca != cb)
+ return ca - cb;
+ if (!ca)
+ break;
+ a++;
+ b++;
+ }
+ if (!*a && !*b)
+ return 0;
+ }
+ }
+
+ static int compare_ref_name(const void *a_, const void *b_)
+ {
+ const char * const*a = a_, * const*b = b_;
+ return version_cmp(*a, *b);
+ }
+
+ static void sort_ref_range(int bottom, int top)
+ {
+ qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
+ compare_ref_name);
+ }
+
+ static int append_ref(const char *refname, const unsigned char *sha1,
+ int allow_dups)
+ {
+ struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ int i;
+
+ if (!commit)
+ return 0;
+
+ if (!allow_dups) {
+ /* Avoid adding the same thing twice */
+ for (i = 0; i < ref_name_cnt; i++)
+ if (!strcmp(refname, ref_name[i]))
+ return 0;
+ }
+ if (MAX_REVS <= ref_name_cnt) {
+ warning("ignoring %s; cannot handle more than %d refs",
+ refname, MAX_REVS);
+ return 0;
+ }
+ ref_name[ref_name_cnt++] = xstrdup(refname);
+ ref_name[ref_name_cnt] = NULL;
+ return 0;
+ }
+
+ static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ unsigned char tmp[20];
+ int ofs = 11;
+ if (prefixcmp(refname, "refs/heads/"))
+ return 0;
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1, 0);
+ }
+
+ static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ unsigned char tmp[20];
+ int ofs = 13;
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1, 0);
+ }
+
+ static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ if (prefixcmp(refname, "refs/tags/"))
+ return 0;
+ return append_ref(refname + 5, sha1, 0);
+ }
+
+ static const char *match_ref_pattern = NULL;
+ static int match_ref_slash = 0;
+ static int count_slash(const char *s)
+ {
+ int cnt = 0;
+ while (*s)
+ if (*s++ == '/')
+ cnt++;
+ return cnt;
+ }
+
+ static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+ {
+ /* we want to allow pattern hold/<asterisk> to show all
+ * branches under refs/heads/hold/, and v0.99.9? to show
+ * refs/tags/v0.99.9a and friends.
+ */
+ const char *tail;
+ int slash = count_slash(refname);
+ for (tail = refname; *tail && match_ref_slash < slash; )
+ if (*tail++ == '/')
+ slash--;
+ if (!*tail)
+ return 0;
+ if (fnmatch(match_ref_pattern, tail, 0))
+ return 0;
+ if (!prefixcmp(refname, "refs/heads/"))
+ return append_head_ref(refname, sha1, flag, cb_data);
+ if (!prefixcmp(refname, "refs/tags/"))
+ return append_tag_ref(refname, sha1, flag, cb_data);
+ return append_ref(refname, sha1, 0);
+ }
+
+ static void snarf_refs(int head, int remotes)
+ {
+ if (head) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_head_ref, NULL);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+ if (remotes) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_remote_ref, NULL);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+ }
+
+ static int rev_is_head(char *head, int headlen, char *name,
+ unsigned char *head_sha1, unsigned char *sha1)
+ {
+ if ((!head[0]) ||
+ (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+ return 0;
+ if (!prefixcmp(head, "refs/heads/"))
+ head += 11;
+ if (!prefixcmp(name, "refs/heads/"))
+ name += 11;
+ else if (!prefixcmp(name, "heads/"))
+ name += 6;
+ return !strcmp(head, name);
+ }
+
+ static int show_merge_base(struct commit_list *seen, int num_rev)
+ {
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+ int exit_status = 1;
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int flags = commit->object.flags & all_mask;
+ if (!(flags & UNINTERESTING) &&
+ ((flags & all_revs) == all_revs)) {
+ puts(sha1_to_hex(commit->object.sha1));
+ exit_status = 0;
+ commit->object.flags |= UNINTERESTING;
+ }
+ }
+ return exit_status;
+ }
+
+ static int show_independent(struct commit **rev,
+ int num_rev,
+ char **ref_name,
+ unsigned int *rev_mask)
+ {
+ int i;
+
+ for (i = 0; i < num_rev; i++) {
+ struct commit *commit = rev[i];
+ unsigned int flag = rev_mask[i];
+
+ if (commit->object.flags == flag)
+ puts(sha1_to_hex(commit->object.sha1));
+ commit->object.flags |= UNINTERESTING;
+ }
+ return 0;
+ }
+
+ static void append_one_rev(const char *av)
+ {
+ unsigned char revkey[20];
+ if (!get_sha1(av, revkey)) {
+ append_ref(av, revkey, 0);
+ return;
+ }
+ if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+ /* glob style match */
+ int saved_matches = ref_name_cnt;
+ match_ref_pattern = av;
+ match_ref_slash = count_slash(av);
+ for_each_ref(append_matching_ref, NULL);
+ if (saved_matches == ref_name_cnt &&
+ ref_name_cnt < MAX_REVS)
+ error("no matching refs with %s", av);
+ if (saved_matches + 1 < ref_name_cnt)
+ sort_ref_range(saved_matches, ref_name_cnt);
+ return;
+ }
+ die("bad sha1 reference %s", av);
+ }
+
+ static int git_show_branch_config(const char *var, const char *value, void *cb)
+ {
+ if (!strcmp(var, "showbranch.default")) {
+ if (!value)
+ return config_error_nonbool(var);
+ /*
+ * default_arg is now passed to parse_options(), so we need to
+ * mimic the real argv a bit better.
+ */
+ if (!default_num) {
+ default_alloc = 20;
+ default_arg = xcalloc(default_alloc, sizeof(*default_arg));
+ default_arg[default_num++] = "show-branch";
+ } else if (default_alloc <= default_num + 1) {
+ default_alloc = default_alloc * 3 / 2 + 20;
+ default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+ }
+ default_arg[default_num++] = xstrdup(value);
+ default_arg[default_num] = NULL;
+ return 0;
+ }
+
+ if (!strcmp(var, "color.showbranch")) {
+ showbranch_use_color = git_config_colorbool(var, value, -1);
+ return 0;
+ }
+
+ return git_color_default_config(var, value, cb);
+ }
+
+ static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+ {
+ /* If the commit is tip of the named branches, do not
+ * omit it.
+ * Otherwise, if it is a merge that is reachable from only one
+ * tip, it is not that interesting.
+ */
+ int i, flag, count;
+ for (i = 0; i < n; i++)
+ if (rev[i] == commit)
+ return 0;
+ flag = commit->object.flags;
+ for (i = count = 0; i < n; i++) {
+ if (flag & (1u << (i + REV_SHIFT)))
+ count++;
+ }
+ if (count == 1)
+ return 1;
+ return 0;
+ }
+
+ static int reflog = 0;
+
+ static int parse_reflog_param(const struct option *opt, const char *arg,
+ int unset)
+ {
+ char *ep;
+ const char **base = (const char **)opt->value;
+ if (!arg)
+ arg = "";
+ reflog = strtoul(arg, &ep, 10);
+ if (*ep == ',')
+ *base = ep + 1;
+ else if (*ep)
+ return error("unrecognized reflog param '%s'", arg);
+ else
+ *base = NULL;
+ if (reflog <= 0)
+ reflog = DEFAULT_REFLOG;
+ return 0;
+ }
+
+ int cmd_show_branch(int ac, const char **av, const char *prefix)
+ {
+ struct commit *rev[MAX_REVS], *commit;
+ char *reflog_msg[MAX_REVS];
+ struct commit_list *list = NULL, *seen = NULL;
+ unsigned int rev_mask[MAX_REVS];
+ int num_rev, i, extra = 0;
+ int all_heads = 0, all_remotes = 0;
+ int all_mask, all_revs;
+ int lifo = 1;
+ char head[128];
+ const char *head_p;
+ int head_len;
+ unsigned char head_sha1[20];
+ int merge_base = 0;
+ int independent = 0;
+ int no_name = 0;
+ int sha1_name = 0;
+ int shown_merge_point = 0;
+ int with_current_branch = 0;
+ int head_at = -1;
+ int topics = 0;
+ int dense = 1;
+ const char *reflog_base = NULL;
+ struct option builtin_show_branch_options[] = {
+ OPT_BOOLEAN('a', "all", &all_heads,
+ "show remote-tracking and local branches"),
+ OPT_BOOLEAN('r', "remotes", &all_remotes,
+ "show remote-tracking branches"),
++ OPT__COLOR(&showbranch_use_color,
+ "color '*!+-' corresponding to the branch"),
+ { OPTION_INTEGER, 0, "more", &extra, "n",
+ "show <n> more commits after the common ancestor",
+ PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
+ OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1),
+ OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"),
+ OPT_BOOLEAN(0, "current", &with_current_branch,
+ "include the current branch"),
+ OPT_BOOLEAN(0, "sha1-name", &sha1_name,
+ "name commits with their object names"),
+ OPT_BOOLEAN(0, "merge-base", &merge_base,
+ "show possible merge bases"),
+ OPT_BOOLEAN(0, "independent", &independent,
+ "show refs unreachable from any other ref"),
+ OPT_BOOLEAN(0, "topo-order", &lifo,
+ "show commits in topological order"),
+ OPT_BOOLEAN(0, "topics", &topics,
+ "show only commits not on the first branch"),
+ OPT_SET_INT(0, "sparse", &dense,
+ "show merges reachable from only one tip", 0),
+ OPT_SET_INT(0, "date-order", &lifo,
+ "show commits where no parent comes before its "
+ "children", 0),
+ { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]",
+ "show <n> most recent ref-log entries starting at "
+ "base",
+ PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+ parse_reflog_param },
+ OPT_END()
+ };
+
+ git_config(git_show_branch_config, NULL);
+
+ if (showbranch_use_color == -1)
+ showbranch_use_color = git_use_color_default;
+
+ /* If nothing is specified, try the default first */
+ if (ac == 1 && default_num) {
+ ac = default_num;
+ av = default_arg;
+ }
+
+ ac = parse_options(ac, av, prefix, builtin_show_branch_options,
+ show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (all_heads)
+ all_remotes = 1;
+
+ if (extra || reflog) {
+ /* "listing" mode is incompatible with
+ * independent nor merge-base modes.
+ */
+ if (independent || merge_base)
+ usage_with_options(show_branch_usage,
+ builtin_show_branch_options);
+ if (reflog && ((0 < extra) || all_heads || all_remotes))
+ /*
+ * Asking for --more in reflog mode does not
+ * make sense. --list is Ok.
+ *
+ * Also --all and --remotes do not make sense either.
+ */
+ die("--reflog is incompatible with --all, --remotes, "
+ "--independent or --merge-base");
+ }
+
+ /* If nothing is specified, show all branches by default */
+ if (ac + all_heads + all_remotes == 0)
+ all_heads = 1;
+
+ if (reflog) {
+ unsigned char sha1[20];
+ char nth_desc[256];
+ char *ref;
+ int base = 0;
+
+ if (ac == 0) {
+ static const char *fake_av[2];
+ const char *refname;
+
+ refname = resolve_ref("HEAD", sha1, 1, NULL);
+ fake_av[0] = xstrdup(refname);
+ fake_av[1] = NULL;
+ av = fake_av;
+ ac = 1;
+ }
+ if (ac != 1)
+ die("--reflog option needs one branch name");
+
+ if (MAX_REVS < reflog)
+ die("Only %d entries can be shown at one time.",
+ MAX_REVS);
+ if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+ die("No such ref %s", *av);
+
+ /* Has the base been specified? */
+ if (reflog_base) {
+ char *ep;
+ base = strtoul(reflog_base, &ep, 10);
+ if (*ep) {
+ /* Ah, that is a date spec... */
+ unsigned long at;
+ at = approxidate(reflog_base);
+ read_ref_at(ref, at, -1, sha1, NULL,
+ NULL, NULL, &base);
+ }
+ }
+
+ for (i = 0; i < reflog; i++) {
+ char *logmsg, *m;
+ const char *msg;
+ unsigned long timestamp;
+ int tz;
+
+ if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+ ×tamp, &tz, NULL)) {
+ reflog = i;
+ break;
+ }
+ msg = strchr(logmsg, '\t');
+ if (!msg)
+ msg = "(none)";
+ else
+ msg++;
+ m = xmalloc(strlen(msg) + 200);
+ sprintf(m, "(%s) %s",
+ show_date(timestamp, tz, 1),
+ msg);
+ reflog_msg[i] = m;
+ free(logmsg);
+ sprintf(nth_desc, "%s@{%d}", *av, base+i);
+ append_ref(nth_desc, sha1, 1);
+ }
+ }
+ else if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
+ else {
+ while (0 < ac) {
+ append_one_rev(*av);
+ ac--; av++;
+ }
+ }
+
+ head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (head_p) {
+ head_len = strlen(head_p);
+ memcpy(head, head_p, head_len + 1);
+ }
+ else {
+ head_len = 0;
+ head[0] = 0;
+ }
+
+ if (with_current_branch && head_p) {
+ int has_head = 0;
+ for (i = 0; !has_head && i < ref_name_cnt; i++) {
+ /* We are only interested in adding the branch
+ * HEAD points at.
+ */
+ if (rev_is_head(head,
+ head_len,
+ ref_name[i],
+ head_sha1, NULL))
+ has_head++;
+ }
+ if (!has_head) {
+ int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
+ append_one_rev(head + offset);
+ }
+ }
+
+ if (!ref_name_cnt) {
+ fprintf(stderr, "No revs to be shown.\n");
+ exit(0);
+ }
+
+ for (num_rev = 0; ref_name[num_rev]; num_rev++) {
+ unsigned char revkey[20];
+ unsigned int flag = 1u << (num_rev + REV_SHIFT);
+
+ if (MAX_REVS <= num_rev)
+ die("cannot handle more than %d revs.", MAX_REVS);
+ if (get_sha1(ref_name[num_rev], revkey))
+ die("'%s' is not a valid ref.", ref_name[num_rev]);
+ commit = lookup_commit_reference(revkey);
+ if (!commit)
+ die("cannot find commit %s (%s)",
+ ref_name[num_rev], revkey);
+ parse_commit(commit);
+ mark_seen(commit, &seen);
+
+ /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
+ * and so on. REV_SHIFT bits from bit 0 are used for
+ * internal bookkeeping.
+ */
+ commit->object.flags |= flag;
+ if (commit->object.flags == flag)
+ insert_by_date(commit, &list);
+ rev[num_rev] = commit;
+ }
+ for (i = 0; i < num_rev; i++)
+ rev_mask[i] = rev[i]->object.flags;
+
+ if (0 <= extra)
+ join_revs(&list, &seen, num_rev, extra);
+
+ sort_by_date(&seen);
+
+ if (merge_base)
+ return show_merge_base(seen, num_rev);
+
+ if (independent)
+ return show_independent(rev, num_rev, ref_name, rev_mask);
+
+ /* Show list; --more=-1 means list-only */
+ if (1 < num_rev || extra < 0) {
+ for (i = 0; i < num_rev; i++) {
+ int j;
+ int is_head = rev_is_head(head,
+ head_len,
+ ref_name[i],
+ head_sha1,
+ rev[i]->object.sha1);
+ if (extra < 0)
+ printf("%c [%s] ",
+ is_head ? '*' : ' ', ref_name[i]);
+ else {
+ for (j = 0; j < i; j++)
+ putchar(' ');
+ printf("%s%c%s [%s] ",
+ get_color_code(i % COLUMN_COLORS_MAX),
+ is_head ? '*' : '!',
+ get_color_reset_code(), ref_name[i]);
+ }
+
+ if (!reflog) {
+ /* header lines never need name */
+ show_one_commit(rev[i], 1);
+ }
+ else
+ puts(reflog_msg[i]);
+
+ if (is_head)
+ head_at = i;
+ }
+ if (0 <= extra) {
+ for (i = 0; i < num_rev; i++)
+ putchar('-');
+ putchar('\n');
+ }
+ }
+ if (extra < 0)
+ exit(0);
+
+ /* Sort topologically */
+ sort_in_topological_order(&seen, lifo);
+
+ /* Give names to commits */
+ if (!sha1_name && !no_name)
+ name_commits(seen, rev, ref_name, num_rev);
+
+ all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int this_flag = commit->object.flags;
+ int is_merge_point = ((this_flag & all_revs) == all_revs);
+
+ shown_merge_point |= is_merge_point;
+
+ if (1 < num_rev) {
+ int is_merge = !!(commit->parents &&
+ commit->parents->next);
+ if (topics &&
+ !is_merge_point &&
+ (this_flag & (1u << REV_SHIFT)))
+ continue;
+ if (dense && is_merge &&
+ omit_in_dense(commit, rev, num_rev))
+ continue;
+ for (i = 0; i < num_rev; i++) {
+ int mark;
+ if (!(this_flag & (1u << (i + REV_SHIFT))))
+ mark = ' ';
+ else if (is_merge)
+ mark = '-';
+ else if (i == head_at)
+ mark = '*';
+ else
+ mark = '+';
+ printf("%s%c%s",
+ get_color_code(i % COLUMN_COLORS_MAX),
+ mark, get_color_reset_code());
+ }
+ putchar(' ');
+ }
+ show_one_commit(commit, no_name);
+
+ if (shown_merge_point && --extra < 0)
+ break;
+ }
+ return 0;
+ }
diff --cc builtin/var.c
index 0000000000000000000000000000000000000000,22805181900c3cd33da0b92a2560b19a0cf68842..70fdb4dec7e8b0c56ded90ee7f086cc9f16293a1
mode 000000,100644..100644
mode 000000,100644..100644
--- /dev/null
--- 2/builtin/var.c
+++ b/builtin/var.c
-static const char var_usage[] = "git var [-l | <variable>]";
+ /*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+ #include "cache.h"
+ #include "exec_cmd.h"
+
- const char *pgm = git_pager();
++static const char var_usage[] = "git var (-l | <variable>)";
+
+ static const char *editor(int flag)
+ {
+ const char *pgm = git_editor();
+
+ if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+ die("Terminal is dumb, but EDITOR unset");
+
+ return pgm;
+ }
+
+ static const char *pager(int flag)
+ {
++ const char *pgm = git_pager(1);
+
+ if (!pgm)
+ pgm = "cat";
+ return pgm;
+ }
+
+ struct git_var {
+ const char *name;
+ const char *(*read)(int);
+ };
+ static struct git_var git_vars[] = {
+ { "GIT_COMMITTER_IDENT", git_committer_info },
+ { "GIT_AUTHOR_IDENT", git_author_info },
+ { "GIT_EDITOR", editor },
+ { "GIT_PAGER", pager },
+ { "", NULL },
+ };
+
+ static void list_vars(void)
+ {
+ struct git_var *ptr;
+ const char *val;
+
+ for (ptr = git_vars; ptr->read; ptr++)
+ if ((val = ptr->read(0)))
+ printf("%s=%s\n", ptr->name, val);
+ }
+
+ static const char *read_var(const char *var)
+ {
+ struct git_var *ptr;
+ const char *val;
+ val = NULL;
+ for (ptr = git_vars; ptr->read; ptr++) {
+ if (strcmp(var, ptr->name) == 0) {
+ val = ptr->read(IDENT_ERROR_ON_NO_NAME);
+ break;
+ }
+ }
+ return val;
+ }
+
+ static int show_config(const char *var, const char *value, void *cb)
+ {
+ if (value)
+ printf("%s=%s\n", var, value);
+ else
+ printf("%s\n", var);
+ return git_default_config(var, value, cb);
+ }
+
+ int cmd_var(int argc, const char **argv, const char *prefix)
+ {
+ const char *val;
+ int nongit;
+ if (argc != 2) {
+ usage(var_usage);
+ }
+
+ setup_git_directory_gently(&nongit);
+ val = NULL;
+
+ if (strcmp(argv[1], "-l") == 0) {
+ git_config(show_config, NULL);
+ list_vars();
+ return 0;
+ }
+ git_config(git_default_config, NULL);
+ val = read_var(argv[1]);
+ if (!val)
+ usage(var_usage);
+
+ printf("%s\n", val);
+
+ return 0;
+ }