diff options
Diffstat (limited to 'builtin')
47 files changed, 2176 insertions, 757 deletions
diff --git a/builtin/am.c b/builtin/am.c index 31fb60578f..f7a7a971fb 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1049,7 +1049,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, } else { write_state_text(state, "abort-safety", ""); if (!state->rebasing) - delete_ref("ORIG_HEAD", NULL, 0); + delete_ref(NULL, "ORIG_HEAD", NULL, 0); } /* @@ -2172,7 +2172,7 @@ static void am_abort(struct am_state *state) has_curr_head ? &curr_head : NULL, 0, UPDATE_REFS_DIE_ON_ERR); else if (curr_branch) - delete_ref(curr_branch, NULL, REF_NODEREF); + delete_ref(NULL, curr_branch, NULL, REF_NODEREF); free(curr_branch); am_destroy(state); diff --git a/builtin/blame.c b/builtin/blame.c index 4ddfadb71f..cffc626540 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1700,13 +1700,23 @@ static void get_commit_info(struct commit *commit, } /* + * Write out any suspect information which depends on the path. This must be + * handled separately from emit_one_suspect_detail(), because a given commit + * may have changes in multiple paths. So this needs to appear each time + * we mention a new group. + * * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(const char *path) +static void write_filename_info(struct origin *suspect) { + if (suspect->previous) { + struct origin *prev = suspect->previous; + printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); + write_name_quoted(prev->path, stdout, '\n'); + } printf("filename "); - write_name_quoted(path, stdout, '\n'); + write_name_quoted(suspect->path, stdout, '\n'); } /* @@ -1735,11 +1745,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); - write_name_quoted(prev->path, stdout, '\n'); - } commit_info_destroy(&ci); @@ -1760,7 +1765,7 @@ static void found_guilty_entry(struct blame_entry *ent, oid_to_hex(&suspect->commit->object.oid), ent->s_lno + 1, ent->lno + 1, ent->num_lines); emit_one_suspect_detail(suspect, 0); - write_filename_info(suspect->path); + write_filename_info(suspect); maybe_flush_or_die(stdout, "stdout"); } pi->blamed_lines += ent->num_lines; @@ -1884,7 +1889,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat) { if (emit_one_suspect_detail(suspect, repeat) || (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); + write_filename_info(suspect); } static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, @@ -1896,7 +1901,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, struct origin *suspect = ent->suspect; char hex[GIT_SHA1_HEXSZ + 1]; - sha1_to_hex_r(hex, suspect->commit->object.oid.hash); + oid_to_hex_r(hex, &suspect->commit->object.oid); printf("%s %d %d %d\n", hex, ent->s_lno + 1, @@ -1936,7 +1941,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); get_commit_info(suspect->commit, &ci, 1); - sha1_to_hex_r(hex, suspect->commit->object.oid.hash); + oid_to_hex_r(hex, &suspect->commit->object.oid); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { @@ -2596,8 +2601,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * and are only included here to get included in the "-h" * output: */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental indent-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, - { OPTION_LOWLEVEL_CALLBACK, 0, "compaction-heuristic", NULL, NULL, N_("Use an experimental blank-line-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), @@ -2645,7 +2649,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); - xdl_opts |= revs.diffopt.xdl_opts & (XDF_COMPACTION_HEURISTIC | XDF_INDENT_HEURISTIC); + xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); @@ -2656,9 +2660,11 @@ parse_done: } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev) + if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) /* one more abbrev length is needed for the boundary commit */ abbrev++; + else if (!abbrev) + abbrev = GIT_SHA1_HEXSZ; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); diff --git a/builtin/branch.c b/builtin/branch.c index 9d30f55b0b..5d44e36cba 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -28,6 +28,7 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), N_("git branch [<options>] [-r | -a] [--points-at]"), + N_("git branch [<options>] [-r | -a] [--format]"), NULL }; @@ -37,11 +38,11 @@ static unsigned char head_sha1[20]; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ - GIT_COLOR_RED, /* REMOTE */ - GIT_COLOR_NORMAL, /* LOCAL */ - GIT_COLOR_GREEN, /* CURRENT */ - 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, @@ -190,17 +191,20 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, 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")); @@ -215,7 +219,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, 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); @@ -251,7 +255,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, goto next; } - if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1, + if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1, REF_NODEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") @@ -280,221 +284,109 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, 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; + strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf); - skip_prefix(it->refname, "refs/heads/", &desc); - skip_prefix(it->refname, "refs/remotes/", &desc); - w = utf8_strwidth(desc); - - 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, @@ -506,18 +398,32 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin 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) @@ -579,14 +485,15 @@ static void rename_branch(const char *oldname, const char *newname, int force) 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); @@ -638,6 +545,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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")), @@ -679,9 +587,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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; @@ -749,7 +660,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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 --git a/builtin/check-attr.c b/builtin/check-attr.c index 53a5a18c16..4d01ca0c8b 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -24,12 +24,13 @@ static const struct option check_attr_options[] = { OPT_END() }; -static void output_attr(int cnt, struct git_attr_check *check, - const char *file) +static void output_attr(struct attr_check *check, const char *file) { int j; + int cnt = check->nr; + for (j = 0; j < cnt; j++) { - const char *value = check[j].value; + const char *value = check->items[j].value; if (ATTR_TRUE(value)) value = "set"; @@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check, printf("%s%c" /* path */ "%s%c" /* attrname */ "%s%c" /* attrvalue */, - file, 0, git_attr_name(check[j].attr), 0, value, 0); + file, 0, + git_attr_name(check->items[j].attr), 0, value, 0); } else { quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", git_attr_name(check[j].attr), value); + printf(": %s: %s\n", + git_attr_name(check->items[j].attr), value); } - } } -static void check_attr(const char *prefix, int cnt, - struct git_attr_check *check, const char *file) +static void check_attr(const char *prefix, + struct attr_check *check, + int collect_all, + const char *file) { char *full_path = prefix_path(prefix, prefix ? strlen(prefix) : 0, file); - if (check != NULL) { - if (git_check_attr(full_path, cnt, check)) - die("git_check_attr died"); - output_attr(cnt, check, file); + + if (collect_all) { + git_all_attrs(full_path, check); } else { - if (git_all_attrs(full_path, &cnt, &check)) - die("git_all_attrs died"); - output_attr(cnt, check, file); - free(check); + if (git_check_attr(full_path, check)) + die("git_check_attr died"); } + output_attr(check, file); + free(full_path); } -static void check_attr_stdin_paths(const char *prefix, int cnt, - struct git_attr_check *check) +static void check_attr_stdin_paths(const char *prefix, + struct attr_check *check, + int collect_all) { struct strbuf buf = STRBUF_INIT; struct strbuf unquoted = STRBUF_INIT; @@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt, die("line is badly quoted"); strbuf_swap(&buf, &unquoted); } - check_attr(prefix, cnt, check, buf.buf); + check_attr(prefix, check, collect_all, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); @@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg) int cmd_check_attr(int argc, const char **argv, const char *prefix) { - struct git_attr_check *check; + struct attr_check *check; int cnt, i, doubledash, filei; if (!is_bare_repository()) @@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) error_with_usage("No file specified"); } - if (all_attrs) { - check = NULL; - } else { - check = xcalloc(cnt, sizeof(*check)); + check = attr_check_alloc(); + if (!all_attrs) { for (i = 0; i < cnt; i++) { - const char *name; - struct git_attr *a; - name = argv[i]; - a = git_attr(name); + const struct git_attr *a = git_attr(argv[i]); + if (!a) return error("%s: not a valid attribute name", - name); - check[i].attr = a; + argv[i]); + attr_check_append(check, a); } } if (stdin_paths) - check_attr_stdin_paths(prefix, cnt, check); + check_attr_stdin_paths(prefix, check, all_attrs); else { for (i = filei; i < argc; i++) - check_attr(prefix, cnt, check, argv[i]); + check_attr(prefix, check, all_attrs, argv[i]); maybe_flush_or_die(stdout, "attribute to stdout"); } + + attr_check_free(check); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index bfe685c198..81f07c3ef2 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -56,8 +56,8 @@ static int post_checkout_hook(struct commit *old, struct commit *new, int changed) { return run_hook_le(NULL, "post-checkout", - sha1_to_hex(old ? old->object.oid.hash : null_sha1), - sha1_to_hex(new ? new->object.oid.hash : null_sha1), + oid_to_hex(old ? &old->object.oid : &null_oid), + oid_to_hex(new ? &new->object.oid : &null_oid), changed ? "1" : "0", NULL); /* "new" can be NULL when checking out from the index before a commit exists. */ @@ -452,7 +452,7 @@ static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; - strbuf_branchname(&buf, branch->name); + strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) branch->name = xstrdup(buf.buf); strbuf_splice(&buf, 0, 0, "refs/heads/", 11); @@ -612,22 +612,25 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - if (opts->new_branch_log && !log_all_ref_updates) { + char *refname; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + if (opts->new_branch_log && + !should_autocreate_reflog(refname)) { int ret; - char *refname; struct strbuf err = STRBUF_INIT; - refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); ret = safe_create_reflog(refname, 1, &err); - free(refname); if (ret) { fprintf(stderr, _("Can not do reflog for '%s': %s\n"), opts->new_orphan_branch, err.buf); strbuf_release(&err); + free(refname); return; } strbuf_release(&err); } + free(refname); } else create_branch(opts->new_branch, new->name, diff --git a/builtin/clean.c b/builtin/clean.c index 0371010afb..d861f836a2 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -174,8 +174,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, /* an empty dir could be removed even if it is unreadble */ res = dry_run ? 0 : rmdir(path->buf); if (res) { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } return res; @@ -208,8 +210,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; } @@ -230,8 +234,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (!res) *dir_gone = 1; else { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; } @@ -287,11 +293,11 @@ static void pretty_print_menus(struct string_list *menu_list) static void prompt_help_cmd(int singleton) { clean_print_color(CLEAN_COLOR_HELP); - printf_ln(singleton ? + printf(singleton ? _("Prompt help:\n" "1 - select a numbered item\n" "foo - select item based on unique prefix\n" - " - (empty) select nothing") : + " - (empty) select nothing\n") : _("Prompt help:\n" "1 - select a single item\n" "3-5 - select a range of items\n" @@ -299,7 +305,7 @@ static void prompt_help_cmd(int singleton) "foo - select item based on unique prefix\n" "-... - unselect specified items\n" "* - choose all items\n" - " - (empty) finish selecting")); + " - (empty) finish selecting\n")); clean_print_color(CLEAN_COLOR_RESET); } @@ -508,7 +514,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf_ln(_("Huh (%s)?"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), (*ptr)->buf); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -774,7 +780,7 @@ static int ask_each_cmd(void) static int quit_cmd(void) { string_list_clear(&del_list, 0); - printf_ln(_("Bye.")); + printf(_("Bye.\n")); return MENU_RETURN_NO_LOOP; } @@ -981,8 +987,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } else { res = dry_run ? 0 : unlink(abs_path.buf); if (res) { + int saved_errno = errno; qname = quote_path_relative(item->string, NULL, &buf); - warning(_(msg_warn_remove_failed), qname); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { qname = quote_path_relative(item->string, NULL, &buf); diff --git a/builtin/clone.c b/builtin/clone.c index 5ef81927a6..3f63edbbf9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -170,7 +170,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) strbuf_addstr(&path, repo); raw = get_repo_path_1(&path, is_bundle); - canon = raw ? xstrdup(absolute_path(raw)) : NULL; + canon = raw ? absolute_pathdup(raw) : NULL; strbuf_release(&path); return canon; } @@ -894,7 +894,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(absolute_path(repo_name)); + repo = absolute_pathdup(repo_name); else if (!strchr(repo_name, ':')) die(_("repository '%s' does not exist"), repo_name); else diff --git a/builtin/commit.c b/builtin/commit.c index 711f96cc43..2de5f6cc64 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -960,15 +960,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { NULL }; - env[0] = index; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path_commit_editmsg(), NULL, env)) { + struct argv_array env = ARGV_ARRAY_INIT; + + argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file); + if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } + argv_array_clear(&env); } if (!no_verify && @@ -1525,12 +1525,10 @@ static int git_commit_config(const char *k, const char *v, void *cb) static int run_rewrite_hook(const unsigned char *oldsha1, const unsigned char *newsha1) { - /* oldsha1 SP newsha1 LF NUL */ - static char buf[2*40 + 3]; struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; int code; - size_t n; + struct strbuf sb = STRBUF_INIT; argv[0] = find_hook("post-rewrite"); if (!argv[0]) @@ -1546,34 +1544,33 @@ static int run_rewrite_hook(const unsigned char *oldsha1, code = start_command(&proc); if (code) return code; - n = snprintf(buf, sizeof(buf), "%s %s\n", - sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); sigchain_push(SIGPIPE, SIG_IGN); - write_in_full(proc.in, buf, n); + write_in_full(proc.in, sb.buf, sb.len); close(proc.in); + strbuf_release(&sb); sigchain_pop(SIGPIPE); return finish_command(&proc); } int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) { - const char *hook_env[3] = { NULL }; - char index[PATH_MAX]; + struct argv_array hook_env = ARGV_ARRAY_INIT; va_list args; int ret; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - hook_env[0] = index; + argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); /* * Let the hook know that no editor will be launched. */ if (!editor_is_used) - hook_env[1] = "GIT_EDITOR=:"; + argv_array_push(&hook_env, "GIT_EDITOR=:"); va_start(args, name); - ret = run_hook_ve(hook_env, name, args); + ret = run_hook_ve(hook_env.argv,name, args); va_end(args); + argv_array_clear(&hook_env); return ret; } diff --git a/builtin/describe.c b/builtin/describe.c index 01490a157e..6769446e1f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -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; @@ -129,9 +130,40 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi 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)) { @@ -404,8 +436,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) N_("only output exact matches"), 0), OPT_INTEGER(0, "candidates", &max_candidates, N_("consider <n> 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 <pattern>")), + OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"), + N_("do not consider tags matching <pattern>")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), @@ -430,6 +464,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("--long is incompatible with --abbrev=0")); if (contains) { + struct string_list_item *item; struct argv_array args; argv_array_init(&args); @@ -440,8 +475,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) 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 --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 806dd7a885..8ce00480cd 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -147,9 +147,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) 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, diff --git a/builtin/diff.c b/builtin/diff.c index 7f91f6d226..3d64b85337 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -45,12 +45,9 @@ static void stuff_change(struct diff_options *opt, return; if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { - unsigned tmp; - const unsigned char *tmp_u; - const char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u; - tmp_c = old_name; old_name = new_name; new_name = tmp_c; + SWAP(old_mode, new_mode); + SWAP(old_sha1, new_sha1); + SWAP(old_name, new_name); } if (opt->prefix && diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..d13350ce83 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,692 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "cache.h" +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap_iter iter; + struct pair_entry *entry; + enum object_type type; + unsigned long size; + struct index_state wtindex; + struct checkout lstate, rstate; + int rc, flags = RUN_GIT_CMD, err = 0; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning("%s", ""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + + if (tool_help) + return print_tool_help(); + + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); +} diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index cfe9e447c2..2a1c1c213f 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -219,12 +219,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) * remote no-such-ref' would silently succeed without issuing * an error. */ - for (i = 0; i < nr_sought; i++) { - if (!sought[i] || sought[i]->matched) - continue; - error("no such remote ref %s", sought[i]->name); - ret = 1; - } + ret |= report_unmatched_refs(sought, nr_sought); while (ref) { printf("%s %s\n", diff --git a/builtin/fetch.c b/builtin/fetch.c index fc74c8471c..b5ad09d046 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -359,9 +359,6 @@ static struct ref *get_ref_map(struct transport *transport, for (i = 0; i < fetch_refspec_nr; i++) get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1); - - if (tags == TAGS_SET) - get_fetch_map(remote_refs, tag_refspec, &tail, 0); } else if (refmap_array) { die("--refmap option is only meaningful with command-line refspec(s)."); } else { @@ -1180,7 +1177,7 @@ static int add_remote_or_group(const char *name, struct string_list *list) git_config(get_remote_group, &g); if (list->nr == prev_nr) { struct remote *remote = remote_get(name); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 0)) return 0; string_list_append(list, remote->name); } diff --git a/builtin/fsck.c b/builtin/fsck.c index f01b81eebf..1a5caccd0f 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -56,6 +56,23 @@ static const char *describe_object(struct object *obj) return buf.buf; } +static const char *printable_type(struct object *obj) +{ + const char *ret; + + if (obj->type == OBJ_NONE) { + enum object_type type = sha1_object_info(obj->oid.hash, NULL); + if (type > 0) + object_as_type(obj, type, 0); + } + + ret = typename(obj->type); + if (!ret) + ret = "unknown"; + + return ret; +} + static int fsck_config(const char *var, const char *value, void *cb) { if (strcmp(var, "fsck.skiplist") == 0) { @@ -83,7 +100,7 @@ static void objreport(struct object *obj, const char *msg_type, const char *err) { fprintf(stderr, "%s in %s %s: %s\n", - msg_type, typename(obj->type), describe_object(obj), err); + msg_type, printable_type(obj), describe_object(obj), err); } static int objerror(struct object *obj, const char *err) @@ -114,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf("broken link from %7s %s\n", (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); errors_found |= ERROR_REACHABLE; @@ -131,9 +148,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf(" to %7s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; } return 1; @@ -205,9 +222,7 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->oid.hash)) return; /* it is in pack - forget about it */ - if (connectivity_only && has_object_file(&obj->oid)) - return; - printf("missing %s %s\n", typename(obj->type), + printf("missing %s %s\n", printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; return; @@ -225,7 +240,7 @@ static void check_unreachable_object(struct object *obj) * to complain about it being unreachable (since it does * not exist). */ - if (!obj->parsed) + if (!(obj->flags & HAS_OBJ)) return; /* @@ -233,7 +248,7 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), + printf("unreachable %s %s\n", printable_type(obj), describe_object(obj)); return; } @@ -252,7 +267,7 @@ static void check_unreachable_object(struct object *obj) */ if (!obj->used) { if (show_dangling) - printf("dangling %s %s\n", typename(obj->type), + printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", @@ -326,7 +341,7 @@ static int fsck_obj(struct object *obj) if (verbose) fprintf(stderr, "Checking %s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); @@ -352,7 +367,7 @@ static int fsck_obj(struct object *obj) struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), + printf("tagged %s %s", printable_type(tag->tagged), describe_object(tag->tagged)); printf(" (%s) in %s\n", tag->tag, describe_object(&tag->object)); @@ -362,18 +377,6 @@ static int fsck_obj(struct object *obj) return 0; } -static int fsck_sha1(const unsigned char *sha1) -{ - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } - obj->flags |= HAS_OBJ; - return fsck_obj(obj); -} - static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten) { @@ -400,7 +403,7 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, if (!is_null_sha1(sha1)) { obj = lookup_object(sha1); - if (obj) { + if (obj && (obj->flags & HAS_OBJ)) { if (timestamp && name_objects) add_decoration(fsck_walk_options.object_names, obj, @@ -488,9 +491,41 @@ static void get_default_heads(void) } } +static struct object *parse_loose_object(const unsigned char *sha1, + const char *path) +{ + struct object *obj; + void *contents; + enum object_type type; + unsigned long size; + int eaten; + + if (read_loose_object(path, sha1, &type, &size, &contents) < 0) + return NULL; + + if (!contents && type != OBJ_BLOB) + die("BUG: read_loose_object streamed a non-blob"); + + obj = parse_object_buffer(sha1, type, size, contents, &eaten); + + if (!eaten) + free(contents); + return obj; +} + static int fsck_loose(const unsigned char *sha1, const char *path, void *data) { - if (fsck_sha1(sha1)) + struct object *obj = parse_loose_object(sha1, path); + + if (!obj) { + errors_found |= ERROR_OBJECT; + error("%s: object corrupt or missing: %s", + sha1_to_hex(sha1), path); + return 0; /* keep checking other objects */ + } + + obj->flags = HAS_OBJ; + if (fsck_obj(obj)) errors_found |= ERROR_OBJECT; return 0; } @@ -584,6 +619,29 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } +static void mark_object_for_connectivity(const unsigned char *sha1) +{ + struct object *obj = lookup_unknown_object(sha1); + obj->flags |= HAS_OBJ; +} + +static int mark_loose_for_connectivity(const unsigned char *sha1, + const char *path, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + +static int mark_packed_for_connectivity(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + static char const * const fsck_usage[] = { N_("git fsck [<options>] [<object>...]"), NULL @@ -640,38 +698,41 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) git_config(fsck_config, NULL); fsck_head_link(); - if (!connectivity_only) { + if (connectivity_only) { + for_each_loose_object(mark_loose_for_connectivity, NULL, 0); + for_each_packed_object(mark_packed_for_connectivity, NULL, 0); + } else { fsck_object_dir(get_object_directory()); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) fsck_object_dir(alt->path); - } - if (check_full) { - struct packed_git *p; - uint32_t total = 0, count = 0; - struct progress *progress = NULL; + if (check_full) { + struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; - prepare_packed_git(); + prepare_packed_git(); - if (show_progress) { + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress(_("Checking objects"), total); + } for (p = packed_git; p; p = p->next) { - if (open_pack_index(p)) - continue; - total += p->num_objects; + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } - - progress = start_progress(_("Checking objects"), total); + stop_progress(&progress); } - for (p = packed_git; p; p = p->next) { - /* verify gives error messages itself */ - if (verify_pack(p, fsck_obj_buffer, - progress, count)) - errors_found |= ERROR_PACK; - count += p->num_objects; - } - stop_progress(&progress); } heads = 0; @@ -681,9 +742,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *obj = lookup_object(sha1); - /* Error is printed by lookup_object(). */ - if (!obj) + if (!obj || !(obj->flags & HAS_OBJ)) { + error("%s: object missing", sha1_to_hex(sha1)); + errors_found |= ERROR_OBJECT; continue; + } obj->used = 1; if (name_objects) @@ -694,6 +757,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; } error("invalid parameter: expected sha1, got '%s'", arg); + errors_found |= ERROR_OBJECT; } /* @@ -701,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) * default ones from .git/refs. We also consider the index file * in this case (ie this implies --cache). */ - if (!heads) { + if (!argc) { get_default_heads(); keep_cache_objects = 1; } diff --git a/builtin/gc.c b/builtin/gc.c index 1e40d45aa2..c2c61a57bb 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -33,6 +33,8 @@ static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; +static unsigned long gc_log_expire_time; +static const char *gc_log_expire = "1.day.ago"; static const char *prune_expire = "2.weeks.ago"; static const char *prune_worktrees_expire = "3.months.ago"; @@ -65,10 +67,28 @@ static void report_pack_garbage(unsigned seen_bits, const char *path) static void process_log_file(void) { struct stat st; - if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size) + if (fstat(get_lock_file_fd(&log_lock), &st)) { + /* + * Perhaps there was an i/o error or another + * unlikely situation. Try to make a note of + * this in gc.log along with any existing + * messages. + */ + int saved_errno = errno; + fprintf(stderr, _("Failed to fstat %s: %s"), + get_tempfile_path(&log_lock.tempfile), + strerror(saved_errno)); + fflush(stderr); commit_lock_file(&log_lock); - else + errno = saved_errno; + } else if (st.st_size) { + /* There was some error recorded in the lock file */ + commit_lock_file(&log_lock); + } else { + /* No error, clean up any old gc.log */ + unlink(git_path("gc.log")); rollback_lock_file(&log_lock); + } } static void process_log_file_at_exit(void) @@ -102,6 +122,8 @@ static void gc_config(void) git_config_get_bool("gc.autodetach", &detach_auto); git_config_get_expiry("gc.pruneexpire", &prune_expire); git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); + git_config_get_expiry("gc.logexpiry", &gc_log_expire); + git_config(git_default_config, NULL); } @@ -180,6 +202,11 @@ static void add_repack_all_option(void) } } +static void add_repack_incremental_option(void) +{ + argv_array_push(&repack, "--no-write-bitmap-index"); +} + static int need_to_gc(void) { /* @@ -197,7 +224,9 @@ static int need_to_gc(void) */ if (too_many_packs()) add_repack_all_option(); - else if (!too_many_loose_objects()) + else if (too_many_loose_objects()) + add_repack_incremental_option(); + else return 0; if (run_hook_le(NULL, "pre-auto-gc", NULL)) @@ -272,19 +301,34 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) static int report_last_gc_error(void) { struct strbuf sb = STRBUF_INIT; - int ret; + int ret = 0; + struct stat st; + char *gc_log_path = git_pathdup("gc.log"); + + if (stat(gc_log_path, &st)) { + if (errno == ENOENT) + goto done; + + ret = error_errno(_("Can't stat %s"), gc_log_path); + goto done; + } + + if (st.st_mtime < gc_log_expire_time) + goto done; - ret = strbuf_read_file(&sb, git_path("gc.log"), 0); + ret = strbuf_read_file(&sb, gc_log_path, 0); if (ret > 0) - return error(_("The last gc run reported the following. " + ret = error(_("The last gc run reported the following. " "Please correct the root cause\n" "and remove %s.\n" "Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), - git_path("gc.log"), sb.buf); + gc_log_path, sb.buf); strbuf_release(&sb); - return 0; +done: + free(gc_log_path); + return ret; } static int gc_before_repack(void) @@ -331,7 +375,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); + /* default expiry time, overwritten in gc_config */ gc_config(); + if (parse_expiry_date(gc_log_expire, &gc_log_expire_time)) + die(_("Failed to parse gc.logexpiry value %s"), gc_log_expire); if (pack_refs < 0) pack_refs = !is_bare_repository(); @@ -430,5 +477,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) warning(_("There are too many unreachable loose objects; " "run 'git prune' to remove them.")); + if (!daemonized) + unlink(git_path("gc.log")); + return 0; } diff --git a/builtin/grep.c b/builtin/grep.c index 8887b6addb..9304c33e75 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -18,12 +18,22 @@ #include "quote.h" #include "dir.h" #include "pathspec.h" +#include "submodule.h" +#include "submodule-config.h" static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; +static const char *super_prefix; +static int recurse_submodules; +static struct argv_array submodule_options = ARGV_ARRAY_INIT; +static const char *parent_basename; + +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs); + #define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; @@ -174,7 +184,10 @@ static void *run(void *arg) break; opt->output_priv = w; - hit |= grep_source(opt, &w->source); + if (w->source.type == GREP_SOURCE_SUBMODULE) + hit |= grep_submodule_launch(opt, &w->source); + else + hit |= grep_source(opt, &w->source); grep_source_clear_data(&w->source); work_done(w); } @@ -300,6 +313,10 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, if (opt->relative && opt->prefix_length) { quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); + } else if (super_prefix) { + strbuf_add(&pathbuf, filename, tree_name_len); + strbuf_addstr(&pathbuf, super_prefix); + strbuf_addstr(&pathbuf, filename + tree_name_len); } else { strbuf_addstr(&pathbuf, filename); } @@ -328,10 +345,13 @@ static int grep_file(struct grep_opt *opt, const char *filename) { struct strbuf buf = STRBUF_INIT; - if (opt->relative && opt->prefix_length) + if (opt->relative && opt->prefix_length) { quote_path_relative(filename, opt->prefix, &buf); - else + } else { + if (super_prefix) + strbuf_addstr(&buf, super_prefix); strbuf_addstr(&buf, filename); + } #ifndef NO_PTHREADS if (num_threads) { @@ -378,31 +398,310 @@ static void run_pager(struct grep_opt *opt, const char *prefix) exit(status); } -static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) +static void compile_submodule_options(const struct grep_opt *opt, + const struct pathspec *pathspec, + int cached, int untracked, + int opt_exclude, int use_index, + int pattern_type_arg) +{ + struct grep_pat *pattern; + int i; + + if (recurse_submodules) + argv_array_push(&submodule_options, "--recurse-submodules"); + + if (cached) + argv_array_push(&submodule_options, "--cached"); + if (!use_index) + argv_array_push(&submodule_options, "--no-index"); + if (untracked) + argv_array_push(&submodule_options, "--untracked"); + if (opt_exclude > 0) + argv_array_push(&submodule_options, "--exclude-standard"); + + if (opt->invert) + argv_array_push(&submodule_options, "-v"); + if (opt->ignore_case) + argv_array_push(&submodule_options, "-i"); + if (opt->word_regexp) + argv_array_push(&submodule_options, "-w"); + switch (opt->binary) { + case GREP_BINARY_NOMATCH: + argv_array_push(&submodule_options, "-I"); + break; + case GREP_BINARY_TEXT: + argv_array_push(&submodule_options, "-a"); + break; + default: + break; + } + if (opt->allow_textconv) + argv_array_push(&submodule_options, "--textconv"); + if (opt->max_depth != -1) + argv_array_pushf(&submodule_options, "--max-depth=%d", + opt->max_depth); + if (opt->linenum) + argv_array_push(&submodule_options, "-n"); + if (!opt->pathname) + argv_array_push(&submodule_options, "-h"); + if (!opt->relative) + argv_array_push(&submodule_options, "--full-name"); + if (opt->name_only) + argv_array_push(&submodule_options, "-l"); + if (opt->unmatch_name_only) + argv_array_push(&submodule_options, "-L"); + if (opt->null_following_name) + argv_array_push(&submodule_options, "-z"); + if (opt->count) + argv_array_push(&submodule_options, "-c"); + if (opt->file_break) + argv_array_push(&submodule_options, "--break"); + if (opt->heading) + argv_array_push(&submodule_options, "--heading"); + if (opt->pre_context) + argv_array_pushf(&submodule_options, "--before-context=%d", + opt->pre_context); + if (opt->post_context) + argv_array_pushf(&submodule_options, "--after-context=%d", + opt->post_context); + if (opt->funcname) + argv_array_push(&submodule_options, "-p"); + if (opt->funcbody) + argv_array_push(&submodule_options, "-W"); + if (opt->all_match) + argv_array_push(&submodule_options, "--all-match"); + if (opt->debug) + argv_array_push(&submodule_options, "--debug"); + if (opt->status_only) + argv_array_push(&submodule_options, "-q"); + + switch (pattern_type_arg) { + case GREP_PATTERN_TYPE_BRE: + argv_array_push(&submodule_options, "-G"); + break; + case GREP_PATTERN_TYPE_ERE: + argv_array_push(&submodule_options, "-E"); + break; + case GREP_PATTERN_TYPE_FIXED: + argv_array_push(&submodule_options, "-F"); + break; + case GREP_PATTERN_TYPE_PCRE: + argv_array_push(&submodule_options, "-P"); + break; + case GREP_PATTERN_TYPE_UNSPECIFIED: + break; + } + + for (pattern = opt->pattern_list; pattern != NULL; + pattern = pattern->next) { + switch (pattern->token) { + case GREP_PATTERN: + argv_array_pushf(&submodule_options, "-e%s", + pattern->pattern); + break; + case GREP_AND: + case GREP_OPEN_PAREN: + case GREP_CLOSE_PAREN: + case GREP_NOT: + case GREP_OR: + argv_array_push(&submodule_options, pattern->pattern); + break; + /* BODY and HEAD are not used by git-grep */ + case GREP_PATTERN_BODY: + case GREP_PATTERN_HEAD: + break; + } + } + + /* + * Limit number of threads for child process to use. + * This is to prevent potential fork-bomb behavior of git-grep as each + * submodule process has its own thread pool. + */ + argv_array_pushf(&submodule_options, "--threads=%d", + (num_threads + 1) / 2); + + /* Add Pathspecs */ + argv_array_push(&submodule_options, "--"); + for (i = 0; i < pathspec->nr; i++) + argv_array_push(&submodule_options, + pathspec->items[i].original); +} + +/* + * Launch child process to grep contents of a submodule + */ +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status, i; + const char *end_of_base; + const char *name; + struct work_item *w = opt->output_priv; + + end_of_base = strchr(gs->name, ':'); + if (gs->identifier && end_of_base) + name = end_of_base + 1; + else + name = gs->name; + + prepare_submodule_repo_env(&cp.env_array); + argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT); + + /* Add super prefix */ + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + super_prefix ? super_prefix : "", + name); + argv_array_push(&cp.args, "grep"); + + /* + * Add basename of parent project + * When performing grep on a tree object the filename is prefixed + * with the object's name: 'tree-name:filename'. In order to + * provide uniformity of output we want to pass the name of the + * parent project's object name to the submodule so the submodule can + * prefix its output with the parent's name and not its own SHA1. + */ + if (gs->identifier && end_of_base) + argv_array_pushf(&cp.args, "--parent-basename=%.*s", + (int) (end_of_base - gs->name), + gs->name); + + /* Add options */ + for (i = 0; i < submodule_options.argc; i++) { + /* + * If there is a tree identifier for the submodule, add the + * rev after adding the submodule options but before the + * pathspecs. To do this we listen for the '--' and insert the + * sha1 before pushing the '--' onto the child process argv + * array. + */ + if (gs->identifier && + !strcmp("--", submodule_options.argv[i])) { + argv_array_push(&cp.args, sha1_to_hex(gs->identifier)); + } + + argv_array_push(&cp.args, submodule_options.argv[i]); + } + + cp.git_cmd = 1; + cp.dir = gs->path; + + /* + * Capture output to output buffer and check the return code from the + * child process. A '0' indicates a hit, a '1' indicates no hit and + * anything else is an error. + */ + status = capture_command(&cp, &w->out, 0); + if (status && (status != 1)) { + /* flush the buffer */ + write_or_die(1, w->out.buf, w->out.len); + die("process for submodule '%s' failed with exit code: %d", + gs->name, status); + } + + /* invert the return code to make a hit equal to 1 */ + return !status; +} + +/* + * Prep grep structures for a submodule grep + * sha1: the sha1 of the submodule or NULL if using the working tree + * filename: name of the submodule including tree name of parent + * path: location of the submodule + */ +static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, + const char *filename, const char *path) +{ + if (!is_submodule_initialized(path)) + return 0; + if (!is_submodule_populated(path)) { + /* + * If searching history, check for the presense of the + * submodule's gitdir before skipping the submodule. + */ + if (sha1) { + const struct submodule *sub = + submodule_from_path(null_sha1, path); + if (sub) + path = git_path("modules/%s", sub->name); + + if (!(is_directory(path) && is_git_directory(path))) + return 0; + } else { + return 0; + } + } + +#ifndef NO_PTHREADS + if (num_threads) { + add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1); + return 0; + } else +#endif + { + struct work_item w; + int hit; + + grep_source_init(&w.source, GREP_SOURCE_SUBMODULE, + filename, path, sha1); + strbuf_init(&w.out, 0); + opt->output_priv = &w; + hit = grep_submodule_launch(opt, &w.source); + + write_or_die(1, w.out.buf, w.out.len); + + grep_source_clear(&w.source); + strbuf_release(&w.out); + return hit; + } +} + +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, + int cached) { int hit = 0; int nr; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + name_base_len = strlen(super_prefix); + strbuf_addstr(&name, super_prefix); + } + read_cache(); for (nr = 0; nr < active_nr; nr++) { const struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!ce_path_match(ce, pathspec, NULL)) + strbuf_setlen(&name, name_base_len); + strbuf_addstr(&name, ce->name); + + if (S_ISREG(ce->ce_mode) && + match_pathspec(pathspec, name.buf, name.len, 0, NULL, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) { + /* + * If CE_VALID is on, we assume worktree file and its + * cache entry are identical, even if worktree file has + * been modified, so use cache version instead + */ + if (cached || (ce->ce_flags & CE_VALID) || + ce_skip_worktree(ce)) { + if (ce_stage(ce) || ce_intent_to_add(ce)) + continue; + hit |= grep_sha1(opt, ce->oid.hash, ce->name, + 0, ce->name); + } else { + hit |= grep_file(opt, ce->name); + } + } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(pathspec, name.buf, NULL)) { + hit |= grep_submodule(opt, NULL, ce->name, ce->name); + } else { continue; - /* - * If CE_VALID is on, we assume worktree file and its cache entry - * are identical, even if worktree file has been modified, so use - * cache version instead - */ - if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce) || ce_intent_to_add(ce)) - continue; - hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0, - ce->name); } - else - hit |= grep_file(opt, ce->name); + if (ce_stage(ce)) { do { nr++; @@ -413,6 +712,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -424,12 +725,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, enum interesting match = entry_not_interesting; struct name_entry entry; int old_baselen = base->len; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + strbuf_addstr(&name, super_prefix); + name_base_len = name.len; + } while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(&entry); if (match != all_entries_interesting) { - match = tree_entry_interesting(&entry, base, tn_len, pathspec); + strbuf_addstr(&name, base->buf + tn_len); + match = tree_entry_interesting(&entry, &name, + 0, pathspec); + strbuf_setlen(&name, name_base_len); + if (match == all_entries_not_interesting) break; if (match == entry_not_interesting) @@ -441,8 +752,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (S_ISREG(entry.mode)) { hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); - } - else if (S_ISDIR(entry.mode)) { + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; void *data; @@ -458,12 +768,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, hit |= grep_tree(opt, pathspec, &sub, base, tn_len, check_attr); free(data); + } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { + hit |= grep_submodule(opt, entry.oid->hash, base->buf, + base->buf + tn_len); } + strbuf_setlen(base, old_baselen); if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -487,6 +803,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); + /* Use parent's name as base when recursing submodules */ + if (recurse_submodules && parent_basename) + name = parent_basename; + len = name ? strlen(name) : 0; strbuf_init(&base, PATH_MAX + len + 1); if (len) { @@ -513,6 +833,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); + + /* load the gitmodules file for this rev */ + if (recurse_submodules) { + submodule_free(); + gitmodules_config_sha1(real_obj->oid.hash); + } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { hit = 1; if (opt->status_only) @@ -641,6 +967,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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, @@ -651,6 +978,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, N_("ignore files specified via '.gitignore'"), 1), + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("recursivley search in each submodule")), + OPT_STRING(0, "parent-basename", &parent_basename, + N_("basename"), + N_("prepend parent project's basename to output")), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -755,6 +1087,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); + super_prefix = get_super_prefix(); /* * If there is no -- then the paths must exist in the working @@ -817,26 +1150,69 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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_context oc; - /* Is it a rev? */ - if (!get_sha1_with_context(arg, 0, sha1, &oc)) { - struct object *object = parse_object_or_die(sha1, 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 (seen_dashdash) + die(_("unable to resolve revision: %s"), arg); + break; + } + + object = parse_object_or_die(sha1, 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; @@ -858,20 +1234,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } #endif - /* The rest are paths */ - if (!seen_dashdash) { - int j; - for (j = i; j < argc; j++) - verify_filename(prefix, argv[j], j == i); + if (recurse_submodules) { + gitmodules_config(); + compile_submodule_options(&opt, &pathspec, cached, untracked, + opt_exclude, use_index, + pattern_type_arg); } - 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 (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); @@ -895,6 +1264,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } + if (recurse_submodules && (!use_index || untracked)) + die(_("option not supported with --recurse-submodules.")); + if (!show_in_pager && !opt.status_only) setup_pager(); @@ -903,8 +1275,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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 --git a/builtin/init-db.c b/builtin/init-db.c index 2399b97d90..8a6acb0ec6 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -262,7 +262,7 @@ static int create_default_files(const char *template_path, const char *work_tree = get_git_work_tree(); git_config_set("core.bare", "false"); /* allow template config file to override the default */ - if (log_all_ref_updates == -1) + if (log_all_ref_updates == LOG_REFS_UNSET) git_config_set("core.logallrefupdates", "true"); if (needs_work_tree_config(original_git_dir, work_tree)) git_config_set("core.worktree", work_tree); @@ -338,7 +338,7 @@ int init_db(const char *git_dir, const char *real_git_dir, { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; - char *original_git_dir = xstrdup(real_path(git_dir)); + char *original_git_dir = real_pathdup(git_dir, 1); if (real_git_dir) { struct stat st; @@ -489,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); if (real_git_dir && !is_absolute_path(real_git_dir)) - real_git_dir = xstrdup(real_path(real_git_dir)); + real_git_dir = real_pathdup(real_git_dir, 1); if (argc == 1) { int mkdir_tried = 0; @@ -560,7 +560,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *git_dir_parent = strrchr(git_dir, '/'); if (git_dir_parent) { char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(real_path(rel)); + git_work_tree_cfg = real_pathdup(rel, 1); free(rel); } if (!git_work_tree_cfg) diff --git a/builtin/log.c b/builtin/log.c index 55d20cc2d8..281af8c1ec 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -989,8 +989,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; - log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, - &need_8bit_cte); + log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte); for (i = 0; !need_8bit_cte && i < nr; i++) { const char *buf = get_commit_buffer(list[i], NULL); @@ -1005,6 +1004,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; + pp.rev = rev; + pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 1592290815..1c0f057d02 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -369,28 +369,30 @@ static void show_files(struct dir_struct *dir) /* * Prune the index to only contain stuff starting with "prefix" */ -static void prune_cache(const char *prefix) +static void prune_cache(const char *prefix, size_t prefixlen) { - int pos = cache_name_pos(prefix, max_prefix_len); + int pos; unsigned int first, last; + if (!prefix) + return; + pos = cache_name_pos(prefix, prefixlen); if (pos < 0) pos = -pos-1; - memmove(active_cache, active_cache + pos, - (active_nr - pos) * sizeof(struct cache_entry *)); - active_nr -= pos; - first = 0; + first = pos; last = active_nr; while (last > first) { int next = (last + first) >> 1; const struct cache_entry *ce = active_cache[next]; - if (!strncmp(ce->name, prefix, max_prefix_len)) { + if (!strncmp(ce->name, prefix, prefixlen)) { first = next+1; continue; } last = next; } - active_nr = last; + memmove(active_cache, active_cache + pos, + (last - pos) * sizeof(struct cache_entry *)); + active_nr = last - pos; } /* @@ -641,8 +643,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) show_killed || show_modified || show_resolve_undo)) show_cached = 1; - if (max_prefix) - prune_cache(max_prefix); + prune_cache(max_prefix, max_prefix_len); if (with_tree) { /* * Basic sanity check; show-stages and show-unmerged diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 0e30d86230..d7ebeb4ce6 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -31,21 +31,18 @@ static const char * const ls_tree_usage[] = { static int show_recursive(const char *base, int baselen, const char *pathname) { - const char **s; + int i; if (ls_options & LS_RECURSIVE) return 1; - s = pathspec._raw; - if (!s) + if (!pathspec.nr) return 0; - for (;;) { - const char *spec = *s++; + for (i = 0; i < pathspec.nr; i++) { + const char *spec = pathspec.items[i].match; int len, speclen; - if (!spec) - return 0; if (strncmp(base, spec, baselen)) continue; len = strlen(pathname); @@ -59,6 +56,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) continue; return 1; } + return 0; } static int show_tree(const unsigned char *sha1, struct strbuf *base, @@ -175,8 +173,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec() or tree_entry_interesting() */ - parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE, + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) diff --git a/builtin/merge-index.c b/builtin/merge-index.c index ce356b1da1..2d1b6db6bd 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -22,7 +22,7 @@ static int merge_entry(int pos, const char *path) if (strcmp(ce->name, path)) break; found++; - sha1_to_hex_r(hexbuf[stage], ce->oid.hash); + oid_to_hex_r(hexbuf[stage], &ce->oid); xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; diff --git a/builtin/merge.c b/builtin/merge.c index 0070bf2556..848a298556 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = { N_("git merge [<options>] [<commit>...]"), N_("git merge [<options>] <msg> HEAD <commit>"), N_("git merge --abort"), + N_("git merge --continue"), NULL }; @@ -65,6 +66,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; @@ -223,6 +225,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "continue", &continue_current_merge, + N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), @@ -434,7 +438,7 @@ static void merge_name(const char *remote, struct strbuf *msg) 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)); @@ -1125,6 +1129,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list *remoteheads, *p; void *branch_to_free; + int orig_argc = argc; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1158,6 +1163,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + if (orig_argc != 2) + usage_msg_opt(_("--abort expects no arguments"), + builtin_merge_usage, builtin_merge_options); + if (!file_exists(git_path_merge_head())) die(_("There is no merge to abort (MERGE_HEAD missing).")); @@ -1166,6 +1175,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (continue_current_merge) { + int nargc = 1; + const char *nargv[] = {"commit", NULL}; + + if (orig_argc != 2) + usage_msg_opt(_("--continue expects no arguments"), + builtin_merge_usage, builtin_merge_options); + + if (!file_exists(git_path_merge_head())) + die(_("There is no merge in progress (MERGE_HEAD missing).")); + + /* Invoke 'git commit' */ + ret = cmd_commit(nargc, nargv, prefix); + goto done; + } + if (read_cache_unmerged()) die_resolve_conflict("merge"); diff --git a/builtin/mv.c b/builtin/mv.c index 43adf92ba6..61d20037ad 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Johannes Schindelin */ #include "builtin.h" +#include "pathspec.h" #include "lockfile.h" #include "dir.h" #include "cache-tree.h" @@ -19,31 +20,42 @@ static const char * const builtin_mv_usage[] = { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_copy_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static const char **internal_prefix_pathspec(const char *prefix, + const char **pathspec, + int count, unsigned flags) { int i; const char **result; + int prefixlen = prefix ? strlen(prefix) : 0; ALLOC_ARRAY(result, count + 1); - COPY_ARRAY(result, pathspec, count); - result[count] = NULL; + + /* Create an intermediate copy of the pathspec based on the flags */ for (i = 0; i < count; i++) { - int length = strlen(result[i]); + int length = strlen(pathspec[i]); int to_copy = length; + char *it; while (!(flags & KEEP_TRAILING_SLASH) && - to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) + to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - if (to_copy != length || flags & DUP_BASENAME) { - char *it = xmemdupz(result[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else - result[i] = it; + + it = xmemdupz(pathspec[i], to_copy); + if (flags & DUP_BASENAME) { + result[i] = xstrdup(basename(it)); + free(it); + } else { + result[i] = it; } } - return get_pathspec(prefix, result); + result[count] = NULL; + + /* Prefix the pathspec and free the old intermediate strings */ + for (i = 0; i < count; i++) { + const char *match = prefix_path(prefix, prefixlen, result[i]); + free((char *) result[i]); + result[i] = match; + } + + return result; } static const char *add_slash(const char *path) @@ -130,7 +142,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - source = internal_copy_pathspec(prefix, argv, argc, 0); + source = internal_prefix_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); /* * Keep trailing slash, needed to let @@ -140,16 +152,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_copy_pathspec(prefix, argv + argc, 1, flags); + dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); } else { if (argc != 1) die(_("destination '%s' is not a directory"), dest_path[0]); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index cd89d48b65..8bdc3eaa6f 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -108,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) struct name_ref_data { int tags_only; int name_only; - const char *ref_filter; + struct string_list ref_filters; + struct string_list exclude_filters; }; static struct tip_table { @@ -150,18 +151,49 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; - if (data->ref_filter) { - switch (subpath_matches(path, data->ref_filter)) { - case -1: /* did not match */ - return 0; - case 0: /* matched fully */ - break; - default: /* matched subpath */ - can_abbreviate_output = 1; - break; + if (data->exclude_filters.nr) { + struct string_list_item *item; + + for_each_string_list_item(item, &data->exclude_filters) { + if (subpath_matches(path, item->string) >= 0) + return 0; } } + if (data->ref_filters.nr) { + struct string_list_item *item; + int matched = 0; + + /* See if any of the patterns match. */ + for_each_string_list_item(item, &data->ref_filters) { + /* + * Check all patterns even after finding a match, so + * that we can see if a match with a subpath exists. + * When a user asked for 'refs/tags/v*' and 'v1.*', + * both of which match, the user is showing her + * willingness to accept a shortened output by having + * the 'v1.*' in the acceptable refnames, so we + * shouldn't stop when seeing 'refs/tags/v1.4' matches + * 'refs/tags/v*'. We should show it as 'v1.4'. + */ + switch (subpath_matches(path, item->string)) { + case -1: /* did not match */ + break; + case 0: /* matched fully */ + matched = 1; + break; + default: /* matched subpath */ + matched = 1; + can_abbreviate_output = 1; + break; + } + } + + /* If none of the patterns matched, stop now */ + if (!matched) + return 0; + } + add_to_tip_table(oid->hash, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { @@ -306,12 +338,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; - struct name_ref_data data = { 0, 0, NULL }; + struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), - OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"), + OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"), N_("only use refs matching <pattern>")), + OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"), + N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), diff --git a/builtin/notes.c b/builtin/notes.c index 5248a9bad8..4b492abd41 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -681,9 +681,9 @@ static int merge_abort(struct notes_merge_options *o) * 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")); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0fd52bd6b4..f294dcffa9 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -61,8 +61,6 @@ static int delta_search_threads; static int pack_to_stdout; static int num_preferred_base; static struct progress *progress_state; -static int pack_compression_level = Z_DEFAULT_COMPRESSION; -static int pack_compression_seen; static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; @@ -896,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; } @@ -1541,6 +1530,8 @@ static int pack_offset_sort(const void *_a, const void *_b) * 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) { @@ -1554,6 +1545,7 @@ static void drop_reused_delta(struct object_entry *entry) p = &(*p)->delta_sibling; } entry->delta = NULL; + entry->depth = 0; oi.sizep = &entry->size; oi.typep = &entry->type; @@ -1572,39 +1564,123 @@ static void drop_reused_delta(struct object_entry *entry) * 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; + } + + /* + * 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; + } + } - case DFS_DONE: - /* object already examined, and not part of a cycle */ - 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; - 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. + * 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. */ - drop_reused_delta(entry); - entry->dfs_state = DFS_DONE; - break; + 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); + + /* + * 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. + */ + cur->depth = (total_depth--) % (depth + 1); + if (!cur->depth) + drop_reused_delta(cur); + + cur->dfs_state = DFS_DONE; } } @@ -2368,16 +2444,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) depth = git_config_int(k, v); return 0; } - if (!strcmp(k, "pack.compression")) { - int level = git_config_int(k, v); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - pack_compression_seen = 1; - return 0; - } if (!strcmp(k, "pack.deltacachesize")) { max_delta_cache_size = git_config_int(k, v); return 0; @@ -2869,8 +2935,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, diff --git a/builtin/push.c b/builtin/push.c index 3bb9d6b7e6..5c22e9f2e5 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -194,15 +194,18 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, die_push_simple(branch, remote); } - strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); add_refspec(refspec.buf); } static void setup_push_current(struct remote *remote, struct branch *branch) { + struct strbuf refspec = STRBUF_INIT; + if (!branch) die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); + add_refspec(refspec.buf); } static int is_workflow_triangular(struct remote *remote) @@ -565,6 +568,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index fa6edb35b2..8ba64bc785 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -109,34 +109,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"), N_("write resulting index to <file>"), PARSE_OPT_NONEG, index_output_cb }, - OPT_SET_INT(0, "empty", &read_empty, - N_("only empty the index"), 1), + OPT_BOOL(0, "empty", &read_empty, + N_("only empty the index")), OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), OPT_GROUP(N_("Merging")), - OPT_SET_INT('m', NULL, &opts.merge, - N_("perform a merge in addition to a read"), 1), - OPT_SET_INT(0, "trivial", &opts.trivial_merges_only, - N_("3-way merge if no file level merging required"), 1), - OPT_SET_INT(0, "aggressive", &opts.aggressive, - N_("3-way merge in presence of adds and removes"), 1), - OPT_SET_INT(0, "reset", &opts.reset, - N_("same as -m, but discard unmerged entries"), 1), + OPT_BOOL('m', NULL, &opts.merge, + N_("perform a merge in addition to a read")), + OPT_BOOL(0, "trivial", &opts.trivial_merges_only, + N_("3-way merge if no file level merging required")), + OPT_BOOL(0, "aggressive", &opts.aggressive, + N_("3-way merge in presence of adds and removes")), + OPT_BOOL(0, "reset", &opts.reset, + N_("same as -m, but discard unmerged entries")), { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), N_("read the tree into the index under <subdirectory>/"), PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, - OPT_SET_INT('u', NULL, &opts.update, - N_("update working tree with merge result"), 1), + OPT_BOOL('u', NULL, &opts.update, + N_("update working tree with merge result")), { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), PARSE_OPT_NONEG, exclude_per_directory_cb }, - OPT_SET_INT('i', NULL, &opts.index_only, - N_("don't check the working tree after merging"), 1), + OPT_BOOL('i', NULL, &opts.index_only, + N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), - OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, - N_("skip applying sparse checkout filter"), 1), - OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, - N_("debug unpack-trees"), 1), + OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + N_("skip applying sparse checkout filter")), + OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, + N_("debug unpack-trees")), OPT_END() }; diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c new file mode 100644 index 0000000000..ca1ebb2fa1 --- /dev/null +++ b/builtin/rebase--helper.c @@ -0,0 +1,40 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "sequencer.h" + +static const char * const builtin_rebase_helper_usage[] = { + N_("git rebase--helper [<options>]"), + NULL +}; + +int cmd_rebase__helper(int argc, const char **argv, const char *prefix) +{ + struct replay_opts opts = REPLAY_OPTS_INIT; + enum { + CONTINUE = 1, ABORT + } command = 0; + struct option options[] = { + OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), + CONTINUE), + OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), + ABORT), + OPT_END() + }; + + git_config(git_default_config, NULL); + + opts.action = REPLAY_INTERACTIVE_REBASE; + opts.allow_ff = 1; + opts.allow_empty = 1; + + argc = parse_options(argc, argv, NULL, options, + builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); + + if (command == CONTINUE && argc == 1) + return !!sequencer_continue(&opts); + if (command == ABORT && argc == 1) + return !!sequencer_remove_state(&opts); + usage_with_options(builtin_rebase_helper_usage, options); +} diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 6b97cbdbe9..f2c6953a39 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -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 <git-dir>"), @@ -250,8 +251,9 @@ static void show_ref(const char *path, const unsigned char *sha1) } 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)) @@ -260,37 +262,38 @@ static int show_ref_cb(const char *path_full, const struct object_id *oid, /* * 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); @@ -1664,8 +1667,11 @@ static const char *unpack(int err_fd, struct shallow_info *si) } 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); /* @@ -1942,8 +1948,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) run_receive_hook(commands, "post-receive", 1, &push_options); run_update_post_hook(commands); - if (push_options.nr) - string_list_clear(&push_options, 0); + string_list_clear(&push_options, 0); if (auto_gc) { const char *argv_gc_auto[] = { "gc", "--auto", "--quiet", NULL, diff --git a/builtin/remote.c b/builtin/remote.c index e52cf3925b..addf97ad29 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -186,7 +186,7 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote_is_configured(remote)) + if (remote_is_configured(remote, 1)) die(_("remote %s already exists."), name); strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); @@ -618,14 +618,14 @@ static int mv(int argc, const char **argv) rename.remote_branches = &remote_branches; oldremote = remote_get(rename.old); - if (!remote_is_configured(oldremote)) + if (!remote_is_configured(oldremote, 1)) die(_("No such remote: %s"), rename.old); if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new); - if (remote_is_configured(newremote)) + if (remote_is_configured(newremote, 1)) die(_("remote %s already exists."), rename.new); strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); @@ -691,7 +691,7 @@ static int mv(int argc, const char **argv) read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag); if (!(flag & REF_ISSYMREF)) continue; - if (delete_ref(item->string, NULL, REF_NODEREF)) + if (delete_ref(NULL, item->string, NULL, REF_NODEREF)) die(_("deleting '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { @@ -753,7 +753,7 @@ static int rm(int argc, const char **argv) usage_with_options(builtin_remote_rm_usage, options); remote = remote_get(argv[1]); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote: %s"), argv[1]); known_remotes.to_delete = remote; @@ -769,7 +769,9 @@ static int rm(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.%s", item->string, *k); - git_config_set(buf.buf, NULL); + result = git_config_set_gently(buf.buf, NULL); + if (result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), buf.buf); } } } @@ -1248,7 +1250,7 @@ static int set_head(int argc, const char **argv) head_name = xstrdup(states.heads.items[0].string); free_remote_ref_states(&states); } else if (opt_d && !opt_a && argc == 1) { - if (delete_ref(buf.buf, NULL, REF_NODEREF)) + if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF)) result |= error(_("Could not delete %s"), buf.buf); } else usage_with_options(builtin_remote_sethead_usage, options); @@ -1415,7 +1417,7 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { @@ -1469,7 +1471,7 @@ static int get_url(int argc, const char **argv) remotename = argv[0]; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); url_nr = 0; @@ -1537,7 +1539,7 @@ static int set_url(int argc, const char **argv) oldurl = newurl; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (push_mode) { diff --git a/builtin/repack.c b/builtin/repack.c index 80dd06b4a2..677bc7c81a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -18,6 +18,12 @@ static const char *const git_repack_usage[] = { NULL }; +static const char incremental_bitmap_conflict_error[] = N_( +"Incremental repacks are incompatible with bitmap indexes. Use\n" +"--no-write-bitmap-index or disable the pack.writebitmaps configuration." +); + + static int repack_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "repack.usedeltabaseoffset")) { @@ -206,6 +212,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) + die(_(incremental_bitmap_conflict_error)); + packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); diff --git a/builtin/replace.c b/builtin/replace.c index b58c714cb8..226d0f9523 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -121,7 +121,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) static int delete_replace_ref(const char *name, const char *ref, const unsigned char *sha1) { - if (delete_ref(ref, sha1, 0)) + if (delete_ref(NULL, ref, sha1, 0)) return 1; printf("Deleted replace ref '%s'\n", name); return 0; diff --git a/builtin/reset.c b/builtin/reset.c index 8ab915bfcb..fc3b906c47 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -256,7 +256,7 @@ static int reset_refs(const char *rev, const struct object_id *oid) update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) - delete_ref("ORIG_HEAD", old_orig->hash, 0); + delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0); set_reflog_message(&msg, "updating HEAD", rev); update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0, UPDATE_REFS_MSG_ON_ERR); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index c43decda70..0aa93d5891 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -237,7 +237,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) cnt = reaches; if (revs->commits) - sha1_to_hex_r(hex, revs->commits->item->object.oid.hash); + oid_to_hex_r(hex, &revs->commits->item->object.oid); if (flags & BISECT_SHOW_ALL) { traverse_commit_list(revs, show_commit, show_object, info); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index ff13e59e1d..e08677e559 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -545,6 +545,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) unsigned int flags = 0; const char *name = NULL; struct object_context unused; + struct strbuf buf = STRBUF_INIT; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -599,7 +600,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--git-path")) { if (!argv[i + 1]) die("--git-path requires an argument"); - puts(git_path("%s", argv[i + 1])); + strbuf_reset(&buf); + puts(relative_path(git_path("%s", argv[i + 1]), + prefix, &buf)); i++; continue; } @@ -802,17 +805,27 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) putchar('\n'); continue; } - if (!strcmp(arg, "--git-dir")) { + if (!strcmp(arg, "--git-dir") || + !strcmp(arg, "--absolute-git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); char *cwd; int len; - if (gitdir) { - puts(gitdir); - continue; - } - if (!prefix) { - puts(".git"); - continue; + if (arg[2] == 'g') { /* --git-dir */ + if (gitdir) { + puts(gitdir); + continue; + } + if (!prefix) { + puts(".git"); + continue; + } + } else { /* --absolute-git-dir */ + if (!gitdir && !prefix) + gitdir = ".git"; + if (gitdir) { + puts(real_path(gitdir)); + continue; + } } cwd = xgetcwd(); len = strlen(cwd); @@ -821,8 +834,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--git-common-dir")) { - const char *pfx = prefix ? prefix : ""; - puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir())); + strbuf_reset(&buf); + puts(relative_path(get_git_common_dir(), + prefix, &buf)); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { @@ -845,7 +859,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) die(_("Could not read the index")); if (the_index.split_index) { const unsigned char *sha1 = the_index.split_index->base_sha1; - puts(git_path("sharedindex.%s", sha1_to_hex(sha1))); + const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1)); + strbuf_reset(&buf); + puts(relative_path(path, prefix, &buf)); } continue; } @@ -897,6 +913,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; verify_filename(prefix, arg, 1); } + strbuf_release(&buf); if (verify) { if (revs_count == 1) { show_rev(type, sha1, name); diff --git a/builtin/rm.c b/builtin/rm.c index 7f15a3d7f8..fb79dcab18 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -59,27 +59,9 @@ static void print_error_files(struct string_list *files_list, } } -static void error_removing_concrete_submodules(struct string_list *files, int *errs) -{ - print_error_files(files, - Q_("the following submodule (or one of its nested " - "submodules)\n" - "uses a .git directory:", - "the following submodules (or one of their nested " - "submodules)\n" - "use a .git directory:", files->nr), - _("\n(use 'rm -rf' if you really want to remove " - "it including all of its history)"), - errs); - string_list_clear(files, 0); -} - -static int check_submodules_use_gitfiles(void) +static void submodules_absorb_gitdir_if_needed(const char *prefix) { int i; - int errs = 0; - struct string_list files = STRING_LIST_INIT_NODUP; - for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; @@ -99,12 +81,9 @@ static int check_submodules_use_gitfiles(void) continue; if (!submodule_uses_gitfile(name)) - string_list_append(&files, name); + absorb_git_dir_into_superproject(prefix, name, + ABSORB_GITDIR_RECURSE_SUBMODULES); } - - error_removing_concrete_submodules(&files, &errs); - - return errs; } static int check_local_mod(struct object_id *head, int index_only) @@ -120,7 +99,6 @@ static int check_local_mod(struct object_id *head, int index_only) int errs = 0; struct string_list files_staged = STRING_LIST_INIT_NODUP; struct string_list files_cached = STRING_LIST_INIT_NODUP; - struct string_list files_submodule = STRING_LIST_INIT_NODUP; struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_oid(head); @@ -187,7 +165,9 @@ static int check_local_mod(struct object_id *head, int index_only) */ if (ce_match_stat(ce, &st, 0) || (S_ISGITLINK(ce->ce_mode) && - !ok_to_remove_submodule(ce->name))) + bad_to_remove_submodule(ce->name, + SUBMODULE_REMOVAL_DIE_ON_ERROR | + SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))) local_changes = 1; /* @@ -217,13 +197,8 @@ static int check_local_mod(struct object_id *head, int index_only) else if (!index_only) { if (staged_changes) string_list_append(&files_cached, name); - if (local_changes) { - if (S_ISGITLINK(ce->ce_mode) && - !submodule_uses_gitfile(name)) - string_list_append(&files_submodule, name); - else - string_list_append(&files_local, name); - } + if (local_changes) + string_list_append(&files_local, name); } } print_error_files(&files_staged, @@ -245,8 +220,6 @@ static int check_local_mod(struct object_id *head, int index_only) &errs); string_list_clear(&files_cached, 0); - error_removing_concrete_submodules(&files_submodule, &errs); - print_error_files(&files_local, Q_("the following file has local modifications:", "the following files have local modifications:", @@ -340,6 +313,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) exit(0); } + if (!index_only) + submodules_absorb_gitdir_if_needed(prefix); + /* * If not forced, the file, the index and the HEAD (if exists) * must match; but the file can already been removed, since @@ -356,9 +332,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) oidclr(&oid); if (check_local_mod(&oid, index_only)) exit(1); - } else if (!index_only) { - if (check_submodules_use_gitfiles()) - exit(1); } /* @@ -391,28 +364,15 @@ int cmd_rm(int argc, const char **argv, const char *prefix) for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { - if (is_empty_dir(path)) { - if (!rmdir(path)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - continue; - } - } else { - strbuf_reset(&buf); - strbuf_addstr(&buf, path); - if (!remove_dir_recursively(&buf, 0)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - strbuf_release(&buf); - continue; - } else if (!file_exists(path)) - /* Submodule was removed by user */ - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - /* Fallthrough and let remove_path() fail. */ - } + strbuf_reset(&buf); + strbuf_addstr(&buf, path); + if (remove_dir_recursively(&buf, 0)) + die(_("could not remove '%s'"), path); + + removed = 1; + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; + continue; } if (!remove_path(path)) { removed = 1; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index ba0e1154a9..f78bb4818d 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -117,11 +117,15 @@ static void read_from_stdin(struct shortlog *log) { struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; + static const char *author_match[2] = { "Author: ", "author " }; + static const char *committer_match[2] = { "Commit: ", "committer " }; + const char **match; + match = log->committer ? committer_match : author_match; while (strbuf_getline_lf(&author, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, "Author: ", &v) && - !skip_prefix(author.buf, "author ", &v)) + if (!skip_prefix(author.buf, match[0], &v) && + !skip_prefix(author.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -140,15 +144,18 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; struct pretty_print_context ctx = {0}; + const char *fmt; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; - ctx.subject = ""; + ctx.print_email_subject = 1; ctx.after_subject = ""; ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - format_commit_message(commit, "%an <%ae>", &author, &ctx); + fmt = log->committer ? "%cn <%ce>" : "%an <%ae>"; + + format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); @@ -238,6 +245,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { + OPT_BOOL('c', "committer", &log.committer, + N_("Group by committer rather than author")), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 974f3403ab..19756595d5 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name) pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); pretty_str = pretty.buf; } - if (starts_with(pretty_str, "[PATCH] ")) - pretty_str += 8; + skip_prefix(pretty_str, "[PATCH] ", &pretty_str); if (!no_name) { if (name && name->head_name) { @@ -470,18 +469,14 @@ static void snarf_refs(int head, int remotes) } } -static int rev_is_head(char *head, int headlen, char *name, +static int rev_is_head(const char *head, const char *name, unsigned char *head_sha1, unsigned char *sha1) { - if ((!head[0]) || - (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) + if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; - if (starts_with(head, "refs/heads/")) - head += 11; - if (starts_with(name, "refs/heads/")) - name += 11; - else if (starts_with(name, "heads/")) - name += 6; + skip_prefix(head, "refs/heads/", &head); + if (!skip_prefix(name, "refs/heads/", &name)) + skip_prefix(name, "heads/", &name); return !strcmp(head, name); } @@ -620,9 +615,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int all_heads = 0, all_remotes = 0; int all_mask, all_revs; enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER; - char head[128]; - const char *head_p; - int head_len; + char *head; struct object_id head_oid; int merge_base = 0; int independent = 0; @@ -787,32 +780,24 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) snarf_refs(all_heads, all_remotes); } - head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - head_oid.hash, NULL); - if (head_p) { - head_len = strlen(head_p); - memcpy(head, head_p, head_len + 1); - } - else { - head_len = 0; - head[0] = 0; - } + head = resolve_refdup("HEAD", RESOLVE_REF_READING, + head_oid.hash, NULL); - if (with_current_branch && head_p) { + if (with_current_branch && head) { int has_head = 0; for (i = 0; !has_head && i < ref_name_cnt; i++) { /* We are only interested in adding the branch * HEAD points at. */ if (rev_is_head(head, - head_len, ref_name[i], head_oid.hash, NULL)) has_head++; } if (!has_head) { - int offset = starts_with(head, "refs/heads/") ? 11 : 0; - append_one_rev(head + offset); + const char *name = head; + skip_prefix(name, "refs/heads/", &name); + append_one_rev(name); } } @@ -866,7 +851,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (i = 0; i < num_rev; i++) { int j; int is_head = rev_is_head(head, - head_len, ref_name[i], head_oid.hash, rev[i]->object.oid.hash); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 6d4e669002..013d241abc 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -19,19 +19,34 @@ static const char *exclude_existing_arg; static void show_one(const char *refname, const struct object_id *oid) { - const char *hex = find_unique_abbrev(oid->hash, abbrev); + const char *hex; + struct object_id peeled; + + if (!has_sha1_file(oid->hash)) + die("git show-ref: bad ref %s (%s)", refname, + oid_to_hex(oid)); + + if (quiet) + return; + + hex = find_unique_abbrev(oid->hash, abbrev); if (hash_only) printf("%s\n", hex); else printf("%s %s\n", hex, refname); + + if (!deref_tags) + return; + + if (!peel_ref(refname, peeled.hash)) { + hex = find_unique_abbrev(peeled.hash, abbrev); + printf("%s %s^{}\n", hex, refname); + } } static int show_ref(const char *refname, const struct object_id *oid, int flag, void *cbdata) { - const char *hex; - struct object_id peeled; - if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -54,9 +69,6 @@ static int show_ref(const char *refname, const struct object_id *oid, continue; if (len == reflen) goto match; - /* "--verify" requires an exact match */ - if (verify) - continue; if (refname[reflen - len - 1] == '/') goto match; } @@ -66,26 +78,8 @@ static int show_ref(const char *refname, const struct object_id *oid, match: found_match++; - /* This changes the semantics slightly that even under quiet we - * detect and return error if the repository is corrupt and - * ref points at a nonexistent object. - */ - if (!has_sha1_file(oid->hash)) - die("git show-ref: bad ref %s (%s)", refname, - oid_to_hex(oid)); - - if (quiet) - return 0; - show_one(refname, oid); - if (!deref_tags) - return 0; - - if (!peel_ref(refname, peeled.hash)) { - hex = find_unique_abbrev(peeled.hash, abbrev); - printf("%s %s^{}\n", hex, refname); - } return 0; } @@ -202,10 +196,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) while (*pattern) { struct object_id oid; - if (starts_with(*pattern, "refs/") && + if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) && !read_ref(*pattern, oid.hash)) { - if (!quiet) - show_one(*pattern, &oid); + show_one(*pattern, &oid); } else if (!quiet) die("'%s' - not a valid ref", *pattern); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 92fd676a2e..15a5430c00 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -317,8 +317,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet) /* Only loads from .gitmodules, no overlay with .git/config */ gitmodules_config(); - if (prefix) { - strbuf_addf(&sb, "%s%s", prefix, path); + if (prefix && get_super_prefix()) + die("BUG: cannot have prefix and superprefix"); + else if (prefix) + displaypath = xstrdup(relative_path(path, prefix, &sb)); + else if (get_super_prefix()) { + strbuf_addf(&sb, "%s%s", get_super_prefix(), path); displaypath = strbuf_detach(&sb, NULL); } else displaypath = xstrdup(path); @@ -352,12 +356,10 @@ static void init_submodule(const char *path, const char *prefix, int quiet) strbuf_addf(&remotesb, "remote.%s.url", remote); free(remote); - if (git_config_get_string(remotesb.buf, &remoteurl)) - /* - * The repository is its own - * authoritative upstream - */ + if (git_config_get_string(remotesb.buf, &remoteurl)) { + warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf); remoteurl = xgetcwd(); + } relurl = relative_url(remoteurl, url, NULL); strbuf_release(&remotesb); free(remoteurl); @@ -403,9 +405,6 @@ static int module_init(int argc, const char **argv, const char *prefix) int i; struct option module_init_options[] = { - OPT_STRING(0, "prefix", &prefix, - N_("path"), - N_("alternative anchor for relative paths")), OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), OPT_END() }; @@ -625,7 +624,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) module_clone_options); strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); - sm_gitdir = xstrdup(absolute_path(sb.buf)); + sm_gitdir = absolute_pathdup(sb.buf); strbuf_reset(&sb); if (!is_absolute_path(path)) { @@ -1091,21 +1090,62 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, return 0; } +static int absorb_git_dirs(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES; + + struct option embed_gitdir_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"), + ABSORB_GITDIR_RECURSE_SUBMODULES), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper embed-git-dir [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, embed_gitdir_options, + git_submodule_helper_usage, 0); + + gitmodules_config(); + git_config(submodule_config, NULL); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + for (i = 0; i < list.nr; i++) + absorb_git_dir_into_superproject(prefix, + list.entries[i]->name, flags); + + return 0; +} + +#define SUPPORT_SUPER_PREFIX (1<<0) + struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); + unsigned option; }; static struct cmd_struct commands[] = { - {"list", module_list}, - {"name", module_name}, - {"clone", module_clone}, - {"update-clone", update_clone}, - {"relative-path", resolve_relative_path}, - {"resolve-relative-url", resolve_relative_url}, - {"resolve-relative-url-test", resolve_relative_url_test}, - {"init", module_init}, - {"remote-branch", resolve_remote_submodule_branch} + {"list", module_list, 0}, + {"name", module_name, 0}, + {"clone", module_clone, 0}, + {"update-clone", update_clone, 0}, + {"relative-path", resolve_relative_path, 0}, + {"resolve-relative-url", resolve_relative_url, 0}, + {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"remote-branch", resolve_remote_submodule_branch, 0}, + {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) @@ -1115,9 +1155,15 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix) die(_("submodule--helper subcommand must be " "called with a subcommand")); - for (i = 0; i < ARRAY_SIZE(commands); i++) - if (!strcmp(argv[1], commands[i].cmd)) + for (i = 0; i < ARRAY_SIZE(commands); i++) { + if (!strcmp(argv[1], commands[i].cmd)) { + if (get_super_prefix() && + !(commands[i].option & SUPPORT_SUPER_PREFIX)) + die(_("%s doesn't support --super-prefix"), + commands[i].cmd); return commands[i].fn(argc - 1, argv + 1, prefix); + } + } die(_("'%s' is not a valid submodule--helper " "subcommand"), argv[1]); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 96eed94468..70addef158 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -58,7 +58,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) die("Cannot delete %s, not a symbolic ref", argv[0]); if (!strcmp(argv[0], "HEAD")) die("deleting '%s' is not allowed", argv[0]); - return delete_ref(argv[0], NULL, REF_NODEREF); + return delete_ref(NULL, argv[0], NULL, REF_NODEREF); } switch (argc) { diff --git a/builtin/tag.c b/builtin/tag.c index 73df728114..ad29be6923 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -24,7 +24,7 @@ static const char * const git_tag_usage[] = { N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), - N_("git tag -v <tagname>..."), + N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -45,11 +45,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (!format) { if (filter->lines) { to_free = xstrfmt("%s %%(contents:lines=%d)", - "%(align:15)%(refname:strip=2)%(end)", + "%(align:15)%(refname:lstrip=2)%(end)", filter->lines); format = to_free; } else - format = "%(refname:strip=2)"; + format = "%(refname:lstrip=2)"; } verify_ref_format(format); @@ -66,9 +66,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const unsigned char *sha1); + const unsigned char *sha1, const void *cb_data); -static int for_each_tag_name(const char **argv, each_tag_name_fn fn) +static int for_each_tag_name(const char **argv, each_tag_name_fn fn, + const void *cb_data) { const char **p; char ref[PATH_MAX]; @@ -87,25 +88,38 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) had_error = 1; continue; } - if (fn(*p, ref, sha1)) + if (fn(*p, ref, sha1, cb_data)) had_error = 1; } return had_error; } static int delete_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { - if (delete_ref(ref, sha1, 0)) + if (delete_ref(NULL, ref, sha1, 0)) return 1; printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; } static int verify_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { - return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE); + int flags; + const char *fmt_pretty = cb_data; + flags = GPG_VERIFY_VERBOSE; + + if (fmt_pretty) + flags = GPG_VERIFY_OMIT_STATUS; + + if (gpg_verify_tag(sha1, name, flags)) + return -1; + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); + + return 0; } static int do_sign(struct strbuf *buffer) @@ -288,6 +302,54 @@ static void create_tag(const unsigned char *object, const char *tag, } } +static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb) +{ + enum object_type type; + struct commit *c; + char *buf; + unsigned long size; + int subject_len = 0; + const char *subject_start; + + char *rla = getenv("GIT_REFLOG_ACTION"); + if (rla) { + strbuf_addstr(sb, rla); + } else { + strbuf_addstr(sb, _("tag: tagging ")); + strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV); + } + + strbuf_addstr(sb, " ("); + type = sha1_object_info(sha1, NULL); + switch (type) { + default: + strbuf_addstr(sb, _("object of unknown type")); + break; + case OBJ_COMMIT: + if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) { + subject_len = find_commit_subject(buf, &subject_start); + strbuf_insert(sb, sb->len, subject_start, subject_len); + } else { + strbuf_addstr(sb, _("commit object")); + } + free(buf); + + if ((c = lookup_commit_reference(sha1)) != NULL) + strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT))); + break; + case OBJ_TREE: + strbuf_addstr(sb, _("tree object")); + break; + case OBJ_BLOB: + strbuf_addstr(sb, _("blob object")); + break; + case OBJ_TAG: + strbuf_addstr(sb, _("other tag object")); + break; + } + strbuf_addch(sb, ')'); +} + struct msg_arg { int given; struct strbuf buf; @@ -321,6 +383,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; struct strbuf ref = STRBUF_INIT; + struct strbuf reflog_msg = STRBUF_INIT; unsigned char object[20], prev[20]; const char *object_ref, *tag; struct create_tag_options opt; @@ -375,6 +438,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_END() }; + setup_ref_filter_porcelain_msg(); + git_config(git_tag_config, sorting_tail); memset(&opt, 0, sizeof(opt)); @@ -428,9 +493,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.merge_commit) die(_("--merged and --no-merged option are only allowed with -l")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag); - if (cmdmode == 'v') - return for_each_tag_name(argv, verify_tag); + return for_each_tag_name(argv, delete_tag, NULL); + if (cmdmode == 'v') { + if (format) + verify_ref_format(format); + return for_each_tag_name(argv, verify_tag, format); + } if (msg.given || msgfile) { if (msg.given && msgfile) @@ -477,6 +545,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); + create_reflog_msg(object, &reflog_msg); + if (create_tag_object) { if (force_sign_annotate && !annotate) opt.sign = 1; @@ -487,7 +557,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, - NULL, &err) || + reflog_msg.buf, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); @@ -497,5 +567,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix) strbuf_release(&err); strbuf_release(&buf); strbuf_release(&ref); + strbuf_release(&reflog_msg); return 0; } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7f30d3a76f..0b2ecf41ae 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -433,7 +433,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) * For purposes of backwards compatibility, we treat * NULL_SHA1 as "don't care" here: */ - return delete_ref(refname, + return delete_ref(msg, refname, (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL, flags); else diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 99f8148cf7..5199553d91 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -12,9 +12,10 @@ #include <signal.h> #include "parse-options.h" #include "gpg-interface.h" +#include "ref-filter.h" static const char * const verify_tag_usage[] = { - N_("git verify-tag [-v | --verbose] <tag>..."), + N_("git verify-tag [-v | --verbose] [--format=<format>] <tag>..."), NULL }; @@ -30,9 +31,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; + char *fmt_pretty = NULL; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), + OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -46,13 +49,26 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; + if (fmt_pretty) { + verify_ref_format(fmt_pretty); + flags |= GPG_VERIFY_OMIT_STATUS; + } + while (i < argc) { unsigned char sha1[20]; const char *name = argv[i++]; - if (get_sha1(name, sha1)) + if (get_sha1(name, sha1)) { had_error = !!error("tag '%s' not found.", name); - else if (gpg_verify_tag(sha1, name, flags)) + continue; + } + + if (gpg_verify_tag(sha1, name, flags)) { had_error = 1; + continue; + } + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); } return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 9a97e37a3f..831fe058a5 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -125,9 +125,9 @@ static int prune(int ac, const char **av, const char *prefix) { struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), - OPT__VERBOSE(&verbose, N_("report pruned objects")), + OPT__VERBOSE(&verbose, N_("report pruned working trees")), OPT_EXPIRY_DATE(0, "expire", &expire, - N_("expire objects older than <time>")), + N_("expire working trees older than <time>")), OPT_END() }; |