From: Junio C Hamano Date: Fri, 17 Mar 2017 20:50:24 +0000 (-0700) Subject: Merge branch 'bc/object-id' X-Git-Tag: v2.13.0-rc0~108 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/e1fae930193b3e8ff02cee936605625f63e1d1e4?ds=inline;hp=-c Merge branch 'bc/object-id' "uchar [40]" to "struct object_id" conversion continues. * bc/object-id: wt-status: convert to struct object_id builtin/merge-base: convert to struct object_id Convert object iteration callbacks to struct object_id sha1_file: introduce an nth_packed_object_oid function refs: simplify parsing of reflog entries refs: convert each_reflog_ent_fn to struct object_id reflog-walk: convert struct reflog_info to struct object_id builtin/replace: convert to struct object_id Convert remaining callers of resolve_refdup to object_id builtin/merge: convert to struct object_id builtin/clone: convert to struct object_id builtin/branch: convert to struct object_id builtin/grep: convert to struct object_id builtin/fmt-merge-message: convert to struct object_id builtin/fast-export: convert to struct object_id builtin/describe: convert to struct object_id builtin/diff-tree: convert to struct object_id builtin/commit: convert to struct object_id hex: introduce parse_oid_hex --- e1fae930193b3e8ff02cee936605625f63e1d1e4 diff --combined builtin/branch.c index 5d44e36cba,faf472ff8f..52688f2e1b --- a/builtin/branch.c +++ b/builtin/branch.c @@@ -28,21 -28,20 +28,21 @@@ static const char * const builtin_branc N_("git branch [] [-r] (-d | -D) ..."), N_("git branch [] (-m | -M) [] "), N_("git branch [] [-r | -a] [--points-at]"), + N_("git branch [] [-r | -a] [--format]"), NULL }; static const char *head; - static unsigned char head_sha1[20]; + static struct object_id head_oid; 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 */ - GIT_COLOR_BLUE, /* UPSTREAM */ + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_RED, /* REMOTE */ + GIT_COLOR_NORMAL, /* LOCAL */ + GIT_COLOR_GREEN, /* CURRENT */ + GIT_COLOR_BLUE, /* UPSTREAM */ }; enum color_branch { BRANCH_COLOR_RESET = 0, @@@ -118,13 -117,13 +118,13 @@@ static int branch_merged(int kind, cons if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); - unsigned char sha1[20]; + struct object_id oid; if (upstream && (reference_name = reference_name_to_free = resolve_refdup(upstream, RESOLVE_REF_READING, - sha1, NULL)) != NULL) - reference_rev = lookup_commit_reference(sha1); + oid.hash, NULL)) != NULL) + reference_rev = lookup_commit_reference(oid.hash); } if (!reference_rev) reference_rev = head_rev; @@@ -154,10 -153,10 +154,10 @@@ } static int check_branch_commit(const char *branchname, const char *refname, - const unsigned char *sha1, struct commit *head_rev, + const struct object_id *oid, struct commit *head_rev, int kinds, int force) { - struct commit *rev = lookup_commit_reference(sha1); + struct commit *rev = lookup_commit_reference(oid->hash); if (!rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; @@@ -184,34 -183,31 +184,34 @@@ static int delete_branches(int argc, co int quiet) { struct commit *head_rev = NULL; - unsigned char sha1[20]; + struct object_id oid; char *name = NULL; const char *fmt; int i; int ret = 0; int remote_branch = 0; struct strbuf bname = STRBUF_INIT; + unsigned allowed_interpret; switch (kinds) { case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; + allowed_interpret = INTERPRET_BRANCH_REMOTE; force = 1; break; case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; + allowed_interpret = INTERPRET_BRANCH_LOCAL; break; default: die(_("cannot use -a with -d")); } if (!force) { - head_rev = lookup_commit_reference(head_sha1); + head_rev = lookup_commit_reference(head_oid.hash); if (!head_rev) die(_("Couldn't look up commit object for HEAD")); } @@@ -219,7 -215,7 +219,7 @@@ char *target = NULL; int flags = 0; - strbuf_branchname(&bname, argv[i]); + strbuf_branchname(&bname, argv[i], allowed_interpret); free(name); name = mkpathdup(fmt, bname.buf); @@@ -239,7 -235,7 +239,7 @@@ RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE | RESOLVE_REF_ALLOW_BAD_NAME, - sha1, &flags); + oid.hash, &flags); if (!target) { error(remote_branch ? _("remote-tracking branch '%s' not found.") @@@ -249,13 -245,13 +249,13 @@@ } if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) && - check_branch_commit(bname.buf, name, sha1, head_rev, kinds, + check_branch_commit(bname.buf, name, &oid, head_rev, kinds, force)) { ret = 1; goto next; } - if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1, - if (delete_ref(name, is_null_oid(&oid) ? NULL : oid.hash, ++ if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash, REF_NODEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") @@@ -271,7 -267,7 +271,7 @@@ bname.buf, (flags & REF_ISBROKEN) ? "broken" : (flags & REF_ISSYMREF) ? target - : find_unique_abbrev(sha1, DEFAULT_ABBREV)); + : find_unique_abbrev(oid.hash, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); @@@ -284,109 -280,221 +284,109 @@@ return(ret); } -static void fill_tracking_info(struct strbuf *stat, const char *branch_name, - int show_upstream_ref) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { - int ours, theirs; - char *ref = NULL; - struct branch *branch = branch_get(branch_name); - const char *upstream; - struct strbuf fancy = STRBUF_INIT; - int upstream_is_gone = 0; - int added_decoration = 1; - - if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) { - if (!upstream) - return; - upstream_is_gone = 1; - } - - if (show_upstream_ref) { - ref = shorten_unambiguous_ref(upstream, 0); - if (want_color(branch_use_color)) - strbuf_addf(&fancy, "%s%s%s", - branch_get_color(BRANCH_COLOR_UPSTREAM), - ref, branch_get_color(BRANCH_COLOR_RESET)); - else - strbuf_addstr(&fancy, ref); - } + int i, max = 0; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; + const char *desc = it->refname; + int w; - if (upstream_is_gone) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: gone]"), fancy.buf); - else - added_decoration = 0; - } else if (!ours && !theirs) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s]"), fancy.buf); - else - added_decoration = 0; - } else if (!ours) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs); - else - strbuf_addf(stat, _("[behind %d]"), theirs); + skip_prefix(it->refname, "refs/heads/", &desc); + skip_prefix(it->refname, "refs/remotes/", &desc); + if (it->kind == FILTER_REFS_DETACHED_HEAD) { + char *head_desc = get_head_description(); + w = utf8_strwidth(head_desc); + free(head_desc); + } else + w = utf8_strwidth(desc); - } else if (!theirs) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours); - else - strbuf_addf(stat, _("[ahead %d]"), ours); - } else { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), - fancy.buf, ours, theirs); - else - strbuf_addf(stat, _("[ahead %d, behind %d]"), - ours, theirs); + if (it->kind == FILTER_REFS_REMOTES) + w += remote_bonus; + if (w > max) + max = w; } - strbuf_release(&fancy); - if (added_decoration) - strbuf_addch(stat, ' '); - free(ref); + return max; } -static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, - struct ref_filter *filter, const char *refname) +static const char *quote_literal_for_format(const char *s) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = _(" **** invalid ref ****"); - struct commit *commit = item->commit; + static struct strbuf buf = STRBUF_INIT; - if (!parse_commit(commit)) { - pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); - sub = subject.buf; + strbuf_reset(&buf); + while (*s) { + const char *ep = strchrnul(s, '%'); + if (s < ep) + strbuf_add(&buf, s, ep - s); + if (*ep == '%') { + strbuf_addstr(&buf, "%%"); + s = ep + 1; + } else { + s = ep; + } } - - if (item->kind == FILTER_REFS_BRANCHES) - fill_tracking_info(&stat, refname, filter->verbose > 1); - - strbuf_addf(out, " %s %s%s", - find_unique_abbrev(item->commit->object.oid.hash, filter->abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); + return buf.buf; } -static char *get_head_description(void) +static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix) { - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - if (state.detached_at) - /* TRANSLATORS: make sure this matches - "HEAD detached at " in wt-status.c */ - strbuf_addf(&desc, _("(HEAD detached at %s)"), - state.detached_from); - else - /* TRANSLATORS: make sure this matches - "HEAD detached from " in wt-status.c */ - strbuf_addf(&desc, _("(HEAD detached from %s)"), - state.detached_from); - } - else - strbuf_addstr(&desc, _("(no branch)")); - free(state.branch); - free(state.onto); - free(state.detached_from); - return strbuf_detach(&desc, NULL); -} + struct strbuf fmt = STRBUF_INIT; + struct strbuf local = STRBUF_INIT; + struct strbuf remote = STRBUF_INIT; -static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, - struct ref_filter *filter, const char *remote_prefix) -{ - char c; - int current = 0; - int color; - struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - const char *prefix_to_show = ""; - const char *prefix_to_skip = NULL; - const char *desc = item->refname; - char *to_free = NULL; + strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else) %%(end)", + branch_get_color(BRANCH_COLOR_CURRENT)); - switch (item->kind) { - case FILTER_REFS_BRANCHES: - prefix_to_skip = "refs/heads/"; - skip_prefix(desc, prefix_to_skip, &desc); - if (!filter->detached && !strcmp(desc, head)) - current = 1; + if (filter->verbose) { + struct strbuf obname = STRBUF_INIT; + + if (filter->abbrev < 0) + strbuf_addf(&obname, "%%(objectname:short)"); + else if (!filter->abbrev) + strbuf_addf(&obname, "%%(objectname)"); else - color = BRANCH_COLOR_LOCAL; - break; - case FILTER_REFS_REMOTES: - prefix_to_skip = "refs/remotes/"; - skip_prefix(desc, prefix_to_skip, &desc); - color = BRANCH_COLOR_REMOTE; - prefix_to_show = remote_prefix; - break; - case FILTER_REFS_DETACHED_HEAD: - desc = to_free = get_head_description(); - current = 1; - break; - default: - color = BRANCH_COLOR_PLAIN; - break; - } + strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev); - c = ' '; - if (current) { - c = '*'; - color = BRANCH_COLOR_CURRENT; - } + strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth); + strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET)); + strbuf_addf(&local, " %s ", obname.buf); - strbuf_addf(&name, "%s%s", prefix_to_show, desc); - if (filter->verbose) { - int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); - strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), - maxwidth + utf8_compensation, 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->symref) { - const char *symref = item->symref; - if (prefix_to_skip) - skip_prefix(symref, prefix_to_skip, &symref); - strbuf_addf(&out, " -> %s", symref); - } - else if (filter->verbose) - /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, filter, desc); - if (column_active(colopts)) { - assert(!filter->verbose && "--column and --verbose are incompatible"); - string_list_append(&output, out.buf); + if (filter->verbose > 1) + strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)" + "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)", + branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)"); + + strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s" + "%%(if)%%(symref)%%(then) -> %%(symref:short)" + "%%(else) %s %%(contents:subject)%%(end)", + branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix), + branch_get_color(BRANCH_COLOR_RESET), obname.buf); + strbuf_release(&obname); } else { - printf("%s\n", out.buf); + strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)", + branch_get_color(BRANCH_COLOR_RESET)); + strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)", + branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix), + branch_get_color(BRANCH_COLOR_RESET)); } - strbuf_release(&name); - strbuf_release(&out); - free(to_free); -} - -static int calc_maxwidth(struct ref_array *refs, int remote_bonus) -{ - int i, max = 0; - for (i = 0; i < refs->nr; i++) { - struct ref_array_item *it = refs->items[i]; - const char *desc = it->refname; - int w; - skip_prefix(it->refname, "refs/heads/", &desc); - skip_prefix(it->refname, "refs/remotes/", &desc); - w = utf8_strwidth(desc); + strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf); - if (it->kind == FILTER_REFS_REMOTES) - w += remote_bonus; - if (w > max) - max = w; - } - return max; + strbuf_release(&local); + strbuf_release(&remote); + return strbuf_detach(&fmt, NULL); } -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { int i; struct ref_array array; int maxwidth = 0; const char *remote_prefix = ""; + struct strbuf out = STRBUF_INIT; + char *to_free = NULL; /* * If we are listing more than just remote branches, @@@ -398,32 -506,18 +398,32 @@@ memset(&array, 0, sizeof(array)); - verify_ref_format("%(refname)%(symref)"); filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); + if (!format) + format = to_free = build_format(filter, maxwidth, remote_prefix); + verify_ref_format(format); + ref_array_sort(sorting, &array); - for (i = 0; i < array.nr; i++) - format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix); + for (i = 0; i < array.nr; i++) { + format_ref_array_item(array.items[i], format, 0, &out); + if (column_active(colopts)) { + assert(!filter->verbose && "--column and --verbose are incompatible"); + /* format to a string_list to let print_columns() do its job */ + string_list_append(&output, out.buf); + } else { + fwrite(out.buf, 1, out.len, stdout); + putchar('\n'); + } + strbuf_release(&out); + } ref_array_clear(&array); + free(to_free); } static void reject_rebase_or_bisect_branch(const char *target) @@@ -485,15 -579,14 +485,15 @@@ static void rename_branch(const char *o 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); - if (replace_each_worktree_head_symref(oldref.buf, newref.buf)) + if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); + strbuf_release(&logmsg); + strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); @@@ -545,7 -638,6 +545,7 @@@ int cmd_branch(int argc, const char **a struct ref_filter filter; int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + const char *format = NULL; struct option options[] = { OPT_GROUP(N_("Generic options")), @@@ -587,12 -679,9 +587,12 @@@ N_("print only branches of the object"), 0, parse_opt_object_name }, OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_END(), }; + setup_ref_filter_porcelain_msg(); + memset(&filter, 0, sizeof(filter)); filter.kind = FILTER_REFS_BRANCHES; filter.abbrev = -1; @@@ -604,7 -693,7 +604,7 @@@ track = git_branch_track; - head = resolve_refdup("HEAD", 0, head_sha1, NULL); + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) @@@ -660,7 -749,7 +660,7 @@@ if (!sorting) sorting = ref_default_sorting(); sorting->ignore_case = icase; - print_ref_list(&filter, sorting); + print_ref_list(&filter, sorting, format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; diff --combined builtin/describe.c index 6769446e1f,738e68f95b..76c18059bf --- a/builtin/describe.c +++ b/builtin/describe.c @@@ -28,8 -28,7 +28,8 @@@ static int abbrev = -1; /* unspecified static int max_candidates = 10; static struct hashmap names; static int have_util; -static const char *pattern; +static struct string_list patterns = STRING_LIST_INIT_NODUP; +static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP; static int always; static const char *dirty; @@@ -40,11 -39,11 +40,11 @@@ static const char *diff_index_args[] = struct commit_name { struct hashmap_entry entry; - unsigned char peeled[20]; + struct object_id peeled; struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; - unsigned char sha1[20]; + struct object_id oid; char *path; }; @@@ -55,17 -54,17 +55,17 @@@ static const char *prio_names[] = static int commit_name_cmp(const struct commit_name *cn1, const struct commit_name *cn2, const void *peeled) { - return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled); + return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled); } - static inline struct commit_name *find_commit_name(const unsigned char *peeled) + static inline struct commit_name *find_commit_name(const struct object_id *peeled) { - return hashmap_get_from_hash(&names, sha1hash(peeled), peeled); + return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash); } static int replace_name(struct commit_name *e, int prio, - const unsigned char *sha1, + const struct object_id *oid, struct tag **tag) { if (!e || e->prio < prio) @@@ -78,13 -77,13 +78,13 @@@ struct tag *t; if (!e->tag) { - t = lookup_tag(e->sha1); + t = lookup_tag(e->oid.hash); if (!t || parse_tag(t)) return 1; e->tag = t; } - t = lookup_tag(sha1); + t = lookup_tag(oid->hash); if (!t || parse_tag(t)) return 0; *tag = t; @@@ -97,24 -96,24 +97,24 @@@ } static void add_to_known_names(const char *path, - const unsigned char *peeled, + const struct object_id *peeled, int prio, - const unsigned char *sha1) + const struct object_id *oid) { struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; - if (replace_name(e, prio, sha1, &tag)) { + if (replace_name(e, prio, oid, &tag)) { if (!e) { e = xmalloc(sizeof(struct commit_name)); - hashcpy(e->peeled, peeled); - hashmap_entry_init(e, sha1hash(peeled)); + oidcpy(&e->peeled, peeled); + hashmap_entry_init(e, sha1hash(peeled->hash)); hashmap_add(&names, e); e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; - hashcpy(e->sha1, sha1); + oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); } @@@ -130,40 -129,9 +130,40 @@@ static int get_name(const char *path, c if (!all && !is_tag) return 0; - /* Accept only tags that match the pattern, if given */ - if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL))) - return 0; + /* + * If we're given exclude patterns, first exclude any tag which match + * any of the exclude pattern. + */ + if (exclude_patterns.nr) { + struct string_list_item *item; + + if (!is_tag) + return 0; + + for_each_string_list_item(item, &exclude_patterns) { + if (!wildmatch(item->string, path + 10, 0, NULL)) + return 0; + } + } + + /* + * If we're given patterns, accept only tags which match at least one + * pattern. + */ + if (patterns.nr) { + struct string_list_item *item; + + if (!is_tag) + return 0; + + for_each_string_list_item(item, &patterns) { + if (!wildmatch(item->string, path + 10, 0, NULL)) + break; + + /* If we get here, no pattern matched. */ + return 0; + } + } /* Is it annotated? */ if (!peel_ref(path, peeled.hash)) { @@@ -186,7 -154,7 +186,7 @@@ else prio = 0; - add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash); + add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid); return 0; } @@@ -244,7 -212,7 +244,7 @@@ static unsigned long finish_depth_compu static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(n->sha1); + n->tag = lookup_tag(n->oid.hash); if (!n->tag || parse_tag(n->tag)) die(_("annotated tag %s not available"), n->path); } @@@ -262,14 -230,14 +262,14 @@@ printf("%s", n->path); } - static void show_suffix(int depth, const unsigned char *sha1) + static void show_suffix(int depth, const struct object_id *oid) { - printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev)); + printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); } static void describe(const char *arg, int last_one) { - unsigned char sha1[20]; + struct object_id oid; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; struct commit_name *n; @@@ -278,20 -246,20 +278,20 @@@ unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - if (get_sha1(arg, sha1)) + if (get_oid(arg, &oid)) die(_("Not a valid object name %s"), arg); - cmit = lookup_commit_reference(sha1); + cmit = lookup_commit_reference(oid.hash); if (!cmit) die(_("%s is not a valid '%s' object"), arg, commit_type); - n = find_commit_name(cmit->object.oid.hash); + n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ display_name(n); if (longformat) - show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1); + show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid); if (dirty) printf("%s", dirty); printf("\n"); @@@ -308,7 -276,7 +308,7 @@@ struct commit *c; struct commit_name *n = hashmap_iter_first(&names, &iter); for (; n; n = hashmap_iter_next(&iter)) { - c = lookup_commit_reference_gently(n->peeled, 1); + c = lookup_commit_reference_gently(n->peeled.hash, 1); if (c) c->util = n; } @@@ -412,7 -380,7 +412,7 @@@ display_name(all_matches[0].name); if (abbrev) - show_suffix(all_matches[0].depth, cmit->object.oid.hash); + show_suffix(all_matches[0].depth, &cmit->object.oid); if (dirty) printf("%s", dirty); printf("\n"); @@@ -436,10 -404,8 +436,10 @@@ int cmd_describe(int argc, const char * N_("only output exact matches"), 0), OPT_INTEGER(0, "candidates", &max_candidates, N_("consider most recent tags (default: 10)")), - OPT_STRING(0, "match", &pattern, N_("pattern"), + OPT_STRING_LIST(0, "match", &patterns, N_("pattern"), N_("only consider tags matching ")), + OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"), + N_("do not consider tags matching ")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), @@@ -464,7 -430,6 +464,7 @@@ die(_("--long is incompatible with --abbrev=0")); if (contains) { + struct string_list_item *item; struct argv_array args; argv_array_init(&args); @@@ -475,10 -440,8 +475,10 @@@ argv_array_push(&args, "--always"); if (!all) { argv_array_push(&args, "--tags"); - if (pattern) - argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); + for_each_string_list_item(item, &patterns) + argv_array_pushf(&args, "--refs=refs/tags/%s", item->string); + for_each_string_list_item(item, &exclude_patterns) + argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string); } if (argc) argv_array_pushv(&args, argv); diff --combined builtin/diff-tree.c index 8ce00480cd,06ac997976..326f88b657 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@@ -7,46 -7,44 +7,44 @@@ static struct rev_info log_tree_opt; - static int diff_tree_commit_sha1(const unsigned char *sha1) + static int diff_tree_commit_sha1(const struct object_id *oid) { - struct commit *commit = lookup_commit_reference(sha1); + struct commit *commit = lookup_commit_reference(oid->hash); if (!commit) return -1; return log_tree_commit(&log_tree_opt, commit); } /* Diff one or more commits. */ - static int stdin_diff_commit(struct commit *commit, char *line, int len) + static int stdin_diff_commit(struct commit *commit, const char *p) { - unsigned char sha1[20]; - if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { - /* Graft the fake parents locally to the commit */ - int pos = 41; - struct commit_list **pptr; - - /* Free the real parent list */ - free_commit_list(commit->parents); - commit->parents = NULL; - pptr = &(commit->parents); - while (line[pos] && !get_sha1_hex(line + pos, sha1)) { - struct commit *parent = lookup_commit(sha1); - if (parent) { - pptr = &commit_list_insert(parent, pptr)->next; - } - pos += 41; + struct object_id oid; + struct commit_list **pptr = NULL; + + /* Graft the fake parents locally to the commit */ + while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) { + struct commit *parent = lookup_commit(oid.hash); + if (!pptr) { + /* Free the real parent list */ + free_commit_list(commit->parents); + commit->parents = NULL; + pptr = &(commit->parents); + } + if (parent) { + pptr = &commit_list_insert(parent, pptr)->next; } } return log_tree_commit(&log_tree_opt, commit); } /* Diff two trees. */ - static int stdin_diff_trees(struct tree *tree1, char *line, int len) + static int stdin_diff_trees(struct tree *tree1, const char *p) { - unsigned char sha1[20]; + struct object_id oid; struct tree *tree2; - if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1)) + if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p) return error("Need exactly two trees, separated by a space"); - tree2 = lookup_tree(sha1); + tree2 = lookup_tree(oid.hash); if (!tree2 || parse_tree(tree2)) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), @@@ -60,23 -58,24 +58,24 @@@ static int diff_tree_stdin(char *line) { int len = strlen(line); - unsigned char sha1[20]; + struct object_id oid; struct object *obj; + const char *p; if (!len || line[len-1] != '\n') return -1; line[len-1] = 0; - if (get_sha1_hex(line, sha1)) + if (parse_oid_hex(line, &oid, &p)) return -1; - obj = parse_object(sha1); + obj = parse_object(oid.hash); if (!obj) return -1; if (obj->type == OBJ_COMMIT) - return stdin_diff_commit((struct commit *)obj, line, len); + return stdin_diff_commit((struct commit *)obj, p); if (obj->type == OBJ_TREE) - return stdin_diff_trees((struct tree *)obj, line, len); + return stdin_diff_trees((struct tree *)obj, p); error("Object %s is a %s, not a commit or tree", - sha1_to_hex(sha1), typename(obj->type)); + oid_to_hex(&oid), typename(obj->type)); return -1; } @@@ -141,13 -140,15 +140,13 @@@ int cmd_diff_tree(int argc, const char break; case 1: tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(tree1->oid.hash); + diff_tree_commit_sha1(&tree1->oid); break; case 2: tree1 = opt->pending.objects[0].item; tree2 = opt->pending.objects[1].item; if (tree2->flags & UNINTERESTING) { - struct object *tmp = tree2; - tree2 = tree1; - tree1 = tmp; + SWAP(tree2, tree1); } diff_tree_sha1(tree1->oid.hash, tree2->oid.hash, @@@ -164,9 -165,9 +163,9 @@@ opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); while (fgets(line, sizeof(line), stdin)) { - unsigned char sha1[20]; + struct object_id oid; - if (get_sha1_hex(line, sha1)) { + if (get_oid_hex(line, &oid)) { fputs(line, stdout); fflush(stdout); } diff --combined builtin/grep.c index 9304c33e75,0393b0fdc4..837836fb3e --- a/builtin/grep.c +++ b/builtin/grep.c @@@ -294,17 -294,17 +294,17 @@@ static int grep_cmd_config(const char * return st; } - static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) + static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size) { void *data; grep_read_lock(); - data = read_sha1_file(sha1, type, size); + data = read_sha1_file(oid->hash, type, size); grep_read_unlock(); return data; } - static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, + static int grep_oid(struct grep_opt *opt, const struct object_id *oid, const char *filename, int tree_name_len, const char *path) { @@@ -323,7 -323,7 +323,7 @@@ #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); strbuf_release(&pathbuf); return 0; } else @@@ -332,7 -332,7 +332,7 @@@ struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); strbuf_release(&pathbuf); hit = grep_source(opt, &gs); @@@ -690,7 -690,7 +690,7 @@@ static int grep_cache(struct grep_opt * ce_skip_worktree(ce)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; - hit |= grep_sha1(opt, ce->oid.hash, ce->name, + hit |= grep_oid(opt, &ce->oid, ce->name, 0, ce->name); } else { hit |= grep_file(opt, ce->name); @@@ -750,7 -750,7 +750,7 @@@ static int grep_tree(struct grep_opt *o strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, + hit |= grep_oid(opt, entry.oid, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@@ -758,7 -758,7 +758,7 @@@ void *data; unsigned long size; - data = lock_and_read_sha1_file(entry.oid->hash, &type, &size); + data = lock_and_read_oid_file(entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(entry.oid)); @@@ -787,7 -787,7 +787,7 @@@ static int grep_object(struct grep_opt struct object *obj, const char *name, const char *path) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->oid.hash, name, 0, path); + return grep_oid(opt, &obj->oid, name, 0, path); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@@ -967,7 -967,6 +967,7 @@@ int cmd_grep(int argc, const char **arg int dummy; int use_index = 1; int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED; + int allow_revs; struct option options[] = { OPT_BOOL(0, "cached", &cached, @@@ -1150,69 -1149,26 +1150,69 @@@ compile_grep_patterns(&opt); - /* Check revs and then paths */ + /* + * We have to find "--" in a separate pass, because its presence + * influences how we will parse arguments that come before it. + */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + seen_dashdash = 1; + break; + } + } + + /* + * Resolve any rev arguments. If we have a dashdash, then everything up + * to it must resolve as a rev. If not, then we stop at the first + * non-rev and assume everything else is a path. + */ + allow_revs = use_index && !untracked; for (i = 0; i < argc; i++) { const char *arg = argv[i]; - unsigned char sha1[20]; + struct object_id oid; struct object_context oc; - /* Is it a rev? */ - if (!get_sha1_with_context(arg, 0, oid.hash, &oc)) { - struct object *object = parse_object_or_die(oid.hash, arg); - if (!seen_dashdash) - verify_non_filename(prefix, arg); - add_object_array_with_path(object, arg, &list, oc.mode, oc.path); - continue; - } + struct object *object; + if (!strcmp(arg, "--")) { i++; - seen_dashdash = 1; + break; } - break; + + if (!allow_revs) { + if (seen_dashdash) + die(_("--no-index or --untracked cannot be used with revs")); + break; + } + - if (get_sha1_with_context(arg, 0, sha1, &oc)) { ++ if (get_sha1_with_context(arg, 0, oid.hash, &oc)) { + if (seen_dashdash) + die(_("unable to resolve revision: %s"), arg); + break; + } + - object = parse_object_or_die(sha1, arg); ++ object = parse_object_or_die(oid.hash, arg); + if (!seen_dashdash) + verify_non_filename(prefix, arg); + add_object_array_with_path(object, arg, &list, oc.mode, oc.path); } + /* + * Anything left over is presumed to be a path. But in the non-dashdash + * "do what I mean" case, we verify and complain when that isn't true. + */ + if (!seen_dashdash) { + int j; + for (j = i; j < argc; j++) + verify_filename(prefix, argv[j], j == i && allow_revs); + } + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD | + (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), + prefix, argv + i); + pathspec.max_depth = opt.max_depth; + pathspec.recursive = 1; + #ifndef NO_PTHREADS if (list.nr || cached || show_in_pager) num_threads = 0; @@@ -1234,6 -1190,20 +1234,6 @@@ } #endif - /* The rest are paths */ - if (!seen_dashdash) { - int j; - for (j = i; j < argc; j++) - verify_filename(prefix, argv[j], j == i); - } - - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), - prefix, argv + i); - pathspec.max_depth = opt.max_depth; - pathspec.recursive = 1; - if (recurse_submodules) { gitmodules_config(); compile_submodule_options(&opt, &pathspec, cached, untracked, @@@ -1275,6 -1245,8 +1275,6 @@@ if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; - if (list.nr) - die(_("--no-index or --untracked cannot be used with revs.")); hit = grep_directory(&opt, &pathspec, use_exclude, use_index); } else if (0 <= opt_exclude) { die(_("--[no-]exclude-standard cannot be used for tracked contents.")); diff --combined builtin/merge.c index 848a298556,099cfab447..7554b8d412 --- a/builtin/merge.c +++ b/builtin/merge.c @@@ -244,7 -244,7 +244,7 @@@ static void drop_save(void unlink(git_path_merge_mode()); } - static int save_state(unsigned char *stash) + static int save_state(struct object_id *stash) { int len; struct child_process cp = CHILD_PROCESS_INIT; @@@ -265,7 -265,7 +265,7 @@@ else if (!len) /* no changes */ return -1; strbuf_setlen(&buffer, buffer.len-1); - if (get_sha1(buffer.buf, stash)) + if (get_oid(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); return 0; } @@@ -305,18 -305,18 +305,18 @@@ static void reset_hard(unsigned const c die(_("read-tree failed")); } - static void restore_state(const unsigned char *head, - const unsigned char *stash) + static void restore_state(const struct object_id *head, + const struct object_id *stash) { struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; - if (is_null_sha1(stash)) + if (is_null_oid(stash)) return; - reset_hard(head, 1); + reset_hard(head->hash, 1); - args[2] = sha1_to_hex(stash); + args[2] = oid_to_hex(stash); /* * It is OK to ignore error here, for example when there was @@@ -376,10 -376,10 +376,10 @@@ static void squash_message(struct commi static void finish(struct commit *head_commit, struct commit_list *remoteheads, - const unsigned char *new_head, const char *msg) + const struct object_id *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; - const unsigned char *head = head_commit->object.oid.hash; + const struct object_id *head = &head_commit->object.oid; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@@ -397,7 -397,7 +397,7 @@@ else { const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", - new_head, head, 0, + new_head->hash, head->hash, 0, UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the @@@ -416,7 -416,7 +416,7 @@@ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; diff_setup_done(&opts); - diff_tree_sha1(head, new_head, "", &opts); + diff_tree_sha1(head->hash, new_head->hash, "", &opts); diffcore_std(&opts); diff_flush(&opts); } @@@ -431,35 -431,35 +431,35 @@@ static void merge_name(const char *remote, struct strbuf *msg) { struct commit *remote_head; - unsigned char branch_head[20]; + struct object_id branch_head; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; char *found_ref; int len, early; - strbuf_branchname(&bname, remote); + strbuf_branchname(&bname, remote, 0); remote = bname.buf; - memset(branch_head, 0, sizeof(branch_head)); + oidclr(&branch_head); remote_head = get_merge_parent(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); - if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { + if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) { if (starts_with(found_ref, "refs/heads/")) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } if (starts_with(found_ref, "refs/tags/")) { strbuf_addf(msg, "%s\t\ttag '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } if (starts_with(found_ref, "refs/remotes/")) { strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } } @@@ -590,8 -590,8 +590,8 @@@ static int git_merge_config(const char return git_diff_ui_config(k, v, cb); } - static int read_tree_trivial(unsigned char *common, unsigned char *head, - unsigned char *one) + static int read_tree_trivial(struct object_id *common, struct object_id *head, + struct object_id *one) { int i, nr_trees = 0; struct tree *trees[MAX_UNPACK_TREES]; @@@ -606,13 -606,13 +606,13 @@@ opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; - trees[nr_trees] = parse_tree_indirect(common); + trees[nr_trees] = parse_tree_indirect(common->hash); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(head); + trees[nr_trees] = parse_tree_indirect(head->hash); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(one); + trees[nr_trees] = parse_tree_indirect(one->hash); if (!trees[nr_trees++]) return -1; opts.fn = threeway_merge; @@@ -626,9 -626,9 +626,9 @@@ return 0; } - static void write_tree_trivial(unsigned char *sha1) + static void write_tree_trivial(struct object_id *oid) { - if (write_cache_as_tree(sha1, 0, NULL)) + if (write_cache_as_tree(oid->hash, 0, NULL)) die(_("git write-tree failed to write a tree")); } @@@ -781,7 -781,7 +781,7 @@@ static void prepare_to_commit(struct co static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { - unsigned char result_tree[20], result_commit[20]; + struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; static struct lock_file lock; @@@ -792,15 -792,15 +792,15 @@@ return error(_("Unable to write index.")); rollback_lock_file(&lock); - write_tree_trivial(result_tree); + write_tree_trivial(&result_tree); printf(_("Wonderful.\n")); pptr = commit_list_append(head, pptr); pptr = commit_list_append(remoteheads->item, pptr); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, - result_commit, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents, + result_commit.hash, NULL, sign_commit)) die(_("failed to write commit object")); - finish(head, remoteheads, result_commit, "In-index merge"); + finish(head, remoteheads, &result_commit, "In-index merge"); drop_save(); return 0; } @@@ -809,12 -809,12 +809,12 @@@ static int finish_automerge(struct comm int head_subsumed, struct commit_list *common, struct commit_list *remoteheads, - unsigned char *result_tree, + struct object_id *result_tree, const char *wt_strategy) { struct commit_list *parents = NULL; struct strbuf buf = STRBUF_INIT; - unsigned char result_commit[20]; + struct object_id result_commit; free_commit_list(common); parents = remoteheads; @@@ -822,11 -822,11 +822,11 @@@ commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, - result_commit, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents, + result_commit.hash, NULL, sign_commit)) die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); - finish(head, remoteheads, result_commit, buf.buf); + finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; @@@ -854,18 -854,18 +854,18 @@@ static int suggest_conflicts(void } static struct commit *is_old_style_invocation(int argc, const char **argv, - const unsigned char *head) + const struct object_id *head) { struct commit *second_token = NULL; if (argc > 2) { - unsigned char second_sha1[20]; + struct object_id second_oid; - if (get_sha1(argv[1], second_sha1)) + if (get_oid(argv[1], &second_oid)) return NULL; - second_token = lookup_commit_reference_gently(second_sha1, 0); + second_token = lookup_commit_reference_gently(second_oid.hash, 0); if (!second_token) die(_("'%s' is not a commit"), argv[1]); - if (hashcmp(second_token->object.oid.hash, head)) + if (oidcmp(&second_token->object.oid, head)) return NULL; } return second_token; @@@ -1038,7 -1038,7 +1038,7 @@@ static void handle_fetch_head(struct co die_errno(_("could not close '%s'"), filename); for (pos = 0; pos < merge_names->len; pos = npos) { - unsigned char sha1[20]; + struct object_id oid; char *ptr; struct commit *commit; @@@ -1048,16 -1048,16 +1048,16 @@@ else npos = merge_names->len; - if (npos - pos < 40 + 2 || - get_sha1_hex(merge_names->buf + pos, sha1)) + if (npos - pos < GIT_SHA1_HEXSZ + 2 || + get_oid_hex(merge_names->buf + pos, &oid)) commit = NULL; /* bad */ - else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2)) + else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2)) continue; /* not-for-merge */ else { - char saved = merge_names->buf[pos + 40]; - merge_names->buf[pos + 40] = '\0'; + char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ]; + merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0'; commit = get_merge_parent(merge_names->buf + pos); - merge_names->buf[pos + 40] = saved; + merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved; } if (!commit) { if (ptr) @@@ -1117,9 -1117,7 +1117,7 @@@ static struct commit_list *collect_pare int cmd_merge(int argc, const char **argv, const char *prefix) { - unsigned char result_tree[20]; - unsigned char stash[20]; - unsigned char head_sha1[20]; + struct object_id result_tree, stash, head_oid; struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; @@@ -1138,13 -1136,13 +1136,13 @@@ * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL); if (branch && starts_with(branch, "refs/heads/")) branch += 11; - if (!branch || is_null_sha1(head_sha1)) + if (!branch || is_null_oid(&head_oid)) head_commit = NULL; else - head_commit = lookup_commit_or_die(head_sha1, "HEAD"); + head_commit = lookup_commit_or_die(head_oid.hash, "HEAD"); init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@@ -1242,7 -1240,7 +1240,7 @@@ * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ - unsigned char *remote_head_sha1; + struct object_id *remote_head_oid; if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) @@@ -1254,9 -1252,9 +1252,9 @@@ die(_("%s - not something we can merge"), argv[0]); if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); - remote_head_sha1 = remoteheads->item->object.oid.hash; - read_empty(remote_head_sha1, 0); - update_ref("initial pull", "HEAD", remote_head_sha1, + remote_head_oid = &remoteheads->item->object.oid; + read_empty(remote_head_oid->hash, 0); + update_ref("initial pull", "HEAD", remote_head_oid->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; } @@@ -1270,7 -1268,7 +1268,7 @@@ * additional safety measure to check for it. */ if (!have_message && - is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) { + is_old_style_invocation(argc, argv, &head_commit->object.oid)) { warning("old-style 'git merge HEAD ' is deprecated."); strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; @@@ -1422,7 -1420,7 +1420,7 @@@ goto done; } - finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf); + finish(head_commit, remoteheads, &commit->object.oid, msg.buf); drop_save(); goto done; } else if (!remoteheads->next && common->next) @@@ -1441,9 -1439,9 +1439,9 @@@ /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); - if (!read_tree_trivial(common->item->object.oid.hash, - head_commit->object.oid.hash, - remoteheads->item->object.oid.hash)) { + if (!read_tree_trivial(&common->item->object.oid, + &head_commit->object.oid, + &remoteheads->item->object.oid)) { ret = merge_trivial(head_commit, remoteheads); goto done; } @@@ -1495,14 -1493,14 +1493,14 @@@ /* * Stash away the local changes so that we can try more than one. */ - save_state(stash)) - hashclr(stash); + save_state(&stash)) + oidclr(&stash); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@@ -1547,7 -1545,7 +1545,7 @@@ } /* Automerge succeeded. */ - write_tree_trivial(result_tree); + write_tree_trivial(&result_tree); automerge_was_ok = 1; break; } @@@ -1559,7 -1557,7 +1557,7 @@@ if (automerge_was_ok) { ret = finish_automerge(head_commit, head_subsumed, common, remoteheads, - result_tree, wt_strategy); + &result_tree, wt_strategy); goto done; } @@@ -1568,7 -1566,7 +1566,7 @@@ * it up. */ if (!best_strategy) { - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); if (use_strategies_nr > 1) fprintf(stderr, _("No merge strategy handled the merge.\n")); @@@ -1581,7 -1579,7 +1579,7 @@@ ; /* We already have its result in the working tree. */ else { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, diff --combined builtin/notes.c index 4b492abd41,8c569a49a0..0513f7455d --- a/builtin/notes.c +++ b/builtin/notes.c @@@ -681,9 -681,9 +681,9 @@@ static int merge_abort(struct notes_mer * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. */ - if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0)) ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL")); - if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF)) ret += error(_("failed to delete ref NOTES_MERGE_REF")); if (notes_merge_abort(o)) ret += error(_("failed to remove 'git notes merge' worktree")); @@@ -693,7 -693,7 +693,7 @@@ static int merge_commit(struct notes_merge_options *o) { struct strbuf msg = STRBUF_INIT; - unsigned char sha1[20], parent_sha1[20]; + struct object_id oid, parent_oid; struct notes_tree *t; struct commit *partial; struct pretty_print_context pretty_ctx; @@@ -705,27 -705,27 +705,27 @@@ * and target notes ref from .git/NOTES_MERGE_REF. */ - if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + if (get_oid("NOTES_MERGE_PARTIAL", &oid)) die(_("failed to read ref NOTES_MERGE_PARTIAL")); - else if (!(partial = lookup_commit_reference(sha1))) + else if (!(partial = lookup_commit_reference(oid.hash))) die(_("could not find commit from NOTES_MERGE_PARTIAL.")); else if (parse_commit(partial)) die(_("could not parse commit from NOTES_MERGE_PARTIAL.")); if (partial->parents) - hashcpy(parent_sha1, partial->parents->item->object.oid.hash); + oidcpy(&parent_oid, &partial->parents->item->object.oid); else - hashclr(parent_sha1); + oidclr(&parent_oid); t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, oid.hash, NULL); if (!o->local_ref) die(_("failed to resolve NOTES_MERGE_REF")); - if (notes_merge_commit(o, t, partial, sha1)) + if (notes_merge_commit(o, t, partial, oid.hash)) die(_("failed to finalize notes merge")); /* Reuse existing commit message in reflog message */ @@@ -733,8 -733,8 +733,8 @@@ format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); strbuf_insert(&msg, 0, "notes: ", 7); - update_ref(msg.buf, o->local_ref, sha1, - is_null_sha1(parent_sha1) ? NULL : parent_sha1, + update_ref(msg.buf, o->local_ref, oid.hash, + is_null_oid(&parent_oid) ? NULL : parent_oid.hash, 0, UPDATE_REFS_DIE_ON_ERR); free_notes(t); diff --combined builtin/pack-objects.c index f294dcffa9,6e7c3a6575..16517f2637 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@@ -894,15 -894,24 +894,15 @@@ static void write_pack_file(void written, nr_result); } -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]; + static struct attr_check *check; - setup_delta_attr_check(check); - if (git_check_attr(path, ARRAY_SIZE(check), check)) + if (!check) + check = attr_check_initl("delta", NULL); + if (git_check_attr(path, check)) return 0; - if (ATTR_FALSE(check->value)) + if (ATTR_FALSE(check->items[0].value)) return 1; return 0; } @@@ -1530,8 -1539,6 +1530,8 @@@ static int pack_offset_sort(const void * 2. Updating our size/type to the non-delta representation. These were * either not recorded initially (size) or overwritten with the delta type * (type) when check_object() decided to reuse the delta. + * + * 3. Resetting our delta depth, as we are now a base object. */ static void drop_reused_delta(struct object_entry *entry) { @@@ -1545,7 -1552,6 +1545,7 @@@ p = &(*p)->delta_sibling; } entry->delta = NULL; + entry->depth = 0; oi.sizep = &entry->size; oi.typep = &entry->type; @@@ -1564,123 -1570,39 +1564,123 @@@ * Follow the chain of deltas from this entry onward, throwing away any links * that cause us to hit a cycle (as determined by the DFS state flags in * the entries). + * + * We also detect too-long reused chains that would violate our --depth + * limit. */ static void break_delta_chains(struct object_entry *entry) { - /* If it's not a delta, it can't be part of a cycle. */ - if (!entry->delta) { - entry->dfs_state = DFS_DONE; - return; - } + /* + * The actual depth of each object we will write is stored as an int, + * as it cannot exceed our int "depth" limit. But before we break + * changes based no that limit, we may potentially go as deep as the + * number of objects, which is elsewhere bounded to a uint32_t. + */ + uint32_t total_depth; + struct object_entry *cur, *next; + + for (cur = entry, total_depth = 0; + cur; + cur = cur->delta, total_depth++) { + if (cur->dfs_state == DFS_DONE) { + /* + * We've already seen this object and know it isn't + * part of a cycle. We do need to append its depth + * to our count. + */ + total_depth += cur->depth; + break; + } + + /* + * We break cycles before looping, so an ACTIVE state (or any + * other cruft which made its way into the state variable) + * is a bug. + */ + if (cur->dfs_state != DFS_NONE) + die("BUG: confusing delta dfs state in first pass: %d", + cur->dfs_state); - switch (entry->dfs_state) { - case DFS_NONE: /* - * This is the first time we've seen the object. We mark it as - * part of the active potential cycle and recurse. + * Now we know this is the first time we've seen the object. If + * it's not a delta, we're done traversing, but we'll mark it + * done to save time on future traversals. */ - entry->dfs_state = DFS_ACTIVE; - break_delta_chains(entry->delta); - entry->dfs_state = DFS_DONE; - break; + if (!cur->delta) { + cur->dfs_state = DFS_DONE; + break; + } - case DFS_DONE: - /* object already examined, and not part of a cycle */ - break; + /* + * Mark ourselves as active and see if the next step causes + * us to cycle to another active object. It's important to do + * this _before_ we loop, because it impacts where we make the + * cut, and thus how our total_depth counter works. + * E.g., We may see a partial loop like: + * + * A -> B -> C -> D -> B + * + * Cutting B->C breaks the cycle. But now the depth of A is + * only 1, and our total_depth counter is at 3. The size of the + * error is always one less than the size of the cycle we + * broke. Commits C and D were "lost" from A's chain. + * + * If we instead cut D->B, then the depth of A is correct at 3. + * We keep all commits in the chain that we examined. + */ + cur->dfs_state = DFS_ACTIVE; + if (cur->delta->dfs_state == DFS_ACTIVE) { + drop_reused_delta(cur); + cur->dfs_state = DFS_DONE; + break; + } + } + + /* + * And now that we've gone all the way to the bottom of the chain, we + * need to clear the active flags and set the depth fields as + * appropriate. Unlike the loop above, which can quit when it drops a + * delta, we need to keep going to look for more depth cuts. So we need + * an extra "next" pointer to keep going after we reset cur->delta. + */ + for (cur = entry; cur; cur = next) { + next = cur->delta; + + /* + * We should have a chain of zero or more ACTIVE states down to + * a final DONE. We can quit after the DONE, because either it + * has no bases, or we've already handled them in a previous + * call. + */ + if (cur->dfs_state == DFS_DONE) + break; + else if (cur->dfs_state != DFS_ACTIVE) + die("BUG: confusing delta dfs state in second pass: %d", + cur->dfs_state); - case DFS_ACTIVE: /* - * We found a cycle that needs broken. It would be correct to - * break any link in the chain, but it's convenient to - * break this one. + * If the total_depth is more than depth, then we need to snip + * the chain into two or more smaller chains that don't exceed + * the maximum depth. Most of the resulting chains will contain + * (depth + 1) entries (i.e., depth deltas plus one base), and + * the last chain (i.e., the one containing entry) will contain + * whatever entries are left over, namely + * (total_depth % (depth + 1)) of them. + * + * Since we are iterating towards decreasing depth, we need to + * decrement total_depth as we go, and we need to write to the + * entry what its final depth will be after all of the + * snipping. Since we're snipping into chains of length (depth + * + 1) entries, the final depth of an entry will be its + * original depth modulo (depth + 1). Any time we encounter an + * entry whose final depth is supposed to be zero, we snip it + * from its delta base, thereby making it so. */ - drop_reused_delta(entry); - entry->dfs_state = DFS_DONE; - break; + cur->depth = (total_depth--) % (depth + 1); + if (!cur->depth) + drop_reused_delta(cur); + + cur->dfs_state = DFS_DONE; } } @@@ -2612,17 -2534,17 +2612,17 @@@ static void add_objects_in_unpacked_pac free(in_pack.array); } - static int add_loose_object(const unsigned char *sha1, const char *path, + static int add_loose_object(const struct object_id *oid, const char *path, void *data) { - enum object_type type = sha1_object_info(sha1, NULL); + enum object_type type = sha1_object_info(oid->hash, NULL); if (type < 0) { warning("loose object at %s could not be examined", path); return 0; } - add_object_entry(sha1, type, "", 0); + add_object_entry(oid->hash, type, "", 0); return 0; } diff --combined builtin/receive-pack.c index f2c6953a39,7966f4f4df..83492af05f --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@@ -21,7 -21,6 +21,7 @@@ #include "sigchain.h" #include "fsck.h" #include "tmp-objdir.h" +#include "oidset.h" static const char * const receive_pack_usage[] = { N_("git receive-pack "), @@@ -251,9 -250,8 +251,9 @@@ static void show_ref(const char *path, } static int show_ref_cb(const char *path_full, const struct object_id *oid, - int flag, void *unused) + int flag, void *data) { + struct oidset *seen = data; const char *path = strip_namespace(path_full); if (ref_is_hidden(path, path_full)) @@@ -262,38 -260,37 +262,38 @@@ /* * Advertise refs outside our current namespace as ".have" * refs, so that the client can use them to minimize data - * transfer but will otherwise ignore them. This happens to - * cover ".have" that are thrown in by add_one_alternate_ref() - * to mark histories that are complete in our alternates as - * well. + * transfer but will otherwise ignore them. */ - if (!path) + if (!path) { + if (oidset_insert(seen, oid)) + return 0; path = ".have"; + } else { + oidset_insert(seen, oid); + } show_ref(path, oid->hash); return 0; } -static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused) +static void show_one_alternate_ref(const char *refname, + const struct object_id *oid, + void *data) { - show_ref(".have", sha1); - return 0; -} + struct oidset *seen = data; -static void collect_one_alternate_ref(const struct ref *ref, void *data) -{ - struct sha1_array *sa = data; - sha1_array_append(sa, ref->old_oid.hash); + if (oidset_insert(seen, oid)) + return; + + show_ref(".have", oid->hash); } static void write_head_info(void) { - struct sha1_array sa = SHA1_ARRAY_INIT; + static struct oidset seen = OIDSET_INIT; - for_each_alternate_ref(collect_one_alternate_ref, &sa); - sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL); - sha1_array_clear(&sa); - for_each_ref(show_ref_cb, NULL); + for_each_ref(show_ref_cb, &seen); + for_each_alternate_ref(show_one_alternate_ref, &seen); + oidset_clear(&seen); if (!sent_capabilities) show_ref("capabilities^{}", null_sha1); @@@ -1417,7 -1414,7 +1417,7 @@@ static void execute_commands(struct com { struct check_connected_options opt = CHECK_CONNECTED_INIT; struct command *cmd; - unsigned char sha1[20]; + struct object_id oid; struct iterate_data data; struct async muxer; int err_fd = 0; @@@ -1474,7 -1471,7 +1474,7 @@@ check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL); if (use_atomic) execute_commands_atomic(commands, si); @@@ -1667,11 -1664,8 +1667,11 @@@ static const char *unpack(int err_fd, s } tmp_objdir = tmp_objdir_create(); - if (!tmp_objdir) + if (!tmp_objdir) { + if (err_fd > 0) + close(err_fd); return "unable to create temporary object directory"; + } child.env = tmp_objdir_env(tmp_objdir); /* diff --combined builtin/replace.c index 226d0f9523,f7716a5472..f83e7b8fc1 --- a/builtin/replace.c +++ b/builtin/replace.c @@@ -88,78 -88,78 +88,78 @@@ static int list_replace_refs(const cha } typedef int (*each_replace_name_fn)(const char *name, const char *ref, - const unsigned char *sha1); + const struct object_id *oid); static int for_each_replace_name(const char **argv, each_replace_name_fn fn) { const char **p, *full_hex; char ref[PATH_MAX]; int had_error = 0; - unsigned char sha1[20]; + struct object_id oid; for (p = argv; *p; p++) { - if (get_sha1(*p, sha1)) { + if (get_oid(*p, &oid)) { error("Failed to resolve '%s' as a valid ref.", *p); had_error = 1; continue; } - full_hex = sha1_to_hex(sha1); + full_hex = oid_to_hex(&oid); snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex); /* read_ref() may reuse the buffer */ full_hex = ref + strlen(git_replace_ref_base); - if (read_ref(ref, sha1)) { + if (read_ref(ref, oid.hash)) { error("replace ref '%s' not found.", full_hex); had_error = 1; continue; } - if (fn(full_hex, ref, sha1)) + if (fn(full_hex, ref, &oid)) had_error = 1; } return had_error; } static int delete_replace_ref(const char *name, const char *ref, - const unsigned char *sha1) + const struct object_id *oid) { - if (delete_ref(NULL, ref, sha1, 0)) - if (delete_ref(ref, oid->hash, 0)) ++ if (delete_ref(NULL, ref, oid->hash, 0)) return 1; printf("Deleted replace ref '%s'\n", name); return 0; } - static void check_ref_valid(unsigned char object[20], - unsigned char prev[20], + static void check_ref_valid(struct object_id *object, + struct object_id *prev, char *ref, int ref_size, int force) { if (snprintf(ref, ref_size, "%s%s", git_replace_ref_base, - sha1_to_hex(object)) > ref_size - 1) + oid_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); - if (read_ref(ref, prev)) - hashclr(prev); + if (read_ref(ref, prev->hash)) + oidclr(prev); else if (!force) die("replace ref '%s' already exists", ref); } - static int replace_object_sha1(const char *object_ref, - unsigned char object[20], + static int replace_object_oid(const char *object_ref, + struct object_id *object, const char *replace_ref, - unsigned char repl[20], + struct object_id *repl, int force) { - unsigned char prev[20]; + struct object_id prev; enum object_type obj_type, repl_type; char ref[PATH_MAX]; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - obj_type = sha1_object_info(object, NULL); - repl_type = sha1_object_info(repl, NULL); + obj_type = sha1_object_info(object->hash, NULL); + repl_type = sha1_object_info(repl->hash, NULL); if (!force && obj_type != repl_type) die("Objects must be of the same type.\n" "'%s' points to a replaced object of type '%s'\n" @@@ -167,11 -167,11 +167,11 @@@ object_ref, typename(obj_type), replace_ref, typename(repl_type)); - check_ref_valid(object, prev, ref, sizeof(ref), force); + check_ref_valid(object, &prev, ref, sizeof(ref), force); transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref, repl, prev, + ref_transaction_update(transaction, ref, repl->hash, prev.hash, 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); @@@ -182,14 -182,14 +182,14 @@@ static int replace_object(const char *object_ref, const char *replace_ref, int force) { - unsigned char object[20], repl[20]; + struct object_id object, repl; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die("Failed to resolve '%s' as a valid ref.", object_ref); - if (get_sha1(replace_ref, repl)) + if (get_oid(replace_ref, &repl)) die("Failed to resolve '%s' as a valid ref.", replace_ref); - return replace_object_sha1(object_ref, object, replace_ref, repl, force); + return replace_object_oid(object_ref, &object, replace_ref, &repl, force); } /* @@@ -197,7 -197,7 +197,7 @@@ * If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ - static void export_object(const unsigned char *sha1, enum object_type type, + static void export_object(const struct object_id *oid, enum object_type type, int raw, const char *filename) { struct child_process cmd = CHILD_PROCESS_INIT; @@@ -213,7 -213,7 +213,7 @@@ argv_array_push(&cmd.args, typename(type)); else argv_array_push(&cmd.args, "-p"); - argv_array_push(&cmd.args, sha1_to_hex(sha1)); + argv_array_push(&cmd.args, oid_to_hex(oid)); cmd.git_cmd = 1; cmd.out = fd; @@@ -226,7 -226,7 +226,7 @@@ * interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ - static void import_object(unsigned char *sha1, enum object_type type, + static void import_object(struct object_id *oid, enum object_type type, int raw, const char *filename) { int fd; @@@ -254,7 -254,7 +254,7 @@@ if (finish_command(&cmd)) die("mktree reported failure"); - if (get_sha1_hex(result.buf, sha1) < 0) + if (get_oid_hex(result.buf, oid) < 0) die("mktree did not return an object name"); strbuf_release(&result); @@@ -264,7 -264,7 +264,7 @@@ if (fstat(fd, &st) < 0) die_errno("unable to fstat %s", filename); - if (index_fd(sha1, fd, &st, type, NULL, flags) < 0) + if (index_fd(oid->hash, fd, &st, type, NULL, flags) < 0) die("unable to write object to database"); /* index_fd close()s fd for us */ } @@@ -279,29 -279,29 +279,29 @@@ static int edit_and_replace(const char { char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); enum object_type type; - unsigned char old[20], new[20], prev[20]; + struct object_id old, new, prev; char ref[PATH_MAX]; - if (get_sha1(object_ref, old) < 0) + if (get_oid(object_ref, &old) < 0) die("Not a valid object name: '%s'", object_ref); - type = sha1_object_info(old, NULL); + type = sha1_object_info(old.hash, NULL); if (type < 0) - die("unable to get object type for %s", sha1_to_hex(old)); + die("unable to get object type for %s", oid_to_hex(&old)); - check_ref_valid(old, prev, ref, sizeof(ref), force); + check_ref_valid(&old, &prev, ref, sizeof(ref), force); - export_object(old, type, raw, tmpfile); + export_object(&old, type, raw, tmpfile); if (launch_editor(tmpfile, NULL, NULL) < 0) die("editing object file failed"); - import_object(new, type, raw, tmpfile); + import_object(&new, type, raw, tmpfile); free(tmpfile); - if (!hashcmp(old, new)) - return error("new object is the same as the old one: '%s'", sha1_to_hex(old)); + if (!oidcmp(&old, &new)) + return error("new object is the same as the old one: '%s'", oid_to_hex(&old)); - return replace_object_sha1(object_ref, old, "replacement", new, force); + return replace_object_oid(object_ref, &old, "replacement", &new, force); } static void replace_parents(struct strbuf *buf, int argc, const char **argv) @@@ -312,7 -312,7 +312,7 @@@ /* find existing parents */ parent_start = buf->buf; - parent_start += 46; /* "tree " + "hex sha1" + "\n" */ + parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */ parent_end = parent_start; while (starts_with(parent_end, "parent ")) @@@ -320,11 -320,11 +320,11 @@@ /* prepare new parents */ for (i = 0; i < argc; i++) { - unsigned char sha1[20]; - if (get_sha1(argv[i], sha1) < 0) + struct object_id oid; + if (get_oid(argv[i], &oid) < 0) die(_("Not a valid object name: '%s'"), argv[i]); - lookup_commit_or_die(sha1, argv[i]); - strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1)); + lookup_commit_or_die(oid.hash, argv[i]); + strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid)); } /* replace existing parents with new ones */ @@@ -345,12 -345,12 +345,12 @@@ static void check_one_mergetag(struct c { struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data; const char *ref = mergetag_data->argv[0]; - unsigned char tag_sha1[20]; + struct object_id tag_oid; struct tag *tag; int i; - hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1); - tag = lookup_tag(tag_sha1); + hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_oid.hash); + tag = lookup_tag(tag_oid.hash); if (!tag) die(_("bad mergetag in commit '%s'"), ref); if (parse_tag_buffer(tag, extra->value, extra->len)) @@@ -366,7 -366,7 +366,7 @@@ } die(_("original commit '%s' contains mergetag '%s' that is discarded; " - "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1)); + "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid)); } static void check_mergetags(struct commit *commit, int argc, const char **argv) @@@ -380,16 -380,16 +380,16 @@@ static int create_graft(int argc, const char **argv, int force) { - unsigned char old[20], new[20]; + struct object_id old, new; const char *old_ref = argv[0]; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *buffer; unsigned long size; - if (get_sha1(old_ref, old) < 0) + if (get_oid(old_ref, &old) < 0) die(_("Not a valid object name: '%s'"), old_ref); - commit = lookup_commit_or_die(old, old_ref); + commit = lookup_commit_or_die(old.hash, old_ref); buffer = get_commit_buffer(commit, &size); strbuf_add(&buf, buffer, size); @@@ -404,15 -404,15 +404,15 @@@ check_mergetags(commit, argc, argv); - if (write_sha1_file(buf.buf, buf.len, commit_type, new)) + if (write_sha1_file(buf.buf, buf.len, commit_type, new.hash)) die(_("could not write replacement commit for: '%s'"), old_ref); strbuf_release(&buf); - if (!hashcmp(old, new)) - return error("new commit is the same as the old one: '%s'", sha1_to_hex(old)); + if (!oidcmp(&old, &new)) + return error("new commit is the same as the old one: '%s'", oid_to_hex(&old)); - return replace_object_sha1(old_ref, old, "replacement", new, force); + return replace_object_oid(old_ref, &old, "replacement", &new, force); } int cmd_replace(int argc, const char **argv, const char *prefix) diff --combined cache.h index d3265cca12,724e905f7a..dc261f40dd --- a/cache.h +++ b/cache.h @@@ -1045,6 -1045,9 +1045,6 @@@ static inline int is_empty_tree_oid(con return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN); } - -int git_mkstemp(char *path, size_t n, const char *template); - /* set default permissions by passing mode arguments to open(2) */ int git_mkstemps_mode(char *pattern, int suffix_len, int mode); int git_mkstemp_mode(char *pattern, int mode); @@@ -1069,9 -1072,8 +1069,9 @@@ int adjust_shared_perm(const char *path /* * Create the directory containing the named path, using care to be - * somewhat safe against races. Return one of the scld_error values - * to indicate success/failure. + * somewhat safe against races. Return one of the scld_error values to + * indicate success/failure. On error, set errno to describe the + * problem. * * SCLD_VANISHED indicates that one of the ancestor directories of the * path existed at one point during the function call and then @@@ -1095,49 -1097,6 +1095,49 @@@ enum scld_error enum scld_error safe_create_leading_directories(char *path); enum scld_error safe_create_leading_directories_const(const char *path); +/* + * Callback function for raceproof_create_file(). This function is + * expected to do something that makes dirname(path) permanent despite + * the fact that other processes might be cleaning up empty + * directories at the same time. Usually it will create a file named + * path, but alternatively it could create another file in that + * directory, or even chdir() into that directory. The function should + * return 0 if the action was completed successfully. On error, it + * should return a nonzero result and set errno. + * raceproof_create_file() treats two errno values specially: + * + * - ENOENT -- dirname(path) does not exist. In this case, + * raceproof_create_file() tries creating dirname(path) + * (and any parent directories, if necessary) and calls + * the function again. + * + * - EISDIR -- the file already exists and is a directory. In this + * case, raceproof_create_file() removes the directory if + * it is empty (and recursively any empty directories that + * it contains) and calls the function again. + * + * Any other errno causes raceproof_create_file() to fail with the + * callback's return value and errno. + * + * Obviously, this function should be OK with being called again if it + * fails with ENOENT or EISDIR. In other scenarios it will not be + * called again. + */ +typedef int create_file_fn(const char *path, void *cb); + +/* + * Create a file in dirname(path) by calling fn, creating leading + * directories if necessary. Retry a few times in case we are racing + * with another process that is trying to clean up the directory that + * contains path. See the documentation for create_file_fn for more + * details. + * + * Return the value and set the errno that resulted from the most + * recent call of fn. fn is always called at least once, and will be + * called more than once if it returns ENOENT or EISDIR. + */ +int raceproof_create_file(const char *path, create_file_fn fn, void *cb); + int mkdir_in_gitdir(const char *path); extern char *expand_user_path(const char *path); const char *enter_repo(const char *path, int strict); @@@ -1150,7 -1109,7 +1150,7 @@@ char *strbuf_realpath(struct strbuf *re int die_on_error); const char *real_path(const char *path); const char *real_path_if_valid(const char *path); -char *real_pathdup(const char *path); +char *real_pathdup(const char *path, int die_on_error); const char *absolute_path(const char *path); char *absolute_pathdup(const char *path); const char *remove_leading_path(const char *in, const char *prefix); @@@ -1270,9 -1229,6 +1270,9 @@@ extern int has_pack_index(const unsigne extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect); +/* Helper to check and "touch" a file */ +extern int check_and_freshen_file(const char *fn, int freshen); + extern const signed char hexval_table[256]; static inline unsigned int hexval(unsigned char c) { @@@ -1363,37 -1319,16 +1363,46 @@@ extern char *oid_to_hex_r(char *out, co extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */ + /* + * Parse a 40-character hexadecimal object ID starting from hex, updating the + * pointer specified by end when parsing stops. The resulting object ID is + * stored in oid. Returns 0 on success. Parsing will stop on the first NUL or + * other invalid character. end is only updated on success; otherwise, it is + * unmodified. + */ + extern int parse_oid_hex(const char *hex, struct object_id *oid, const char **end); + -extern int interpret_branch_name(const char *str, int len, struct strbuf *); +/* + * This reads short-hand syntax that not only evaluates to a commit + * object name, but also can act as if the end user spelled the name + * of the branch from the command line. + * + * - "@{-N}" finds the name of the Nth previous branch we were on, and + * places the name of the branch in the given buf and returns the + * number of characters parsed if successful. + * + * - "@{upstream}" finds the name of the other ref that + * is configured to merge with (missing defaults + * to the current branch), and places the name of the branch in the + * given buf and returns the number of characters parsed if + * successful. + * + * If the input is not of the accepted format, it returns a negative + * number to signal an error. + * + * If the input was ok but there are not N branch switches in the + * reflog, it returns 0. + * + * If "allowed" is non-zero, it is a treated as a bitfield of allowable + * expansions: local branches ("refs/heads/"), remote branches + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is + * allowed, even ones to refs outside of those namespaces. + */ +#define INTERPRET_BRANCH_LOCAL (1<<0) +#define INTERPRET_BRANCH_REMOTE (1<<1) +#define INTERPRET_BRANCH_HEAD (1<<2) +extern int interpret_branch_name(const char *str, int len, struct strbuf *, + unsigned allowed); extern int get_oid_mb(const char *str, struct object_id *oid); extern int validate_headref(const char *ref); @@@ -1673,6 -1608,12 +1682,12 @@@ extern void check_pack_index_ptr(const * error. */ extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t n); + /* + * Like nth_packed_object_sha1, but write the data into the object specified by + * the the first argument. Returns the first argument on success, and NULL on + * error. + */ + extern const struct object_id *nth_packed_object_oid(struct object_id *, struct packed_git *, uint32_t n); /* * Return the offset of the nth object within the specified packfile. @@@ -1714,7 -1655,7 +1729,7 @@@ extern int unpack_object_header(struct * scratch buffer, but restored to its original contents before * the function returns. */ - typedef int each_loose_object_fn(const unsigned char *sha1, + typedef int each_loose_object_fn(const struct object_id *oid, const char *path, void *data); typedef int each_loose_cruft_fn(const char *basename, @@@ -1740,7 -1681,7 +1755,7 @@@ int for_each_loose_file_in_objdir_buf(s * LOCAL_ONLY flag is set). */ #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1 - typedef int each_packed_object_fn(const unsigned char *sha1, + typedef int each_packed_object_fn(const struct object_id *oid, struct packed_git *pack, uint32_t pos, void *data); @@@ -1893,11 -1834,8 +1908,11 @@@ extern int git_config_include(const cha * * (i.e., what gets handed to a config_fn_t). The caller provides the section; * we return -1 if it does not match, 0 otherwise. The subsection and key - * out-parameters are filled by the function (and subsection is NULL if it is + * out-parameters are filled by the function (and *subsection is NULL if it is * missing). + * + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if + * there is no subsection at all. */ extern int parse_config_key(const char *var, const char *section, @@@ -1959,11 -1897,6 +1974,11 @@@ extern int git_config_get_bool_or_int(c extern int git_config_get_maybe_bool(const char *key, int *dest); extern int git_config_get_pathname(const char *key, const char **dest); extern int git_config_get_untracked_cache(void); +extern int git_config_get_split_index(void); +extern int git_config_get_max_percent_split_change(void); + +/* This dies if the configured or default date is in the future */ +extern int git_config_get_expiry(const char *key, const char **output); /* * This is a hack for test programs like test-dump-untracked-cache to diff --combined ref-filter.c index 1ec0fb8391,f0de30e2ef..89798dc59b --- a/ref-filter.c +++ b/ref-filter.c @@@ -14,50 -14,14 +14,50 @@@ #include "git-compat-util.h" #include "version.h" #include "trailer.h" +#include "wt-status.h" + +static struct ref_msg { + const char *gone; + const char *ahead; + const char *behind; + const char *ahead_behind; +} msgs = { + /* Untranslated plumbing messages: */ + "gone", + "ahead %d", + "behind %d", + "ahead %d, behind %d" +}; + +void setup_ref_filter_porcelain_msg(void) +{ + msgs.gone = _("gone"); + msgs.ahead = _("ahead %d"); + msgs.behind = _("behind %d"); + msgs.ahead_behind = _("ahead %d, behind %d"); +} typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; +typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status; struct align { align_type position; unsigned int width; }; +struct if_then_else { + cmp_status cmp_status; + const char *str; + unsigned int then_atom_seen : 1, + else_atom_seen : 1, + condition_satisfied : 1; +}; + +struct refname_atom { + enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option; + int lstrip, rstrip; +}; + /* * An atom is a valid field atom listed below, possibly prefixed with * a "*" to denote deref_tag(). @@@ -74,24 -38,13 +74,24 @@@ static struct used_atom union { char color[COLOR_MAXLEN]; struct align align; - enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT } - remote_ref; + struct { + enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option; + struct refname_atom refname; + unsigned int nobracket : 1; + } remote_ref; struct { enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option; unsigned int nlines; } contents; - enum { O_FULL, O_SHORT } objectname; + struct { + cmp_status cmp_status; + const char *str; + } if_then_else; + struct { + enum { O_FULL, O_LENGTH, O_SHORT } option; + unsigned int length; + } objectname; + struct refname_atom refname; } u; } *used_atom; static int used_atom_cnt, need_tagged, need_symref; @@@ -105,58 -58,18 +105,58 @@@ static void color_atom_parser(struct us die(_("unrecognized color: %%(color:%s)"), color_value); } -static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) +static void refname_atom_parser_internal(struct refname_atom *atom, + const char *arg, const char *name) { if (!arg) - atom->u.remote_ref = RR_NORMAL; + atom->option = R_NORMAL; else if (!strcmp(arg, "short")) - atom->u.remote_ref = RR_SHORTEN; - else if (!strcmp(arg, "track")) - atom->u.remote_ref = RR_TRACK; - else if (!strcmp(arg, "trackshort")) - atom->u.remote_ref = RR_TRACKSHORT; - else - die(_("unrecognized format: %%(%s)"), atom->name); + atom->option = R_SHORT; + else if (skip_prefix(arg, "lstrip=", &arg) || + skip_prefix(arg, "strip=", &arg)) { + atom->option = R_LSTRIP; + if (strtol_i(arg, 10, &atom->lstrip)) + die(_("Integer value expected refname:lstrip=%s"), arg); + } else if (skip_prefix(arg, "rstrip=", &arg)) { + atom->option = R_RSTRIP; + if (strtol_i(arg, 10, &atom->rstrip)) + die(_("Integer value expected refname:rstrip=%s"), arg); + } else + die(_("unrecognized %%(%s) argument: %s"), name, arg); +} + +static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) +{ + struct string_list params = STRING_LIST_INIT_DUP; + int i; + + if (!arg) { + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); + return; + } + + atom->u.remote_ref.nobracket = 0; + string_list_split(¶ms, arg, ',', -1); + + for (i = 0; i < params.nr; i++) { + const char *s = params.items[i].string; + + if (!strcmp(s, "track")) + atom->u.remote_ref.option = RR_TRACK; + else if (!strcmp(s, "trackshort")) + atom->u.remote_ref.option = RR_TRACKSHORT; + else if (!strcmp(s, "nobracket")) + atom->u.remote_ref.nobracket = 1; + else { + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); + } + } + + string_list_clear(¶ms, 0); } static void body_atom_parser(struct used_atom *atom, const char *arg) @@@ -203,25 -116,13 +203,25 @@@ static void contents_atom_parser(struc static void objectname_atom_parser(struct used_atom *atom, const char *arg) { if (!arg) - atom->u.objectname = O_FULL; + atom->u.objectname.option = O_FULL; else if (!strcmp(arg, "short")) - atom->u.objectname = O_SHORT; - else + atom->u.objectname.option = O_SHORT; + else if (skip_prefix(arg, "short=", &arg)) { + atom->u.objectname.option = O_LENGTH; + if (strtoul_ui(arg, 10, &atom->u.objectname.length) || + atom->u.objectname.length == 0) + die(_("positive value expected objectname:short=%s"), arg); + if (atom->u.objectname.length < MINIMUM_ABBREV) + atom->u.objectname.length = MINIMUM_ABBREV; + } else die(_("unrecognized %%(objectname) argument: %s"), arg); } +static void refname_atom_parser(struct used_atom *atom, const char *arg) +{ + return refname_atom_parser_internal(&atom->u.refname, arg, atom->name); +} + static align_type parse_align_position(const char *s) { if (!strcmp(s, "right")) @@@ -272,27 -173,12 +272,27 @@@ static void align_atom_parser(struct us string_list_clear(¶ms, 0); } +static void if_atom_parser(struct used_atom *atom, const char *arg) +{ + if (!arg) { + atom->u.if_then_else.cmp_status = COMPARE_NONE; + return; + } else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) { + atom->u.if_then_else.cmp_status = COMPARE_EQUAL; + } else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) { + atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL; + } else { + die(_("unrecognized %%(if) argument: %s"), arg); + } +} + + static struct { const char *name; cmp_type cmp_type; void (*parser)(struct used_atom *atom, const char *arg); } valid_atom[] = { - { "refname" }, + { "refname" , FIELD_STR, refname_atom_parser }, { "objecttype" }, { "objectsize", FIELD_ULONG }, { "objectname", FIELD_STR, objectname_atom_parser }, @@@ -322,15 -208,12 +322,15 @@@ { "contents", FIELD_STR, contents_atom_parser }, { "upstream", FIELD_STR, remote_ref_atom_parser }, { "push", FIELD_STR, remote_ref_atom_parser }, - { "symref" }, + { "symref", FIELD_STR, refname_atom_parser }, { "flag" }, { "HEAD" }, { "color", FIELD_STR, color_atom_parser }, { "align", FIELD_STR, align_atom_parser }, { "end" }, + { "if", FIELD_STR, if_atom_parser }, + { "then" }, + { "else" }, }; #define REF_FORMATTING_STATE_INIT { 0, NULL } @@@ -338,7 -221,7 +338,7 @@@ struct ref_formatting_stack { struct ref_formatting_stack *prev; struct strbuf output; - void (*at_end)(struct ref_formatting_stack *stack); + void (*at_end)(struct ref_formatting_stack **stack); void *at_end_data; }; @@@ -349,9 -232,11 +349,9 @@@ struct ref_formatting_state struct atom_value { const char *s; - union { - struct align align; - } u; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ + struct used_atom *atom; }; /* @@@ -408,7 -293,7 +408,7 @@@ int parse_ref_filter_atom(const char *a valid_atom[i].parser(&used_atom[at], arg); if (*atom == '*') need_tagged = 1; - if (!strcmp(used_atom[at].name, "symref")) + if (!strcmp(valid_atom[i].name, "symref")) need_symref = 1; return at; } @@@ -469,14 -354,13 +469,14 @@@ static void pop_stack_element(struct re *stack = prev; } -static void end_align_handler(struct ref_formatting_stack *stack) +static void end_align_handler(struct ref_formatting_stack **stack) { - struct align *align = (struct align *)stack->at_end_data; + struct ref_formatting_stack *cur = *stack; + struct align *align = (struct align *)cur->at_end_data; struct strbuf s = STRBUF_INIT; - strbuf_utf8_align(&s, align->position, align->width, stack->output.buf); - strbuf_swap(&stack->output, &s); + strbuf_utf8_align(&s, align->position, align->width, cur->output.buf); + strbuf_swap(&cur->output, &s); strbuf_release(&s); } @@@ -487,115 -371,7 +487,115 @@@ static void align_atom_handler(struct a push_stack_element(&state->stack); new = state->stack; new->at_end = end_align_handler; - new->at_end_data = &atomv->u.align; + new->at_end_data = &atomv->atom->u.align; +} + +static void if_then_else_handler(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *cur = *stack; + struct ref_formatting_stack *prev = cur->prev; + struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data; + + if (!if_then_else->then_atom_seen) + die(_("format: %%(if) atom used without a %%(then) atom")); + + if (if_then_else->else_atom_seen) { + /* + * There is an %(else) atom: we need to drop one state from the + * stack, either the %(else) branch if the condition is satisfied, or + * the %(then) branch if it isn't. + */ + if (if_then_else->condition_satisfied) { + strbuf_reset(&cur->output); + pop_stack_element(&cur); + } else { + strbuf_swap(&cur->output, &prev->output); + strbuf_reset(&cur->output); + pop_stack_element(&cur); + } + } else if (!if_then_else->condition_satisfied) { + /* + * No %(else) atom: just drop the %(then) branch if the + * condition is not satisfied. + */ + strbuf_reset(&cur->output); + } + + *stack = cur; + free(if_then_else); +} + +static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *new; + struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1); + + if_then_else->str = atomv->atom->u.if_then_else.str; + if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status; + + push_stack_element(&state->stack); + new = state->stack; + new->at_end = if_then_else_handler; + new->at_end_data = if_then_else; +} + +static int is_empty(const char *s) +{ + while (*s != '\0') { + if (!isspace(*s)) + return 0; + s++; + } + return 1; +} + +static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *cur = state->stack; + struct if_then_else *if_then_else = NULL; + + if (cur->at_end == if_then_else_handler) + if_then_else = (struct if_then_else *)cur->at_end_data; + if (!if_then_else) + die(_("format: %%(then) atom used without an %%(if) atom")); + if (if_then_else->then_atom_seen) + die(_("format: %%(then) atom used more than once")); + if (if_then_else->else_atom_seen) + die(_("format: %%(then) atom used after %%(else)")); + if_then_else->then_atom_seen = 1; + /* + * If the 'equals' or 'notequals' attribute is used then + * perform the required comparison. If not, only non-empty + * strings satisfy the 'if' condition. + */ + if (if_then_else->cmp_status == COMPARE_EQUAL) { + if (!strcmp(if_then_else->str, cur->output.buf)) + if_then_else->condition_satisfied = 1; + } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) { + if (strcmp(if_then_else->str, cur->output.buf)) + if_then_else->condition_satisfied = 1; + } else if (cur->output.len && !is_empty(cur->output.buf)) + if_then_else->condition_satisfied = 1; + strbuf_reset(&cur->output); +} + +static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *prev = state->stack; + struct if_then_else *if_then_else = NULL; + + if (prev->at_end == if_then_else_handler) + if_then_else = (struct if_then_else *)prev->at_end_data; + if (!if_then_else) + die(_("format: %%(else) atom used without an %%(if) atom")); + if (!if_then_else->then_atom_seen) + die(_("format: %%(else) atom used without a %%(then) atom")); + if (if_then_else->else_atom_seen) + die(_("format: %%(else) atom used more than once")); + if_then_else->else_atom_seen = 1; + push_stack_element(&state->stack); + state->stack->at_end_data = prev->at_end_data; + state->stack->at_end = prev->at_end; } static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) @@@ -605,17 -381,14 +605,17 @@@ if (!current->at_end) die(_("format: %%(end) atom used without corresponding atom")); - current->at_end(current); + current->at_end(&state->stack); + + /* Stack may have been popped within at_end(), hence reset the current pointer */ + current = state->stack; /* * Perform quote formatting when the stack element is that of * a supporting atom. If nested then perform quote formatting * only on the topmost supporting atom. */ - if (!state->stack->prev->prev) { + if (!current->prev->prev) { quote_formatting(&s, current->output.buf, state->quote_style); strbuf_swap(¤t->output, &s); } @@@ -692,15 -465,12 +692,15 @@@ static int grab_objectname(const char * struct atom_value *v, struct used_atom *atom) { if (starts_with(name, "objectname")) { - if (atom->u.objectname == O_SHORT) { + if (atom->u.objectname.option == O_SHORT) { v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 1; - } else if (atom->u.objectname == O_FULL) { + } else if (atom->u.objectname.option == O_FULL) { v->s = xstrdup(sha1_to_hex(sha1)); return 1; + } else if (atom->u.objectname.option == O_LENGTH) { + v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length)); + return 1; } else die("BUG: unknown %%(objectname) option"); } @@@ -1117,108 -887,50 +1117,108 @@@ static inline char *copy_advance(char * return dst; } -static const char *strip_ref_components(const char *refname, const char *nr_arg) +static const char *lstrip_ref_components(const char *refname, int len) { - char *end; - long nr = strtol(nr_arg, &end, 10); - long remaining = nr; + long remaining = len; const char *start = refname; - if (nr < 1 || *end != '\0') - die(_(":strip= requires a positive integer argument")); + if (len < 0) { + int i; + const char *p = refname; + + /* Find total no of '/' separated path-components */ + for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) + ; + /* + * The number of components we need to strip is now + * the total minus the components to be left (Plus one + * because we count the number of '/', but the number + * of components is one more than the no of '/'). + */ + remaining = i + len + 1; + } - while (remaining) { + while (remaining > 0) { switch (*start++) { case '\0': - die(_("ref '%s' does not have %ld components to :strip"), - refname, nr); + return ""; case '/': remaining--; break; } } + return start; } +static const char *rstrip_ref_components(const char *refname, int len) +{ + long remaining = len; + char *start = xstrdup(refname); + + if (len < 0) { + int i; + const char *p = refname; + + /* Find total no of '/' separated path-components */ + for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) + ; + /* + * The number of components we need to strip is now + * the total minus the components to be left (Plus one + * because we count the number of '/', but the number + * of components is one more than the no of '/'). + */ + remaining = i + len + 1; + } + + while (remaining-- > 0) { + char *p = strrchr(start, '/'); + if (p == NULL) + return ""; + else + p[0] = '\0'; + } + return start; +} + +static const char *show_ref(struct refname_atom *atom, const char *refname) +{ + if (atom->option == R_SHORT) + return shorten_unambiguous_ref(refname, warn_ambiguous_refs); + else if (atom->option == R_LSTRIP) + return lstrip_ref_components(refname, atom->lstrip); + else if (atom->option == R_RSTRIP) + return rstrip_ref_components(refname, atom->rstrip); + else + return refname; +} + static void fill_remote_ref_details(struct used_atom *atom, const char *refname, struct branch *branch, const char **s) { int num_ours, num_theirs; - if (atom->u.remote_ref == RR_SHORTEN) - *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs); - else if (atom->u.remote_ref == RR_TRACK) { + if (atom->u.remote_ref.option == RR_REF) + *s = show_ref(&atom->u.remote_ref.refname, refname); + else if (atom->u.remote_ref.option == RR_TRACK) { if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - return; - - if (!num_ours && !num_theirs) + &num_theirs, NULL)) { + *s = xstrdup(msgs.gone); + } else if (!num_ours && !num_theirs) *s = ""; else if (!num_ours) - *s = xstrfmt("[behind %d]", num_theirs); + *s = xstrfmt(msgs.behind, num_theirs); else if (!num_theirs) - *s = xstrfmt("[ahead %d]", num_ours); + *s = xstrfmt(msgs.ahead, num_ours); else - *s = xstrfmt("[ahead %d, behind %d]", + *s = xstrfmt(msgs.ahead_behind, num_ours, num_theirs); - } else if (atom->u.remote_ref == RR_TRACKSHORT) { + if (!atom->u.remote_ref.nobracket && *s[0]) { + const char *to_free = *s; + *s = xstrfmt("[%s]", *s); + free((void *)to_free); + } + } else if (atom->u.remote_ref.option == RR_TRACKSHORT) { if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) return; @@@ -1231,56 -943,8 +1231,56 @@@ *s = ">"; else *s = "<>"; - } else /* RR_NORMAL */ - *s = refname; + } else + die("BUG: unhandled RR_* enum"); +} + +char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) { + if (state.detached_at) + /* TRANSLATORS: make sure this matches + "HEAD detached at " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + /* TRANSLATORS: make sure this matches + "HEAD detached from " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} + +static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref) +{ + if (!ref->symref) + return ""; + else + return show_ref(&atom->u.refname, ref->symref); +} + +static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref) +{ + if (ref->kind & FILTER_REFS_DETACHED_HEAD) + return get_head_description(); + return show_ref(&atom->u.refname, ref->refname); } /* @@@ -1297,9 -961,9 +1297,9 @@@ static void populate_value(struct ref_a ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { - unsigned char unused1[20]; + struct object_id unused1; ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, - unused1, NULL); + unused1.hash, NULL); if (!ref->symref) ref->symref = ""; } @@@ -1311,10 -975,10 +1311,10 @@@ struct atom_value *v = &ref->value[i]; int deref = 0; const char *refname; - const char *formatp; struct branch *branch = NULL; v->handler = append_atom; + v->atom = atom; if (*name == '*') { deref = 1; @@@ -1322,9 -986,9 +1322,9 @@@ } if (starts_with(name, "refname")) - refname = ref->refname; + refname = get_refname(atom, ref); else if (starts_with(name, "symref")) - refname = ref->symref ? ref->symref : ""; + refname = get_symref(atom, ref); else if (starts_with(name, "upstream")) { const char *branch_name; /* only local branches may have an upstream */ @@@ -1379,27 -1043,30 +1379,27 @@@ v->s = " "; continue; } else if (starts_with(name, "align")) { - v->u.align = atom->u.align; v->handler = align_atom_handler; continue; } else if (!strcmp(name, "end")) { v->handler = end_atom_handler; continue; + } else if (starts_with(name, "if")) { + const char *s; + + if (skip_prefix(name, "if:", &s)) + v->s = xstrdup(s); + v->handler = if_atom_handler; + continue; + } else if (!strcmp(name, "then")) { + v->handler = then_atom_handler; + continue; + } else if (!strcmp(name, "else")) { + v->handler = else_atom_handler; + continue; } else continue; - formatp = strchr(name, ':'); - if (formatp) { - const char *arg; - - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else if (skip_prefix(formatp, "strip=", &arg)) - refname = strip_ref_components(refname, arg); - else - die(_("unknown %.*s format %s"), - (int)(formatp - name), name, formatp); - } - if (!deref) v->s = refname; else @@@ -1968,10 -1635,10 +1968,10 @@@ static void append_literal(const char * } } -void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) +void format_ref_array_item(struct ref_array_item *info, const char *format, + int quote_style, struct strbuf *final_buf) { const char *cp, *sp, *ep; - struct strbuf *final_buf; struct ref_formatting_state state = REF_FORMATTING_STATE_INIT; state.quote_style = quote_style; @@@ -2001,17 -1668,9 +2001,17 @@@ } if (state.stack->prev) die(_("format: %%(end) atom missing")); - final_buf = &state.stack->output; - fwrite(final_buf->buf, 1, final_buf->len, stdout); + strbuf_addbuf(final_buf, &state.stack->output); pop_stack_element(&state.stack); +} + +void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) +{ + struct strbuf final_buf = STRBUF_INIT; + + format_ref_array_item(info, format, quote_style, &final_buf); + fwrite(final_buf.buf, 1, final_buf.len, stdout); + strbuf_release(&final_buf); putchar('\n'); } diff --combined refs.c index b0d58948a5,b900626d3f..e7606716dd --- a/refs.c +++ b/refs.c @@@ -3,7 -3,6 +3,7 @@@ */ #include "cache.h" +#include "hashmap.h" #include "lockfile.h" #include "refs.h" #include "refs/refs-internal.h" @@@ -405,7 -404,7 +405,7 @@@ int refname_match(const char *abbrev_na static char *substitute_branch_name(const char **string, int *len) { struct strbuf buf = STRBUF_INIT; - int ret = interpret_branch_name(*string, *len, &buf); + int ret = interpret_branch_name(*string, *len, &buf, 0); if (ret == *len) { size_t size; @@@ -592,8 -591,8 +592,8 @@@ static int delete_pseudoref(const char return 0; } -int delete_ref(const char *refname, const unsigned char *old_sha1, - unsigned int flags) +int delete_ref(const char *msg, const char *refname, + const unsigned char *old_sha1, unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; @@@ -604,7 -603,7 +604,7 @@@ transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, old_sha1, - flags, NULL, &err) || + flags, msg, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); @@@ -675,7 -674,7 +675,7 @@@ struct read_ref_at_cb int *cutoff_cnt; }; - static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1, + static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@@ -699,30 -698,30 +699,30 @@@ * hold the values for the previous record. */ if (!is_null_sha1(cb->osha1)) { - hashcpy(cb->sha1, nsha1); - if (hashcmp(cb->osha1, nsha1)) + hashcpy(cb->sha1, noid->hash); + if (hashcmp(cb->osha1, noid->hash)) warning("Log for ref %s has gap after %s.", cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); } else if (cb->date == cb->at_time) - hashcpy(cb->sha1, nsha1); - else if (hashcmp(nsha1, cb->sha1)) + hashcpy(cb->sha1, noid->hash); + else if (hashcmp(noid->hash, cb->sha1)) warning("Log for ref %s unexpectedly ended on %s.", cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); - hashcpy(cb->osha1, osha1); - hashcpy(cb->nsha1, nsha1); + hashcpy(cb->osha1, ooid->hash); + hashcpy(cb->nsha1, noid->hash); cb->found_it = 1; return 1; } - hashcpy(cb->osha1, osha1); - hashcpy(cb->nsha1, nsha1); + hashcpy(cb->osha1, ooid->hash); + hashcpy(cb->nsha1, noid->hash); if (cb->cnt > 0) cb->cnt--; return 0; } - static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, + static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@@ -736,9 -735,9 +736,9 @@@ *cb->cutoff_tz = tz; if (cb->cutoff_cnt) *cb->cutoff_cnt = cb->reccnt; - hashcpy(cb->sha1, osha1); + hashcpy(cb->sha1, ooid->hash); if (is_null_sha1(cb->sha1)) - hashcpy(cb->sha1, nsha1); + hashcpy(cb->sha1, noid->hash); /* We just want the first entry */ return 1; } @@@ -1035,10 -1034,10 +1035,10 @@@ static struct string_list *hide_refs int parse_hide_refs_config(const char *var, const char *value, const char *section) { + const char *key; if (!strcmp("transfer.hiderefs", var) || - /* NEEDSWORK: use parse_config_key() once both are merged */ - (starts_with(var, section) && var[strlen(section)] == '.' && - !strcmp(var + strlen(section), ".hiderefs"))) { + (!parse_config_key(var, section, NULL, NULL, &key) && + !strcmp(key, "hiderefs"))) { char *ref; int len; @@@ -1235,10 -1234,10 +1235,10 @@@ int for_each_rawref(each_ref_fn fn, voi } /* This function needs to return a meaningful errno on failure */ -static const char *resolve_ref_recursively(struct ref_store *refs, - const char *refname, - int resolve_flags, - unsigned char *sha1, int *flags) +const char *resolve_ref_recursively(struct ref_store *refs, + const char *refname, + int resolve_flags, + unsigned char *sha1, int *flags) { static struct strbuf sb_refname = STRBUF_INIT; int unused_flags; @@@ -1358,102 -1357,62 +1358,102 @@@ int resolve_gitlink_ref(const char *sub return 0; } +struct submodule_hash_entry +{ + struct hashmap_entry ent; /* must be the first member! */ + + struct ref_store *refs; + + /* NUL-terminated name of submodule: */ + char submodule[FLEX_ARRAY]; +}; + +static int submodule_hash_cmp(const void *entry, const void *entry_or_key, + const void *keydata) +{ + const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key; + const char *submodule = keydata ? keydata : e2->submodule; + + return strcmp(e1->submodule, submodule); +} + +static struct submodule_hash_entry *alloc_submodule_hash_entry( + const char *submodule, struct ref_store *refs) +{ + struct submodule_hash_entry *entry; + + FLEX_ALLOC_STR(entry, submodule, submodule); + hashmap_entry_init(entry, strhash(submodule)); + entry->refs = refs; + return entry; +} + /* A pointer to the ref_store for the main repository: */ static struct ref_store *main_ref_store; -/* A linked list of ref_stores for submodules: */ -static struct ref_store *submodule_ref_stores; +/* A hashmap of ref_stores, stored by submodule name: */ +static struct hashmap submodule_ref_stores; -void base_ref_store_init(struct ref_store *refs, - const struct ref_storage_be *be, - const char *submodule) +/* + * Return the ref_store instance for the specified submodule (or the + * main repository if submodule is NULL). If that ref_store hasn't + * been initialized yet, return NULL. + */ +static struct ref_store *lookup_ref_store(const char *submodule) +{ + struct submodule_hash_entry *entry; + + if (!submodule) + return main_ref_store; + + if (!submodule_ref_stores.tablesize) + /* It's initialized on demand in register_ref_store(). */ + return NULL; + + entry = hashmap_get_from_hash(&submodule_ref_stores, + strhash(submodule), submodule); + return entry ? entry->refs : NULL; +} + +/* + * Register the specified ref_store to be the one that should be used + * for submodule (or the main repository if submodule is NULL). It is + * a fatal error to call this function twice for the same submodule. + */ +static void register_ref_store(struct ref_store *refs, const char *submodule) { - refs->be = be; if (!submodule) { if (main_ref_store) die("BUG: main_ref_store initialized twice"); - refs->submodule = ""; - refs->next = NULL; main_ref_store = refs; } else { - if (lookup_ref_store(submodule)) + if (!submodule_ref_stores.tablesize) + hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0); + + if (hashmap_put(&submodule_ref_stores, + alloc_submodule_hash_entry(submodule, refs))) die("BUG: ref_store for submodule '%s' initialized twice", submodule); - - refs->submodule = xstrdup(submodule); - refs->next = submodule_ref_stores; - submodule_ref_stores = refs; } } -struct ref_store *ref_store_init(const char *submodule) +/* + * Create, record, and return a ref_store instance for the specified + * submodule (or the main repository if submodule is NULL). + */ +static struct ref_store *ref_store_init(const char *submodule) { const char *be_name = "files"; struct ref_storage_be *be = find_ref_storage_backend(be_name); + struct ref_store *refs; if (!be) die("BUG: reference backend %s is unknown", be_name); - if (!submodule || !*submodule) - return be->init(NULL); - else - return be->init(submodule); -} - -struct ref_store *lookup_ref_store(const char *submodule) -{ - struct ref_store *refs; - - if (!submodule || !*submodule) - return main_ref_store; - - for (refs = submodule_ref_stores; refs; refs = refs->next) { - if (!strcmp(submodule, refs->submodule)) - return refs; - } - - return NULL; + refs = be->init(submodule); + register_ref_store(refs, submodule); + return refs; } struct ref_store *get_ref_store(const char *submodule) @@@ -1481,10 -1440,10 +1481,10 @@@ return refs; } -void assert_main_repository(struct ref_store *refs, const char *caller) +void base_ref_store_init(struct ref_store *refs, + const struct ref_storage_be *be) { - if (*refs->submodule) - die("BUG: %s called for a submodule", caller); + refs->be = be; } /* backend functions */ diff --combined refs.h index e529f4c3a8,a075117a2a..3df0d45ebb --- a/refs.h +++ b/refs.h @@@ -276,8 -276,8 +276,8 @@@ int reflog_exists(const char *refname) * exists, regardless of its old value. It is an error for old_sha1 to * be NULL_SHA1. flags is passed through to ref_transaction_delete(). */ -int delete_ref(const char *refname, const unsigned char *old_sha1, - unsigned int flags); +int delete_ref(const char *msg, const char *refname, + const unsigned char *old_sha1, unsigned int flags); /* * Delete the specified references. If there are any problems, emit @@@ -292,7 -292,7 +292,7 @@@ int delete_reflog(const char *refname) /* iterate over reflog entries */ typedef int each_reflog_ent_fn( - unsigned char *old_sha1, unsigned char *new_sha1, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, unsigned long timestamp, int tz, const char *msg, void *cb_data); @@@ -334,8 -334,7 +334,8 @@@ int create_symref(const char *refname, * $GIT_DIR points to. * Return 0 if successful, non-zero otherwise. * */ -int set_worktree_head_symref(const char *gitdir, const char *target); +int set_worktree_head_symref(const char *gitdir, const char *target, + const char *logmsg); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, diff --combined refs/files-backend.c index b42df147c9,fea20e99fe..50188e92f9 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@@ -912,14 -912,6 +912,14 @@@ struct packed_ref_cache */ struct files_ref_store { struct ref_store base; + + /* + * The name of the submodule represented by this object, or + * NULL if it represents the main repository's reference + * store: + */ + const char *submodule; + struct ref_entry *loose; struct packed_ref_cache *packed; }; @@@ -980,24 -972,11 +980,24 @@@ static struct ref_store *files_ref_stor struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; - base_ref_store_init(ref_store, &refs_be_files, submodule); + base_ref_store_init(ref_store, &refs_be_files); + + refs->submodule = xstrdup_or_null(submodule); return ref_store; } +/* + * Die if refs is for a submodule (i.e., not for the main repository). + * caller is used in any necessary error messages. + */ +static void files_assert_main_repository(struct files_ref_store *refs, + const char *caller) +{ + if (refs->submodule) + die("BUG: %s called for a submodule", caller); +} + /* * Downcast ref_store to files_ref_store. Die if ref_store is not a * files_ref_store. If submodule_allowed is not true, then also die if @@@ -1008,18 -987,14 +1008,18 @@@ static struct files_ref_store *files_do struct ref_store *ref_store, int submodule_allowed, const char *caller) { + struct files_ref_store *refs; + if (ref_store->be != &refs_be_files) die("BUG: ref_store is type \"%s\" not \"files\" in %s", ref_store->be->name, caller); + refs = (struct files_ref_store *)ref_store; + if (!submodule_allowed) - assert_main_repository(ref_store, caller); + files_assert_main_repository(refs, caller); - return (struct files_ref_store *)ref_store; + return refs; } /* The length of a peeled reference line in packed-refs, including EOL: */ @@@ -1158,8 -1133,8 +1158,8 @@@ static struct packed_ref_cache *get_pac { char *packed_refs_file; - if (*refs->base.submodule) - packed_refs_file = git_pathdup_submodule(refs->base.submodule, + if (refs->submodule) + packed_refs_file = git_pathdup_submodule(refs->submodule, "packed-refs"); else packed_refs_file = git_pathdup("packed-refs"); @@@ -1228,8 -1203,8 +1228,8 @@@ static void read_loose_refs(const char size_t path_baselen; int err = 0; - if (*refs->base.submodule) - err = strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname); + if (refs->submodule) + err = strbuf_git_path_submodule(&path, refs->submodule, "%s", dirname); else strbuf_git_path(&path, "%s", dirname); path_baselen = path.len; @@@ -1267,10 -1242,20 +1267,10 @@@ create_dir_entry(refs, refname.buf, refname.len, 1)); } else { - int read_ok; - - if (*refs->base.submodule) { - hashclr(sha1); - flag = 0; - read_ok = !resolve_gitlink_ref(refs->base.submodule, - refname.buf, sha1); - } else { - read_ok = !read_ref_full(refname.buf, - RESOLVE_REF_READING, - sha1, &flag); - } - - if (!read_ok) { + if (!resolve_ref_recursively(&refs->base, + refname.buf, + RESOLVE_REF_READING, + sha1, &flag)) { hashclr(sha1); flag |= REF_ISBROKEN; } else if (is_null_sha1(sha1)) { @@@ -1373,8 -1358,8 +1373,8 @@@ static int files_read_raw_ref(struct re *type = 0; strbuf_reset(&sb_path); - if (*refs->base.submodule) - strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname); + if (refs->submodule) + strbuf_git_path_submodule(&sb_path, refs->submodule, "%s", refname); else strbuf_git_path(&sb_path, "%s", refname); @@@ -1555,7 -1540,7 +1555,7 @@@ static int lock_raw_ref(struct files_re int ret = TRANSACTION_GENERIC_ERROR; assert(err); - assert_main_repository(&refs->base, "lock_raw_ref"); + files_assert_main_repository(refs, "lock_raw_ref"); *type = 0; @@@ -2000,13 -1985,6 +2000,13 @@@ static int remove_empty_directories(str return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); } +static int create_reflock(const char *path, void *cb) +{ + struct lock_file *lk = cb; + + return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0; +} + /* * Locks a ref returning the lock on success and NULL on failure. * On failure errno is set to something meaningful. @@@ -2022,11 -2000,13 +2022,11 @@@ static struct ref_lock *lock_ref_sha1_b struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; int last_errno = 0; - int lflags = LOCK_NO_DEREF; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); int resolve_flags = RESOLVE_REF_NO_RECURSE; - int attempts_remaining = 3; int resolved; - assert_main_repository(&refs->base, "lock_ref_sha1_basic"); + files_assert_main_repository(refs, "lock_ref_sha1_basic"); assert(err); lock = xcalloc(1, sizeof(struct ref_lock)); @@@ -2088,12 -2068,35 +2088,12 @@@ lock->ref_name = xstrdup(refname); - retry: - switch (safe_create_leading_directories_const(ref_file.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: + if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) { last_errno = errno; - strbuf_addf(err, "unable to create directory for '%s'", - ref_file.buf); + unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } - if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { - last_errno = errno; - if (errno == ENOENT && --attempts_remaining > 0) - /* - * Maybe somebody just deleted one of the - * directories leading to ref_file. Try - * again: - */ - goto retry; - else { - unable_to_lock_message(ref_file.buf, errno, err); - goto error_return; - } - } if (verify_lock(lock, old_sha1, mustexist, err)) { last_errno = errno; goto error_return; @@@ -2149,7 -2152,7 +2149,7 @@@ static int lock_packed_refs(struct file static int timeout_value = 1000; struct packed_ref_cache *packed_ref_cache; - assert_main_repository(&refs->base, "lock_packed_refs"); + files_assert_main_repository(refs, "lock_packed_refs"); if (!timeout_configured) { git_config_get_int("core.packedrefstimeout", &timeout_value); @@@ -2187,7 -2190,7 +2187,7 @@@ static int commit_packed_refs(struct fi int save_errno = 0; FILE *out; - assert_main_repository(&refs->base, "commit_packed_refs"); + files_assert_main_repository(refs, "commit_packed_refs"); if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@@ -2220,7 -2223,7 +2220,7 @@@ static void rollback_packed_refs(struc struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs); - assert_main_repository(&refs->base, "rollback_packed_refs"); + files_assert_main_repository(refs, "rollback_packed_refs"); if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@@ -2295,25 -2298,15 +2295,25 @@@ static int pack_if_possible_fn(struct r return 0; } +enum { + REMOVE_EMPTY_PARENTS_REF = 0x01, + REMOVE_EMPTY_PARENTS_REFLOG = 0x02 +}; + /* - * Remove empty parents, but spare refs/ and immediate subdirs. - * Note: munges *name. + * Remove empty parent directories associated with the specified + * reference and/or its reflog, but spare [logs/]refs/ and immediate + * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or + * REMOVE_EMPTY_PARENTS_REFLOG. */ -static void try_remove_empty_parents(char *name) +static void try_remove_empty_parents(const char *refname, unsigned int flags) { + struct strbuf buf = STRBUF_INIT; char *p, *q; int i; - p = name; + + strbuf_addstr(&buf, refname); + p = buf.buf; for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ while (*p && *p != '/') p++; @@@ -2321,23 -2314,19 +2321,23 @@@ while (*p == '/') p++; } - for (q = p; *q; q++) - ; - while (1) { + q = buf.buf + buf.len; + while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) { while (q > p && *q != '/') q--; while (q > p && *(q-1) == '/') q--; if (q == p) break; - *q = '\0'; - if (rmdir(git_path("%s", name))) - break; + strbuf_setlen(&buf, q - buf.buf); + if ((flags & REMOVE_EMPTY_PARENTS_REF) && + rmdir(git_path("%s", buf.buf))) + flags &= ~REMOVE_EMPTY_PARENTS_REF; + if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) && + rmdir(git_path("logs/%s", buf.buf))) + flags &= ~REMOVE_EMPTY_PARENTS_REFLOG; } + strbuf_release(&buf); } /* make sure nobody touched the ref, and unlink */ @@@ -2361,6 -2350,7 +2361,6 @@@ static void prune_ref(struct ref_to_pru } ref_transaction_free(transaction); strbuf_release(&err); - try_remove_empty_parents(r->name); } static void prune_refs(struct ref_to_prune *r) @@@ -2407,7 -2397,7 +2407,7 @@@ static int repack_without_refs(struct f struct string_list_item *refname; int ret, needs_repacking = 0, removed = 0; - assert_main_repository(&refs->base, "repack_without_refs"); + files_assert_main_repository(refs, "repack_without_refs"); assert(err); /* Look for a packed ref */ @@@ -2449,6 -2439,24 +2449,6 @@@ return ret; } -static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) -{ - assert(err); - - if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { - /* - * loose. The loose file name is the same as the - * lockfile name, minus ".lock": - */ - char *loose_filename = get_locked_file_path(lock->lk); - int res = unlink_or_msg(loose_filename, err); - free(loose_filename); - if (res) - return 1; - } - return 0; -} - static int files_delete_refs(struct ref_store *ref_store, struct string_list *refnames, unsigned int flags) { @@@ -2481,7 -2489,7 +2481,7 @@@ for (i = 0; i < refnames->nr; i++) { const char *refname = refnames->items[i].string; - if (delete_ref(refname, NULL, flags)) + if (delete_ref(NULL, refname, NULL, flags)) result |= error(_("could not remove reference %s"), refname); } @@@ -2499,43 -2507,55 +2499,43 @@@ out */ #define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" -static int rename_tmp_log(const char *newrefname) +static int rename_tmp_log_callback(const char *path, void *cb) { - int attempts_remaining = 4; - struct strbuf path = STRBUF_INIT; - int ret = -1; + int *true_errno = cb; - retry: - strbuf_reset(&path); - strbuf_git_path(&path, "logs/%s", newrefname); - switch (safe_create_leading_directories_const(path.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: - error("unable to create directory for %s", newrefname); - goto out; + if (rename(git_path(TMP_RENAMED_LOG), path)) { + /* + * rename(a, b) when b is an existing directory ought + * to result in ISDIR, but Solaris 5.8 gives ENOTDIR. + * Sheesh. Record the true errno for error reporting, + * but report EISDIR to raceproof_create_file() so + * that it knows to retry. + */ + *true_errno = errno; + if (errno == ENOTDIR) + errno = EISDIR; + return -1; + } else { + return 0; } +} - if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { - if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { - /* - * rename(a, b) when b is an existing - * directory ought to result in ISDIR, but - * Solaris 5.8 gives ENOTDIR. Sheesh. - */ - if (remove_empty_directories(&path)) { - error("Directory not empty: logs/%s", newrefname); - goto out; - } - goto retry; - } else if (errno == ENOENT && --attempts_remaining > 0) { - /* - * Maybe another process just deleted one of - * the directories in the path to newrefname. - * Try again from the beginning. - */ - goto retry; - } else { - error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", - newrefname, strerror(errno)); - goto out; - } +static int rename_tmp_log(const char *newrefname) +{ + char *path = git_pathdup("logs/%s", newrefname); + int ret, true_errno; + + ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno); + if (ret) { + if (errno == EISDIR) + error("directory not empty: %s", path); + else + error("unable to move logfile %s to %s: %s", + git_path(TMP_RENAMED_LOG), path, + strerror(true_errno)); } - ret = 0; -out: - strbuf_release(&path); + + free(path); return ret; } @@@ -2596,7 -2616,7 +2596,7 @@@ static int files_rename_ref(struct ref_ return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", oldrefname, strerror(errno)); - if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) { + if (delete_ref(logmsg, oldrefname, orig_sha1, REF_NODEREF)) { error("unable to delete old %s", oldrefname); goto rollback; } @@@ -2610,8 -2630,8 +2610,8 @@@ */ if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, sha1, NULL) && - delete_ref(newrefname, NULL, REF_NODEREF)) { - if (errno==EISDIR) { + delete_ref(NULL, newrefname, NULL, REF_NODEREF)) { + if (errno == EISDIR) { struct strbuf path = STRBUF_INIT; int result; @@@ -2720,89 -2740,66 +2720,89 @@@ static int commit_ref(struct ref_lock * return 0; } +static int open_or_create_logfile(const char *path, void *cb) +{ + int *fd = cb; + + *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666); + return (*fd < 0) ? -1 : 0; +} + /* - * Create a reflog for a ref. If force_create = 0, the reflog will - * only be created for certain refs (those for which - * should_autocreate_reflog returns non-zero. Otherwise, create it - * regardless of the ref name. Fill in *err and return -1 on failure. + * Create a reflog for a ref. If force_create = 0, only create the + * reflog for certain refs (those for which should_autocreate_reflog + * returns non-zero). Otherwise, create it regardless of the reference + * name. If the logfile already existed or was created, return 0 and + * set *logfd to the file descriptor opened for appending to the file. + * If no logfile exists and we decided not to create one, return 0 and + * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and + * return -1. */ -static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) +static int log_ref_setup(const char *refname, int force_create, + int *logfd, struct strbuf *err) { - int logfd, oflags = O_APPEND | O_WRONLY; + char *logfile = git_pathdup("logs/%s", refname); - strbuf_git_path(logfile, "logs/%s", refname); if (force_create || should_autocreate_reflog(refname)) { - if (safe_create_leading_directories(logfile->buf) < 0) { - strbuf_addf(err, "unable to create directory for '%s': " - "%s", logfile->buf, strerror(errno)); - return -1; - } - oflags |= O_CREAT; - } - - logfd = open(logfile->buf, oflags, 0666); - if (logfd < 0) { - if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) - return 0; + if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) { + if (errno == ENOENT) + strbuf_addf(err, "unable to create directory for '%s': " + "%s", logfile, strerror(errno)); + else if (errno == EISDIR) + strbuf_addf(err, "there are still logs under '%s'", + logfile); + else + strbuf_addf(err, "unable to append to '%s': %s", + logfile, strerror(errno)); - if (errno == EISDIR) { - if (remove_empty_directories(logfile)) { - strbuf_addf(err, "there are still logs under " - "'%s'", logfile->buf); - return -1; - } - logfd = open(logfile->buf, oflags, 0666); + goto error; } - - if (logfd < 0) { - strbuf_addf(err, "unable to append to '%s': %s", - logfile->buf, strerror(errno)); - return -1; + } else { + *logfd = open(logfile, O_APPEND | O_WRONLY, 0666); + if (*logfd < 0) { + if (errno == ENOENT || errno == EISDIR) { + /* + * The logfile doesn't already exist, + * but that is not an error; it only + * means that we won't write log + * entries to it. + */ + ; + } else { + strbuf_addf(err, "unable to append to '%s': %s", + logfile, strerror(errno)); + goto error; + } } } - adjust_shared_perm(logfile->buf); - close(logfd); + if (*logfd >= 0) + adjust_shared_perm(logfile); + + free(logfile); return 0; -} +error: + free(logfile); + return -1; +} static int files_create_reflog(struct ref_store *ref_store, const char *refname, int force_create, struct strbuf *err) { - int ret; - struct strbuf sb = STRBUF_INIT; + int fd; /* Check validity (but we don't need the result): */ files_downcast(ref_store, 0, "create_reflog"); - ret = log_ref_setup(refname, &sb, err, force_create); - strbuf_release(&sb); - return ret; + if (log_ref_setup(refname, force_create, &fd, err)) + return -1; + + if (fd >= 0) + close(fd); + + return 0; } static int log_ref_write_fd(int fd, const unsigned char *old_sha1, @@@ -2831,43 -2828,59 +2831,43 @@@ return 0; } -static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - struct strbuf *logfile, int flags, - struct strbuf *err) +int files_log_ref_write(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err) { - int logfd, result, oflags = O_APPEND | O_WRONLY; + int logfd, result; if (log_all_ref_updates == LOG_REFS_UNSET) log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; - result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); + result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG, + &logfd, err); if (result) return result; - logfd = open(logfile->buf, oflags); if (logfd < 0) return 0; result = log_ref_write_fd(logfd, old_sha1, new_sha1, git_committer_info(0), msg); if (result) { - strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, - strerror(errno)); + int save_errno = errno; + + strbuf_addf(err, "unable to append to '%s': %s", + git_path("logs/%s", refname), strerror(save_errno)); close(logfd); return -1; } if (close(logfd)) { - strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, - strerror(errno)); + int save_errno = errno; + + strbuf_addf(err, "unable to append to '%s': %s", + git_path("logs/%s", refname), strerror(save_errno)); return -1; } return 0; } -static int log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - int flags, struct strbuf *err) -{ - return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags, - err); -} - -int files_log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - int flags, struct strbuf *err) -{ - struct strbuf sb = STRBUF_INIT; - int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags, - err); - strbuf_release(&sb); - return ret; -} - /* * Write sha1 into the open lockfile, then close the lockfile. On * errors, rollback the lockfile, fill in *err and @@@ -2917,11 -2930,10 +2917,11 @@@ static int commit_ref_update(struct fil const unsigned char *sha1, const char *logmsg, struct strbuf *err) { - assert_main_repository(&refs->base, "commit_ref_update"); + files_assert_main_repository(refs, "commit_ref_update"); clear_loose_ref_cache(refs); - if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) { + if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, + logmsg, 0, err)) { char *old_msg = strbuf_detach(err, NULL); strbuf_addf(err, "cannot update the ref '%s': %s", lock->ref_name, old_msg); @@@ -2952,7 -2964,7 +2952,7 @@@ if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) { struct strbuf log_err = STRBUF_INIT; - if (log_ref_write("HEAD", lock->old_oid.hash, sha1, + if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg, 0, &log_err)) { error("%s", log_err.buf); strbuf_release(&log_err); @@@ -2991,8 -3003,7 +2991,8 @@@ static void update_symref_reflog(struc struct strbuf err = STRBUF_INIT; unsigned char new_sha1[20]; if (logmsg && !read_ref(target, new_sha1) && - log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) { + files_log_ref_write(refname, lock->old_oid.hash, new_sha1, + logmsg, 0, &err)) { error("%s", err.buf); strbuf_release(&err); } @@@ -3044,7 -3055,7 +3044,7 @@@ static int files_create_symref(struct r return ret; } -int set_worktree_head_symref(const char *gitdir, const char *target) +int set_worktree_head_symref(const char *gitdir, const char *target, const char *logmsg) { static struct lock_file head_lock; struct ref_lock *lock; @@@ -3072,7 -3083,7 +3072,7 @@@ lock->lk = &head_lock; lock->ref_name = xstrdup(head_rel); - ret = create_symref_locked(lock, head_rel, target, NULL); + ret = create_symref_locked(lock, head_rel, target, logmsg); unlock_ref(lock); /* will free lock */ strbuf_release(&head_path); @@@ -3102,16 -3113,17 +3102,17 @@@ static int files_delete_reflog(struct r static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) { - unsigned char osha1[20], nsha1[20]; + struct object_id ooid, noid; char *email_end, *message; unsigned long timestamp; int tz; + const char *p = sb->buf; /* old SP new SP name SP time TAB msg LF */ - if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || - get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || - get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || - !(email_end = strchr(sb->buf + 82, '>')) || + if (!sb->len || sb->buf[sb->len - 1] != '\n' || + parse_oid_hex(p, &ooid, &p) || *p++ != ' ' || + parse_oid_hex(p, &noid, &p) || *p++ != ' ' || + !(email_end = strchr(p, '>')) || email_end[1] != ' ' || !(timestamp = strtoul(email_end + 2, &message, 10)) || !message || message[0] != ' ' || @@@ -3125,7 -3137,7 +3126,7 @@@ message += 6; else message += 7; - return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); + return fn(&ooid, &noid, p, timestamp, tz, message, cb_data); } static char *find_beginning_of_line(char *bob, char *scan) @@@ -3549,7 -3561,7 +3550,7 @@@ static int lock_ref_for_update(struct f int ret; struct ref_lock *lock; - assert_main_repository(&refs->base, "lock_ref_for_update"); + files_assert_main_repository(refs, "lock_ref_for_update"); if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) update->flags |= REF_DELETING; @@@ -3767,11 -3779,9 +3768,11 @@@ static int files_transaction_commit(str if (update->flags & REF_NEEDS_COMMIT || update->flags & REF_LOG_ONLY) { - if (log_ref_write(lock->ref_name, lock->old_oid.hash, - update->new_sha1, - update->msg, update->flags, err)) { + if (files_log_ref_write(lock->ref_name, + lock->old_oid.hash, + update->new_sha1, + update->msg, update->flags, + err)) { char *old_msg = strbuf_detach(err, NULL); strbuf_addf(err, "cannot update the ref '%s': %s", @@@ -3801,14 -3811,9 +3802,14 @@@ if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY)) { - if (delete_ref_loose(lock, update->type, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; + if (!(update->type & REF_ISPACKED) || + update->type & REF_ISSYMREF) { + /* It is a loose reference. */ + if (unlink_or_msg(git_path("%s", lock->ref_name), err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + update->flags |= REF_DELETED_LOOSE; } if (!(update->flags & REF_ISPRUNING)) @@@ -3821,38 -3826,16 +3822,38 @@@ ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } - for_each_string_list_item(ref_to_delete, &refs_to_delete) - unlink_or_warn(git_path("logs/%s", ref_to_delete->string)); + + /* Delete the reflogs of any references that were deleted: */ + for_each_string_list_item(ref_to_delete, &refs_to_delete) { + if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string))) + try_remove_empty_parents(ref_to_delete->string, + REMOVE_EMPTY_PARENTS_REFLOG); + } + clear_loose_ref_cache(refs); cleanup: transaction->state = REF_TRANSACTION_CLOSED; - for (i = 0; i < transaction->nr; i++) - if (transaction->updates[i]->backend_data) - unlock_ref(transaction->updates[i]->backend_data); + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + struct ref_lock *lock = update->backend_data; + + if (lock) + unlock_ref(lock); + + if (update->flags & REF_DELETED_LOOSE) { + /* + * The loose reference was deleted. Delete any + * empty parent directories. (Note that this + * can only work because we have already + * removed the lockfile.) + */ + try_remove_empty_parents(update->refname, + REMOVE_EMPTY_PARENTS_REF); + } + } + string_list_clear(&refs_to_delete, 0); free(head_ref); string_list_clear(&affected_refnames, 0); @@@ -3954,10 -3937,10 +3955,10 @@@ struct expire_reflog_cb reflog_expiry_should_prune_fn *should_prune_fn; void *policy_cb; FILE *newlog; - unsigned char last_kept_sha1[20]; + struct object_id last_kept_oid; }; - static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@@ -3965,9 -3948,9 +3966,9 @@@ struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; if (cb->flags & EXPIRE_REFLOGS_REWRITE) - osha1 = cb->last_kept_sha1; + ooid = &cb->last_kept_oid; - if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, + if ((*cb->should_prune_fn)(ooid->hash, noid->hash, email, timestamp, tz, message, policy_cb)) { if (!cb->newlog) printf("would prune %s", message); @@@ -3976,9 -3959,9 +3977,9 @@@ } else { if (cb->newlog) { fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), + oid_to_hex(ooid), oid_to_hex(noid), email, timestamp, tz, message); - hashcpy(cb->last_kept_sha1, nsha1); + oidcpy(&cb->last_kept_oid, noid); } if (cb->flags & EXPIRE_REFLOGS_VERBOSE) printf("keep %s", message); @@@ -4065,14 -4048,14 +4066,14 @@@ static int files_reflog_expire(struct r */ int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && !(type & REF_ISSYMREF) && - !is_null_sha1(cb.last_kept_sha1); + !is_null_oid(&cb.last_kept_oid); if (close_lock_file(&reflog_lock)) { status |= error("couldn't write %s: %s", log_file, strerror(errno)); } else if (update && (write_in_full(get_lock_file_fd(lock->lk), - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ || write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || close_ref(lock) < 0)) { status |= error("couldn't write %s", diff --combined revision.c index 771d079f6e,d9fe73318a..7ff61ff5f7 --- a/revision.c +++ b/revision.c @@@ -147,7 -147,7 +147,7 @@@ static void add_pending_object_with_pat revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { struct strbuf buf = STRBUF_INIT; - int len = interpret_branch_name(name, 0, &buf); + int len = interpret_branch_name(name, 0, &buf, 0); int st; if (0 < len && name[len] && buf.len) @@@ -1196,11 -1196,11 +1196,11 @@@ static void handle_refs(const char *sub for_each(submodule, handle_one_ref, &cb); } - static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) + static void handle_one_reflog_commit(struct object_id *oid, void *cb_data) { struct all_refs_cb *cb = cb_data; - if (!is_null_sha1(sha1)) { - struct object *o = parse_object(sha1); + if (!is_null_oid(oid)) { + struct object *o = parse_object(oid->hash); if (o) { o->flags |= cb->all_flags; /* ??? CMDLINEFLAGS ??? */ @@@ -1214,12 -1214,12 +1214,12 @@@ } } - static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - handle_one_reflog_commit(osha1, cb_data); - handle_one_reflog_commit(nsha1, cb_data); + handle_one_reflog_commit(ooid, cb_data); + handle_one_reflog_commit(noid, cb_data); return 0; } diff --combined sha1_file.c index f1c83a9248,6b74c7d3bc..29bbc5f427 --- a/sha1_file.c +++ b/sha1_file.c @@@ -129,10 -129,8 +129,10 @@@ enum scld_error safe_create_leading_dir *slash = '\0'; if (!stat(path, &st)) { /* path exists */ - if (!S_ISDIR(st.st_mode)) + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; ret = SCLD_EXISTS; + } } else if (mkdir(path, 0777)) { if (errno == EEXIST && !stat(path, &st) && S_ISDIR(st.st_mode)) @@@ -160,85 -158,13 +160,85 @@@ enum scld_error safe_create_leading_directories_const(const char *path) { + int save_errno; /* path points to cache entries, so xstrdup before messing with it */ char *buf = xstrdup(path); enum scld_error result = safe_create_leading_directories(buf); + + save_errno = errno; free(buf); + errno = save_errno; return result; } +int raceproof_create_file(const char *path, create_file_fn fn, void *cb) +{ + /* + * The number of times we will try to remove empty directories + * in the way of path. This is only 1 because if another + * process is racily creating directories that conflict with + * us, we don't want to fight against them. + */ + int remove_directories_remaining = 1; + + /* + * The number of times that we will try to create the + * directories containing path. We are willing to attempt this + * more than once, because another process could be trying to + * clean up empty directories at the same time as we are + * trying to create them. + */ + int create_directories_remaining = 3; + + /* A scratch copy of path, filled lazily if we need it: */ + struct strbuf path_copy = STRBUF_INIT; + + int ret, save_errno; + + /* Sanity check: */ + assert(*path); + +retry_fn: + ret = fn(path, cb); + save_errno = errno; + if (!ret) + goto out; + + if (errno == EISDIR && remove_directories_remaining-- > 0) { + /* + * A directory is in the way. Maybe it is empty; try + * to remove it: + */ + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) + goto retry_fn; + } else if (errno == ENOENT && create_directories_remaining-- > 0) { + /* + * Maybe the containing directory didn't exist, or + * maybe it was just deleted by a process that is + * racing with us to clean up empty directories. Try + * to create it: + */ + enum scld_error scld_result; + + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + do { + scld_result = safe_create_leading_directories(path_copy.buf); + if (scld_result == SCLD_OK) + goto retry_fn; + } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); + } + +out: + strbuf_release(&path_copy); + errno = save_errno; + return ret; +} + static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1) { int i; @@@ -667,7 -593,7 +667,7 @@@ static int freshen_file(const char *fn * either does not exist on disk, or has a stale mtime and may be subject to * pruning). */ -static int check_and_freshen_file(const char *fn, int freshen) +int check_and_freshen_file(const char *fn, int freshen) { if (access(fn, F_OK)) return 0; @@@ -2606,7 -2532,6 +2606,7 @@@ void *unpack_entry(struct packed_git *p while (delta_stack_nr) { void *delta_data; void *base = data; + void *external_base = NULL; unsigned long delta_size, base_size = size; int i; @@@ -2633,7 -2558,6 +2633,7 @@@ p->pack_name); mark_bad_packed_object(p, base_sha1); base = read_object(base_sha1, &type, &base_size); + external_base = base; } } @@@ -2652,7 -2576,6 +2652,7 @@@ "at offset %"PRIuMAX" from %s", (uintmax_t)curpos, p->pack_name); data = NULL; + free(external_base); continue; } @@@ -2672,7 -2595,6 +2672,7 @@@ error("failed to apply delta"); free(delta_data); + free(external_base); } *final_type = type; @@@ -2706,6 -2628,17 +2706,17 @@@ const unsigned char *nth_packed_object_ } } + const struct object_id *nth_packed_object_oid(struct object_id *oid, + struct packed_git *p, + uint32_t n) + { + const unsigned char *hash = nth_packed_object_sha1(p, n); + if (!hash) + return NULL; + hashcpy(oid->hash, hash); + return oid; + } + void check_pack_index_ptr(const struct packed_git *p, const void *vptr) { const unsigned char *ptr = vptr; @@@ -3752,15 -3685,15 +3763,15 @@@ static int for_each_file_in_obj_subdir( strbuf_setlen(path, baselen); strbuf_addf(path, "/%s", de->d_name); - if (strlen(de->d_name) == 38) { - char hex[41]; - unsigned char sha1[20]; + if (strlen(de->d_name) == GIT_SHA1_HEXSZ - 2) { + char hex[GIT_SHA1_HEXSZ+1]; + struct object_id oid; snprintf(hex, sizeof(hex), "%02x%s", subdir_nr, de->d_name); - if (!get_sha1_hex(hex, sha1)) { + if (!get_oid_hex(hex, &oid)) { if (obj_cb) { - r = obj_cb(sha1, path->buf, data); + r = obj_cb(&oid, path->buf, data); if (r) break; } @@@ -3866,13 -3799,13 +3877,13 @@@ static int for_each_object_in_pack(stru int r = 0; for (i = 0; i < p->num_objects; i++) { - const unsigned char *sha1 = nth_packed_object_sha1(p, i); + struct object_id oid; - if (!sha1) + if (!nth_packed_object_oid(&oid, p, i)) return error("unable to get sha1 of object %u in %s", i, p->pack_name); - r = cb(sha1, p, i, data); + r = cb(&oid, p, i, data); if (r) break; } diff --combined sha1_name.c index 26ceec1d79,744e9f884a..cda9e49b12 --- a/sha1_name.c +++ b/sha1_name.c @@@ -1051,7 -1051,7 +1051,7 @@@ struct grab_nth_branch_switch_cbdata struct strbuf buf; }; - static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, + static int grab_nth_branch_switch(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@@ -1176,8 -1176,7 +1176,8 @@@ static int interpret_empty_at(const cha return 1; } -static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf) +static int reinterpret(const char *name, int namelen, int len, + struct strbuf *buf, unsigned allowed) { /* we have extra data, which might need further processing */ struct strbuf tmp = STRBUF_INIT; @@@ -1185,7 -1184,7 +1185,7 @@@ int ret; strbuf_add(buf, name + len, namelen - len); - ret = interpret_branch_name(buf->buf, buf->len, &tmp); + ret = interpret_branch_name(buf->buf, buf->len, &tmp, allowed); /* that data was not interpreted, remove our cruft */ if (ret < 0) { strbuf_setlen(buf, used); @@@ -1206,27 -1205,11 +1206,27 @@@ static void set_shortened_ref(struct st free(s); } +static int branch_interpret_allowed(const char *refname, unsigned allowed) +{ + if (!allowed) + return 1; + + if ((allowed & INTERPRET_BRANCH_LOCAL) && + starts_with(refname, "refs/heads/")) + return 1; + if ((allowed & INTERPRET_BRANCH_REMOTE) && + starts_with(refname, "refs/remotes/")) + return 1; + + return 0; +} + static int interpret_branch_mark(const char *name, int namelen, int at, struct strbuf *buf, int (*get_mark)(const char *, int), const char *(*get_data)(struct branch *, - struct strbuf *)) + struct strbuf *), + unsigned allowed) { int len; struct branch *branch; @@@ -1251,55 -1234,64 +1251,55 @@@ if (!value) die("%s", err.buf); + if (!branch_interpret_allowed(value, allowed)) + return -1; + set_shortened_ref(buf, value); return len + at; } -/* - * This reads short-hand syntax that not only evaluates to a commit - * object name, but also can act as if the end user spelled the name - * of the branch from the command line. - * - * - "@{-N}" finds the name of the Nth previous branch we were on, and - * places the name of the branch in the given buf and returns the - * number of characters parsed if successful. - * - * - "@{upstream}" finds the name of the other ref that - * is configured to merge with (missing defaults - * to the current branch), and places the name of the branch in the - * given buf and returns the number of characters parsed if - * successful. - * - * If the input is not of the accepted format, it returns a negative - * number to signal an error. - * - * If the input was ok but there are not N branch switches in the - * reflog, it returns 0. - */ -int interpret_branch_name(const char *name, int namelen, struct strbuf *buf) +int interpret_branch_name(const char *name, int namelen, struct strbuf *buf, + unsigned allowed) { char *at; const char *start; - int len = interpret_nth_prior_checkout(name, namelen, buf); + int len; if (!namelen) namelen = strlen(name); - if (!len) { - return len; /* syntax Ok, not enough switches */ - } else if (len > 0) { - if (len == namelen) - return len; /* consumed all */ - else - return reinterpret(name, namelen, len, buf); + if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) { + len = interpret_nth_prior_checkout(name, namelen, buf); + if (!len) { + return len; /* syntax Ok, not enough switches */ + } else if (len > 0) { + if (len == namelen) + return len; /* consumed all */ + else + return reinterpret(name, namelen, len, buf, allowed); + } } for (start = name; (at = memchr(start, '@', namelen - (start - name))); start = at + 1) { - len = interpret_empty_at(name, namelen, at - name, buf); - if (len > 0) - return reinterpret(name, namelen, len, buf); + if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) { + len = interpret_empty_at(name, namelen, at - name, buf); + if (len > 0) + return reinterpret(name, namelen, len, buf, + allowed); + } len = interpret_branch_mark(name, namelen, at - name, buf, - upstream_mark, branch_get_upstream); + upstream_mark, branch_get_upstream, + allowed); if (len > 0) return len; len = interpret_branch_mark(name, namelen, at - name, buf, - push_mark, branch_get_push); + push_mark, branch_get_push, + allowed); if (len > 0) return len; } @@@ -1307,19 -1299,22 +1307,19 @@@ return -1; } -int strbuf_branchname(struct strbuf *sb, const char *name) +void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed) { int len = strlen(name); - int used = interpret_branch_name(name, len, sb); + int used = interpret_branch_name(name, len, sb, allowed); - if (used == len) - return 0; if (used < 0) used = 0; strbuf_add(sb, name + used, len - used); - return len; } int strbuf_check_branch_ref(struct strbuf *sb, const char *name) { - strbuf_branchname(sb, name); + strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL); if (name[0] == '-') return -1; strbuf_splice(sb, 0, 0, "refs/heads/", 11); diff --combined transport.c index ea1feac9dc,141af31e8e..417ed7f19f --- a/transport.c +++ b/transport.c @@@ -204,7 -204,6 +204,7 @@@ static struct ref *get_refs_via_connect static int fetch_refs_via_pack(struct transport *transport, int nr_heads, struct ref **to_fetch) { + int ret = 0; struct git_transport_data *data = transport->data; struct ref *refs; char *dest = xstrdup(transport->url); @@@ -242,22 -241,19 +242,22 @@@ &transport->pack_lockfile); close(data->fd[0]); close(data->fd[1]); - if (finish_connect(data->conn)) { - free_refs(refs); - refs = NULL; - } + if (finish_connect(data->conn)) + ret = -1; data->conn = NULL; data->got_remote_heads = 0; data->options.self_contained_and_connected = args.self_contained_and_connected; + if (refs == NULL) + ret = -1; + if (report_unmatched_refs(to_fetch, nr_heads)) + ret = -1; + free_refs(refs_tmp); free_refs(refs); free(dest); - return (refs ? 0 : -1); + return ret; } static int push_had_errors(struct ref *ref) @@@ -303,7 -299,7 +303,7 @@@ void transport_update_tracking_ref(stru if (verbose) fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); if (ref->deletion) { - delete_ref(rs.dst, NULL, 0); + delete_ref(NULL, rs.dst, NULL, 0); } else update_ref("update by push", rs.dst, ref->new_oid.hash, NULL, 0, 0); @@@ -471,11 -467,11 +471,11 @@@ void transport_print_push_status(const { struct ref *ref; int n = 0; - unsigned char head_sha1[20]; + struct object_id head_oid; char *head; int summary_width = transport_summary_width(refs); - head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) @@@ -1210,42 -1206,6 +1210,42 @@@ literal_copy return xstrdup(url); } +static void read_alternate_refs(const char *path, + alternate_ref_fn *cb, + void *data) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf line = STRBUF_INIT; + FILE *fh; + + cmd.git_cmd = 1; + argv_array_pushf(&cmd.args, "--git-dir=%s", path); + argv_array_push(&cmd.args, "for-each-ref"); + argv_array_push(&cmd.args, "--format=%(objectname) %(refname)"); + cmd.env = local_repo_env; + cmd.out = -1; + + if (start_command(&cmd)) + return; + + fh = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&line, fh) != EOF) { + struct object_id oid; + + if (get_oid_hex(line.buf, &oid) || + line.buf[GIT_SHA1_HEXSZ] != ' ') { + warning("invalid line while parsing alternate refs: %s", + line.buf); + break; + } + + cb(line.buf + GIT_SHA1_HEXSZ + 1, &oid, data); + } + + fclose(fh); + finish_command(&cmd); +} + struct alternate_refs_data { alternate_ref_fn *fn; void *data; @@@ -1254,26 -1214,34 +1254,26 @@@ static int refs_from_alternate_cb(struct alternate_object_database *e, void *data) { - char *other; - size_t len; - struct remote *remote; - struct transport *transport; - const struct ref *extra; + struct strbuf path = STRBUF_INIT; + size_t base_len; struct alternate_refs_data *cb = data; - other = real_pathdup(e->path); - len = strlen(other); - - while (other[len-1] == '/') - other[--len] = '\0'; - if (len < 8 || memcmp(other + len - 8, "/objects", 8)) + if (!strbuf_realpath(&path, e->path, 0)) + goto out; + if (!strbuf_strip_suffix(&path, "/objects")) goto out; + base_len = path.len; + /* Is this a git repository with refs? */ - memcpy(other + len - 8, "/refs", 6); - if (!is_directory(other)) + strbuf_addstr(&path, "/refs"); + if (!is_directory(path.buf)) goto out; - other[len - 8] = '\0'; - remote = remote_get(other); - transport = transport_get(remote, other); - for (extra = transport_get_remote_refs(transport); - extra; - extra = extra->next) - cb->fn(extra, cb->data); - transport_disconnect(transport); + strbuf_setlen(&path, base_len); + + read_alternate_refs(path.buf, cb->fn, cb->data); + out: - free(other); + strbuf_release(&path); return 0; }