From: Junio C Hamano Date: Wed, 10 Mar 2010 23:25:18 +0000 (-0800) Subject: Merge branch 'lt/deepen-builtin-source' X-Git-Tag: v1.7.1-rc0~76 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=2e0e8b68e3ba8ea87f001c45c78f9b7ce549c61f;p=git.git Merge branch 'lt/deepen-builtin-source' * lt/deepen-builtin-source: Move 'builtin-*' into a 'builtin/' subdirectory Conflicts: Makefile --- 2e0e8b68e3ba8ea87f001c45c78f9b7ce549c61f diff --cc Makefile index f64610a57,f1025d5c0..cdc58fed7 --- a/Makefile +++ b/Makefile @@@ -370,35 -362,16 +370,35 @@@ EXTRA_PROGRAMS # ... 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 @@@ -1632,133 -1592,12 +1632,133 @@@ git.o git.spec $(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 . +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 000000000,a28a13986..6cf7e721e mode 000000,100644..100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@@ -1,0 -1,696 +1,696 @@@ + /* + * Builtin "git branch" + * + * Copyright (c) 2006 Kristian Høgsberg + * 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] []", + "git branch [options] [-r] (-d | -D) ", + "git branch [options] (-m | -M) [] ", + 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_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), ++ 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 000000000,c5ab7835e..acefaaf41 mode 000000,100644..100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@@ -1,0 -1,853 +1,835 @@@ + #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] ", + "git checkout [options] [] -- ...", + 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"); + } + -/* 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; -} - + 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); + - fill_mm(active_cache[pos]->sha1, &ancestor); - fill_mm(active_cache[pos+1]->sha1, &ours); - fill_mm(active_cache[pos+2]->sha1, &theirs); ++ 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 + * 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 -- [] + * + * must be a valid tree, everything after the '--' must be + * a path. + * + * case 2: git checkout -- [] + * + * everything after the '--' must be paths. + * + * case 3: git checkout [] + * + * With no paths, if is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if 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 is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * in one and only one remote, then this is a short-hand + * to fork local from that remote tracking branch. + * + * Otherwise 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 000000000,55676fd87..f4c73442c mode 000000,100644..100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -1,0 -1,1310 +1,1310 @@@ + /* + * Builtin "git commit" + * + * Copyright (c) 2007 Kristian Høgsberg + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds + */ + + #include "cache.h" + #include "cache-tree.h" + #include "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] [--] ...", + NULL + }; + + static const char * const builtin_status_usage[] = { + "git status [options] [--] ...", + 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" -" git config --global user.name Your Name\n" ++" 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 '\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(); ++ 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 000000000,8654fa7a2..b6c5b344b mode 000000,100644..100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@@ -1,0 -1,920 +1,942 @@@ + /* + * "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] [ ...]", + "git fetch [options] ", + "git fetch --multiple [options] [ | ]...", + "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..remote, we add the + * ref given in branch..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; + } + -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) + #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) - sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, ++ 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, "[rejected]", 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, "[tag update]", REFCOL_WIDTH, remote, ++ 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, what, REFCOL_WIDTH, remote, pretty_ref, ++ 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, quickref, 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, "[rejected]", REFCOL_WIDTH, remote, ++ 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, *kind ? kind : "branch", ++ 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", - SUMMARY_WIDTH, "[deleted]", ++ 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) { - 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); ++ 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[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; - int argc = 1; ++ 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 000000000,a5a83f146..62be1bbfd mode 000000,100644..100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@@ -1,0 -1,955 +1,996 @@@ + #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; -static int used_atom_cnt, sort_atom_limit, need_tagged; ++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 == '%') { - /* %( is the start of an atom; ++ /* ++ * %( 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"; + * it's not possible that is not ":" 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); + } + - /* For a tag or a commit object, if "creator" or "creatordate" is ++ /* ++ * 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); + } + } + -/* We want to have empty print-string for field requests ++/* ++ * 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 there is no atom that wants to know about tagged ++ /* ++ * If there is no atom that wants to know about tagged + * object, we are done. + */ + if (!need_tagged || (obj->type != OBJ_TAG)) + return; + - /* If it is a tag object, see if we use a value that derefs ++ /* ++ * 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; + - /* NEEDSWORK: This derefs tag only once, which ++ /* ++ * 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; + }; + + /* - * 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. ++ * 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; + } + - /* We do not open the object yet; sort may only need refname ++ /* ++ * 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] []", + 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 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; + - for (i = 0; i < used_atom_cnt; i++) { - if (used_atom[i][0] == '*') { - need_tagged = 1; - break; - } - } - + 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 000000000,552ef1fac..40b9a9312 mode 000000,100644..100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@@ -1,0 -1,1010 +1,1011 @@@ + /* + * 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 + #endif + + static char const * const grep_usage[] = { + "git grep [options] [-e] [...] [[--] 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 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_SET_INT(0, "color", &opt.color, "highlight matches", 1), ++ OPT__COLOR(&opt.color, "highlight matches"), + OPT_GROUP(""), + OPT_CALLBACK('C', NULL, &opt, "n", + "show context lines before and after matches", + context_callback), + OPT_INTEGER('B', NULL, &opt.pre_context, + "show context lines before matches"), + OPT_INTEGER('A', NULL, &opt.post_context, + "show 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 ", 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 ', 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 000000000,6a5f5b5f0..080af1a01 mode 000000,100644..100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@@ -1,0 -1,134 +1,134 @@@ + /* + * 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); + } - hash_object(buf.buf, type, write_objects, buf.buf); ++ 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 ] [-w] [--path=|--no-filters] [--stdin] [--] ...", + "git hash-object --stdin-paths < ", + NULL + }; + + static const char *type; + static int write_object; + static int hashstdin; + static int stdin_paths; -static int 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 (no_filters) - errstr = "Can't use --stdin-paths with --no-filters"; + } + 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 000000000,dd84caecb..aae7a4d7e mode 000000,100644..100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@@ -1,0 -1,498 +1,501 @@@ + /* + * 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"); + } + - if (!(flags & INIT_DB_QUIET)) - printf("%s%s Git repository in %s/\n", ++ 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" : "", - get_git_dir()); ++ 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=] [--shared[=]] [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=) not allowed without " + "specifying %s (or --git-dir=)", + 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 000000000,a50ac2256..ce2ef6bed mode 000000,100644..100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@@ -1,0 -1,1064 +1,1063 @@@ + /* + * 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 */ + + 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 " (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe (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; + + /* + * "--- " starts patches without headers + * "---*" 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) { - strbuf_ltrim(line); - if (!line->len) ++ 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= | -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 000000000,e1d3adf40..97802585e mode 000000,100644..100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@@ -1,0 -1,2375 +1,2334 @@@ + #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 + #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" + " [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; + } + -/* - * 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; -} - + /* + * 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, 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; - hdrlen = encode_header(type, entry->size, header); ++ 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; + } + -/* forward declaration for write_pack_file */ -static int adjust_perm(const char *path, mode_t mode); - + 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, "", 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) { - mode_t mode = umask(0); + struct stat st; + const char *idx_tmp_name; + char tmpname[PATH_MAX]; + - umask(mode); - mode = 0444 & ~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(pack_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)); - if (adjust_perm(idx_tmp_name, mode)) ++ 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); + } + -static int adjust_perm(const char *path, mode_t mode) -{ - if (chmod(path, mode)) - return -1; - return adjust_shared_perm(path); -} - + 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 + * + * 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 000000000,4675f6054..81f915ec3 mode 000000,100644..100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@@ -1,0 -1,169 +1,166 @@@ + #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