diff options
Diffstat (limited to 'builtin')
59 files changed, 1342 insertions, 2384 deletions
diff --git a/builtin/add.c b/builtin/add.c index b2a5c57f0a..145f06ef97 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -336,14 +336,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); - if ((0 < addremove_explicit || take_worktree_changes) && !argc) { - static const char *whole[2] = { ":/", NULL }; - argc = 1; - argv = whole; - } - add_new_files = !take_worktree_changes && !refresh_only; - require_pathspec = !take_worktree_changes; + require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); hold_locked_index(&lock_file, 1); diff --git a/builtin/am.c b/builtin/am.c index 4f77e07b95..f1a25ab6ad 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -27,6 +27,7 @@ #include "notes-utils.h" #include "rerere.h" #include "prompt.h" +#include "mailinfo.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -1258,58 +1259,61 @@ static void am_append_signoff(struct am_state *state) static int parse_mail(struct am_state *state, const char *mail) { FILE *fp; - struct child_process cp = CHILD_PROCESS_INIT; struct strbuf sb = STRBUF_INIT; struct strbuf msg = STRBUF_INIT; struct strbuf author_name = STRBUF_INIT; struct strbuf author_date = STRBUF_INIT; struct strbuf author_email = STRBUF_INIT; int ret = 0; + struct mailinfo mi; - cp.git_cmd = 1; - cp.in = xopen(mail, O_RDONLY, 0); - cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777); + setup_mailinfo(&mi); - argv_array_push(&cp.args, "mailinfo"); - argv_array_push(&cp.args, state->utf8 ? "-u" : "-n"); + if (state->utf8) + mi.metainfo_charset = get_commit_output_encoding(); + else + mi.metainfo_charset = NULL; switch (state->keep) { case KEEP_FALSE: break; case KEEP_TRUE: - argv_array_push(&cp.args, "-k"); + mi.keep_subject = 1; break; case KEEP_NON_PATCH: - argv_array_push(&cp.args, "-b"); + mi.keep_non_patch_brackets_in_subject = 1; break; default: die("BUG: invalid value for state->keep"); } if (state->message_id) - argv_array_push(&cp.args, "-m"); + mi.add_message_id = 1; switch (state->scissors) { case SCISSORS_UNSET: break; case SCISSORS_FALSE: - argv_array_push(&cp.args, "--no-scissors"); + mi.use_scissors = 0; break; case SCISSORS_TRUE: - argv_array_push(&cp.args, "--scissors"); + mi.use_scissors = 1; break; default: die("BUG: invalid value for state->scissors"); } - argv_array_push(&cp.args, am_path(state, "msg")); - argv_array_push(&cp.args, am_path(state, "patch")); - - if (run_command(&cp) < 0) + mi.input = fopen(mail, "r"); + if (!mi.input) + die("could not open input"); + mi.output = fopen(am_path(state, "info"), "w"); + if (!mi.output) + die("could not open output 'info'"); + if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch"))) die("could not parse patch"); - close(cp.in); - close(cp.out); + fclose(mi.input); + fclose(mi.output); /* Extract message and author information */ fp = xfopen(am_path(state, "info"), "r"); @@ -1341,9 +1345,8 @@ static int parse_mail(struct am_state *state, const char *mail) } strbuf_addstr(&msg, "\n\n"); - if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0) - die_errno(_("could not read '%s'"), am_path(state, "msg")); - stripspace(&msg, 0); + strbuf_addbuf(&msg, &mi.log_message); + strbuf_stripspace(&msg, 0); if (state->signoff) am_signoff(&msg); @@ -1366,6 +1369,7 @@ finish: strbuf_release(&author_email); strbuf_release(&author_name); strbuf_release(&sb); + clear_mailinfo(&mi); return ret; } @@ -1590,16 +1594,44 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f } /** + * Do the three-way merge using fake ancestor, his tree constructed + * from the fake ancestor and the postimage of the patch, and our + * state. + */ +static int run_fallback_merge_recursive(const struct am_state *state, + unsigned char *orig_tree, + unsigned char *our_tree, + unsigned char *his_tree) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status; + + cp.git_cmd = 1; + + argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s", + sha1_to_hex(his_tree), linelen(state->msg), state->msg); + if (state->quiet) + argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0"); + + argv_array_push(&cp.args, "merge-recursive"); + argv_array_push(&cp.args, sha1_to_hex(orig_tree)); + argv_array_push(&cp.args, "--"); + argv_array_push(&cp.args, sha1_to_hex(our_tree)); + argv_array_push(&cp.args, sha1_to_hex(his_tree)); + + status = run_command(&cp) ? (-1) : 0; + discard_cache(); + read_cache(); + return status; +} + +/** * Attempt a threeway merge, using index_path as the temporary index. */ static int fall_back_threeway(const struct am_state *state, const char *index_path) { unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ], our_tree[GIT_SHA1_RAWSZ]; - const unsigned char *bases[1] = {orig_tree}; - struct merge_options o; - struct commit *result; - char *his_tree_name; if (get_sha1("HEAD", our_tree) < 0) hashcpy(our_tree, EMPTY_TREE_SHA1_BIN); @@ -1651,22 +1683,11 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa * changes. */ - init_merge_options(&o); - - o.branch1 = "HEAD"; - his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); - o.branch2 = his_tree_name; - - if (state->quiet) - o.verbosity = 0; - - if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) { + if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) { rerere(state->allow_rerere_autoupdate); - free(his_tree_name); return error(_("Failed to merge in the changes.")); } - free(his_tree_name); return 0; } @@ -2208,6 +2229,17 @@ enum resume_mode { RESUME_ABORT }; +static int git_am_config(const char *k, const char *v, void *cb) +{ + int status; + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + + return git_default_config(k, v, NULL); +} + int cmd_am(int argc, const char **argv, const char *prefix) { struct am_state state; @@ -2218,8 +2250,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) int in_progress; const char * const usage[] = { - N_("git am [options] [(<mbox>|<Maildir>)...]"), - N_("git am [options] (--continue | --skip | --abort)"), + N_("git am [<options>] [(<mbox>|<Maildir>)...]"), + N_("git am [<options>] (--continue | --skip | --abort)"), NULL }; @@ -2308,7 +2340,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_am_config, NULL); am_state_init(&state, git_path("rebase-apply")); diff --git a/builtin/apply.c b/builtin/apply.c index 4aa53f7fd8..deb1364fa8 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -77,8 +77,7 @@ static enum ws_ignore { static const char *patch_input_file; -static const char *root; -static int root_len; +static struct strbuf root = STRBUF_INIT; static int read_stdin = 1; static int options; @@ -494,8 +493,8 @@ static char *find_name_gnu(const char *line, const char *def, int p_value) } strbuf_remove(&name, 0, cp - name.buf); - if (root) - strbuf_insert(&name, 0, root, root_len); + if (root.len) + strbuf_insert(&name, 0, root.buf, root.len); return squash_slash(strbuf_detach(&name, NULL)); } @@ -697,11 +696,8 @@ static char *find_name_common(const char *line, const char *def, return squash_slash(xstrdup(def)); } - if (root) { - char *ret = xmalloc(root_len + len + 1); - strcpy(ret, root); - memcpy(ret + root_len, start, len); - ret[root_len + len] = '\0'; + if (root.len) { + char *ret = xstrfmt("%s%.*s", root.buf, len, start); return squash_slash(ret); } @@ -1277,8 +1273,8 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct * the default name from the header. */ patch->def_name = git_header_name(line, len); - if (patch->def_name && root) { - char *s = xstrfmt("%s%s", root, patch->def_name); + if (patch->def_name && root.len) { + char *s = xstrfmt("%s%s", root.buf, patch->def_name); free(patch->def_name); patch->def_name = s; } @@ -4501,14 +4497,9 @@ static int option_parse_whitespace(const struct option *opt, static int option_parse_directory(const struct option *opt, const char *arg, int unset) { - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; + strbuf_reset(&root); + strbuf_addstr(&root, arg); + strbuf_complete(&root, '/'); return 0; } diff --git a/builtin/blame.c b/builtin/blame.c index 6fd1a63fc7..83612f5b64 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -459,12 +459,13 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin, static struct origin *make_origin(struct commit *commit, const char *path) { struct origin *o; - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); + size_t pathlen = strlen(path) + 1; + o = xcalloc(1, sizeof(*o) + pathlen); o->commit = commit; o->refcnt = 1; o->next = commit->util; commit->util = o; - strcpy(o->path, path); + memcpy(o->path, path, pathlen); /* includes NUL */ return o; } @@ -974,7 +975,10 @@ static void pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; - diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); + if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d)) + die("unable to generate diff (%s -> %s)", + sha1_to_hex(parent->commit->object.sha1), + sha1_to_hex(target->commit->object.sha1)); /* The rest are the same as the parent */ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); *d.dstq = NULL; @@ -1120,7 +1124,9 @@ static void find_copy_in_blob(struct scoreboard *sb, * file_p partially may match that image. */ memset(split, 0, sizeof(struct blame_entry [3])); - diff_hunks(file_p, &file_o, 1, handle_split_cb, &d); + if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d)) + die("unable to generate diff (%s)", + sha1_to_hex(parent->commit->object.sha1)); /* remainder, if any, all match the preimage */ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } @@ -1366,8 +1372,15 @@ static void pass_whole_blame(struct scoreboard *sb, */ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) { - if (!reverse) + if (!reverse) { + if (revs->first_parent_only && + commit->parents && + commit->parents->next) { + free_commit_list(commit->parents->next); + commit->parents->next = NULL; + } return commit->parents; + } return lookup_decoration(&revs->children, &commit->object); } @@ -1867,9 +1880,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, int cnt; const char *cp; struct origin *suspect = ent->suspect; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + sha1_to_hex_r(hex, suspect->commit->object.sha1); printf("%s %d %d %d\n", hex, ent->s_lno + 1, @@ -1905,11 +1918,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) const char *cp; struct origin *suspect = ent->suspect; struct commit_info ci; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); get_commit_info(suspect->commit, &ci, 1); - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + sha1_to_hex_r(hex, suspect->commit->object.sha1); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { @@ -2389,16 +2402,11 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, return commit; } -static char *prepare_final(struct scoreboard *sb) +static struct object_array_entry *find_single_final(struct rev_info *revs) { int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; + struct object_array_entry *found = NULL; - /* - * There must be one and only one positive commit in the - * revs->pending array. - */ for (i = 0; i < revs->pending.nr; i++) { struct object *obj = revs->pending.objects[i].item; if (obj->flags & UNINTERESTING) @@ -2407,14 +2415,24 @@ static char *prepare_final(struct scoreboard *sb) obj = deref_tag(obj, NULL, 0); if (obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) + if (found) die("More than one commit to dig from %s and %s?", revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; + found->name); + found = &(revs->pending.objects[i]); + } + return found; +} + +static char *prepare_final(struct scoreboard *sb) +{ + struct object_array_entry *found = find_single_final(sb->revs); + if (found) { + sb->final = (struct commit *) found->item; + return xstrdup(found->name); + } else { + return NULL; } - return xstrdup_or_null(final_commit_name); } static char *prepare_initial(struct scoreboard *sb) @@ -2490,6 +2508,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) long dashdash_pos, lno; char *final_commit_name = NULL; enum object_type type; + struct commit *final_commit = NULL; static struct string_list range_list; static int output_option = 0, opt = 0; @@ -2678,10 +2697,12 @@ parse_done: sb.commits.compare = compare_commits_by_commit_date; } else if (contents_from) - die("--contents and --children do not blend well."); + die("--contents and --reverse do not blend well."); else { final_commit_name = prepare_initial(&sb); sb.commits.compare = compare_commits_by_reverse_commit_date; + if (revs.first_parent_only) + revs.children.name = NULL; } if (!sb.final) { @@ -2698,6 +2719,14 @@ parse_done: else if (contents_from) die("Cannot use --contents with final commit object name"); + if (reverse && revs.first_parent_only) { + struct object_array_entry *entry = find_single_final(sb.revs); + if (!entry) + die("--reverse and --first-parent together require specified latest commit"); + else + final_commit = (struct commit*) entry->item; + } + /* * If we have bottom, this will mark the ancestors of the * bottom commits we would reach while traversing as @@ -2706,6 +2735,25 @@ parse_done: if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); + if (reverse && revs.first_parent_only) { + struct commit *c = final_commit; + + sb.revs->children.name = "children"; + while (c->parents && + hashcmp(c->object.sha1, sb.final->object.sha1)) { + struct commit_list *l = xcalloc(1, sizeof(*l)); + + l->item = c; + if (add_decoration(&sb.revs->children, + &c->parents->item->object, l)) + die("BUG: not unique item in first-parent chain"); + c = c->parents->item; + } + + if (hashcmp(c->object.sha1, sb.final->object.sha1)) + die("--reverse --first-parent together require range along first-parent chain"); + } + if (is_null_sha1(sb.final->object.sha1)) { o = sb.final->util; sb.final_buf = xmemdupz(o->file.ptr, o->file.size); diff --git a/builtin/branch.c b/builtin/branch.c index 3ba4d1bd3b..b99a436ef3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,18 +19,17 @@ #include "column.h" #include "utf8.h" #include "wt-status.h" +#include "ref-filter.h" static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), 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]"), NULL }; -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 - static const char *head; static unsigned char head_sha1[20]; @@ -52,13 +51,6 @@ enum color_branch { BRANCH_COLOR_UPSTREAM = 5 }; -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED -} merge_filter; -static unsigned char merge_filter_ref[20]; - static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; @@ -121,7 +113,7 @@ static int branch_merged(int kind, const char *name, void *reference_name_to_free = NULL; int merged; - if (kind == REF_LOCAL_BRANCH) { + if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; @@ -199,14 +191,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, struct strbuf bname = STRBUF_INIT; switch (kinds) { - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; force = 1; break; - case REF_LOCAL_BRANCH: + case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; break; default: @@ -223,7 +215,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { + if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " "which you are currently on."), bname.buf); ret = 1; @@ -279,147 +271,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -struct ref_item { - char *name; - char *dest; - unsigned int kind, width; - struct commit *commit; - int ignore; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, maxwidth, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - -static char *resolve_symref(const char *src, const char *prefix) -{ - unsigned char sha1[20]; - int flag; - const char *dst; - - dst = resolve_ref_unsafe(src, 0, sha1, &flag); - if (!(dst && (flag & REF_ISSYMREF))) - return NULL; - if (prefix) - skip_prefix(dst, prefix, &dst); - return xstrdup(dst); -} - -struct append_ref_cb { - struct ref_list *ref_list; - const char **pattern; - int ret; -}; - -static int match_patterns(const char **pattern, const char *refname) -{ - if (!*pattern) - return 1; /* no pattern always matches */ - while (*pattern) { - if (!wildmatch(*pattern, refname, 0, NULL)) - return 1; - pattern++; - } - return 0; -} - -static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) -{ - struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); - struct ref_list *ref_list = cb->ref_list; - struct ref_item *newitem; - struct commit *commit; - int kind, i; - const char *prefix, *orig_refname = refname; - - static struct { - int kind; - const char *prefix; - } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/" }, - { REF_REMOTE_BRANCH, "refs/remotes/" }, - }; - - /* Detect kind */ - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { - prefix = ref_kind[i].prefix; - if (skip_prefix(refname, prefix, &refname)) { - kind = ref_kind[i].kind; - break; - } - } - if (ARRAY_SIZE(ref_kind) <= i) - return 0; - - /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) - return 0; - - if (!match_patterns(cb->pattern, refname)) - return 0; - - commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) { - cb->ret = error(_("branch '%s' does not point at a commit"), refname); - return 0; - } - - /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, ref_list->with_commit)) - return 0; - - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, - (struct object *)commit, refname); - } - - ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); - - /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->width = utf8_strwidth(refname); - newitem->dest = resolve_symref(orig_refname, prefix); - newitem->ignore = 0; - /* adjust for "remotes/" */ - if (newitem->kind == REF_REMOTE_BRANCH && - ref_list->kinds != REF_REMOTE_BRANCH) - newitem->width += 8; - if (newitem->width > ref_list->maxwidth) - ref_list->maxwidth = newitem->width; - - return 0; -} - -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - -static int ref_cmp(const void *r1, const void *r2) -{ - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(r2); - - if (c1->kind != c2->kind) - return c1->kind - c2->kind; - return strcmp(c1->name, c2->name); -} - static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int show_upstream_ref) { @@ -482,8 +333,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, free(ref); } -static void add_verbose_info(struct strbuf *out, struct ref_item *item, - int verbose, int abbrev) +static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, + struct ref_filter *filter, const char *refname) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = _(" **** invalid ref ****"); @@ -494,32 +345,74 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, sub = subject.buf; } - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); + 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.sha1, abbrev), + find_unique_abbrev(item->commit->object.sha1, filter->abbrev), stat.buf, sub); strbuf_release(&stat); strbuf_release(&subject); } -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current, char *prefix) +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) { + /* TRANSLATORS: make sure these match _("HEAD detached at ") + and _("HEAD detached from ") in wt-status.c */ + if (state.detached_at) + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} + +static 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; - - if (item->ignore) - return; + const char *prefix = ""; + const char *desc = item->refname; + char *to_free = NULL; switch (item->kind) { - case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; + case FILTER_REFS_BRANCHES: + skip_prefix(desc, "refs/heads/", &desc); + if (!filter->detached && !strcmp(desc, head)) + current = 1; + else + color = BRANCH_COLOR_LOCAL; break; - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: + skip_prefix(desc, "refs/remotes/", &desc); color = BRANCH_COLOR_REMOTE; + prefix = remote_prefix; + break; + case FILTER_REFS_DETACHED_HEAD: + desc = to_free = get_head_description(); + current = 1; break; default: color = BRANCH_COLOR_PLAIN; @@ -532,8 +425,8 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, color = BRANCH_COLOR_CURRENT; } - strbuf_addf(&name, "%s%s", prefix, item->name); - if (verbose) { + strbuf_addf(&name, "%s%s", prefix, 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, @@ -542,159 +435,82 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) + if (item->symref) { + skip_prefix(item->symref, "refs/remotes/", &desc); + strbuf_addf(&out, " -> %s", desc); + } + else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, verbose, abbrev); + add_verbose_info(&out, item, filter, desc); if (column_active(colopts)) { - assert(!verbose && "--column and --verbose are incompatible"); + assert(!filter->verbose && "--column and --verbose are incompatible"); string_list_append(&output, out.buf); } else { printf("%s\n", out.buf); } strbuf_release(&name); strbuf_release(&out); + free(to_free); } -static int calc_maxwidth(struct ref_list *refs) -{ - int i, w = 0; - for (i = 0; i < refs->index; i++) { - if (refs->list[i].ignore) - continue; - if (refs->list[i].width > w) - w = refs->list[i].width; - } - return w; -} - -static char *get_head_description(void) -{ - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - /* TRANSLATORS: make sure these match _("HEAD detached at ") - and _("HEAD detached from ") in wt-status.c */ - if (state.detached_at) - strbuf_addf(&desc, _("(HEAD detached at %s)"), - state.detached_from); - else - strbuf_addf(&desc, _("(HEAD detached from %s)"), - state.detached_from); - } - else - strbuf_addstr(&desc, _("(no branch)")); - free(state.branch); - free(state.onto); - free(state.detached_from); - return strbuf_detach(&desc, NULL); -} - -static void show_detached(struct ref_list *ref_list) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { - struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - - if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { - struct ref_item item; - item.name = get_head_description(); - item.width = utf8_strwidth(item.name); - item.kind = REF_LOCAL_BRANCH; - item.dest = NULL; - item.commit = head_commit; - item.ignore = 0; - if (item.width > ref_list->maxwidth) - ref_list->maxwidth = item.width; - print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); - free(item.name); + int i, max = 0; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; + const char *desc = it->refname; + int w; + + skip_prefix(it->refname, "refs/heads/", &desc); + skip_prefix(it->refname, "refs/remotes/", &desc); + w = utf8_strwidth(desc); + + if (it->kind == FILTER_REFS_REMOTES) + w += remote_bonus; + if (w > max) + max = w; } + return max; } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) { int i; - struct append_ref_cb cb; - struct ref_list ref_list; - - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - ref_list.verbose = verbose; - ref_list.abbrev = abbrev; - ref_list.with_commit = with_commit; - if (merge_filter != NO_FILTER) - init_revisions(&ref_list.revs, NULL); - cb.ref_list = &ref_list; - cb.pattern = pattern; - cb.ret = 0; - for_each_rawref(append_ref, &cb); + struct ref_array array; + int maxwidth = 0; + const char *remote_prefix = ""; + /* - * The following implementation is currently duplicated in ref-filter. It - * will eventually be removed when we port branch.c to use ref-filter APIs. + * If we are listing more than just remote branches, + * then remote branches will have a "remotes/" prefix. + * We need to account for this in the width. */ - if (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - if (!filter) - die(_("object '%s' does not point to a commit"), - sha1_to_hex(merge_filter_ref)); - - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - - if (prepare_revision_walk(&ref_list.revs)) - die(_("revision walk setup failed")); - - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; - struct commit *commit = item->commit; - int is_merged = !!(commit->object.flags & UNINTERESTING); - item->ignore = is_merged != (merge_filter == SHOW_MERGED); - } + if (filter->kind != FILTER_REFS_REMOTES) + remote_prefix = "remotes/"; - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; - clear_commit_marks(item->commit, ALL_REV_FLAGS); - } - clear_commit_marks(filter, ALL_REV_FLAGS); + memset(&array, 0, sizeof(array)); - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); - } + verify_ref_format("%(refname)%(symref)"); + filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached && match_patterns(pattern, "HEAD")) - show_detached(&ref_list); - - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - char *prefix = (kinds != REF_REMOTE_BRANCH && - ref_list.list[i].kind == REF_REMOTE_BRANCH) - ? "remotes/" : ""; - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current, prefix); - } + if (filter->verbose) + maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - free_ref_list(&ref_list); + /* + * If no sorting parameter is given then we default to sorting + * by 'refname'. This would give us an alphabetically sorted + * array with the 'HEAD' ref at the beginning followed by + * local branches 'refs/heads/...' and finally remote-tacking + * branches 'refs/remotes/...'. + */ + if (!sorting) + sorting = ref_default_sorting(); + ref_array_sort(sorting, &array); - if (cb.ret) - error(_("some refs could not be read")); + for (i = 0; i < array.nr; i++) + format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix); - return cb.ret; + ref_array_clear(&array); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -750,24 +566,6 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } -/* - * This function is duplicated in ref-filter. It will eventually be removed - * when we port branch.c to use ref-filter APIs. - */ -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die(_("malformed object name %s"), arg); - return 0; -} - static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) @@ -794,7 +592,7 @@ static int edit_branch_description(const char *branch_name) strbuf_release(&buf); return -1; } - stripspace(&buf, 1); + strbuf_stripspace(&buf, 1); strbuf_addf(&name, "branch.%s.description", branch_name); status = git_config_set(name.buf, buf.len ? buf.buf : NULL); @@ -807,17 +605,16 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force = 0, list = 0; - int verbose = 0, abbrev = -1, detached = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; + struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { OPT_GROUP(N_("Generic options")), - OPT__VERBOSE(&verbose, + OPT__VERBOSE(&filter.verbose, N_("show hash and subject, give twice for upstream branch")), OPT__QUIET(&quiet, N_("suppress informational messages")), OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), @@ -827,15 +624,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), OPT__COLOR(&branch_use_color, N_("use colored output")), - OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), - REF_REMOTE_BRANCH), - OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")), - OPT_WITH(&with_commit, N_("print only branches that contain the commit")), - OPT__ABBREV(&abbrev), + OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), + FILTER_REFS_REMOTES), + OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")), + OPT__ABBREV(&filter.abbrev), OPT_GROUP(N_("Specific git-branch actions:")), - OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"), - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"), + FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES), OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1), OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), @@ -845,22 +642,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), OPT__FORCE(&force, N_("force creation, move/rename, deletion")), + OPT_MERGED(&filter, N_("print only branches that are merged")), + OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), + OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), { - OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - N_("commit"), N_("print only not merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - N_("commit"), N_("print only merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), + N_("print only branches of the object"), 0, parse_opt_object_name }, - OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_END(), }; + memset(&filter, 0, sizeof(filter)); + filter.kind = FILTER_REFS_BRANCHES; + filter.abbrev = -1; + if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); @@ -872,11 +669,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) - detached = 1; + filter.detached = 1; else if (!skip_prefix(head, "refs/heads/", &head)) die(_("HEAD not found below refs/heads!")); - hashcpy(merge_filter_ref, head_sha1); - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); @@ -884,17 +679,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (with_commit || merge_filter != NO_FILTER) + if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); - if (abbrev == -1) - abbrev = DEFAULT_ABBREV; + if (filter.abbrev == -1) + filter.abbrev = DEFAULT_ABBREV; finalize_colopts(&colopts, -1); - if (verbose) { + if (filter.verbose) { if (explicitly_enable_column(colopts)) die(_("--column and --verbose are incompatible")); colopts = 0; @@ -908,20 +703,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) { if (!argc) die(_("branch name required")); - return delete_branches(argc, argv, delete > 1, kinds, quiet); + return delete_branches(argc, argv, delete > 1, filter.kind, quiet); } else if (list) { - int ret = print_ref_list(kinds, detached, verbose, abbrev, - with_commit, argv); + /* git branch --local also shows HEAD when it is detached */ + if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) + filter.kind |= FILTER_REFS_DETACHED_HEAD; + filter.name_patterns = argv; + print_ref_list(&filter, sorting); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); - return ret; + return 0; } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; if (!argc) { - if (detached) + if (filter.detached) die(_("Cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) @@ -1009,7 +807,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!branch) die(_("no such branch '%s'"), argv[0]); - if (kinds != REF_LOCAL_BRANCH) + if (filter.kind != FILTER_REFS_BRANCHES) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); if (track == BRANCH_TRACK_OVERRIDE) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 07baad1e59..c0fd8dbb1c 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -426,7 +426,7 @@ static int batch_objects(struct batch_options *opt) static const char * const cat_file_usage[] = { N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"), + N_("git cat-file (--batch | --batch-check) [--follow-symlinks]"), NULL }; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 21d2bedcc9..265c9ba022 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -9,7 +9,7 @@ static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."), -N_("git check-attr --stdin [-z] [-a | --all | <attr>...] < <list-of-paths>"), +N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"), NULL }; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index dc8d97c56c..43f361797a 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -8,7 +8,7 @@ static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { "git check-ignore [<options>] <pathname>...", -"git check-ignore [<options>] --stdin < <list-of-paths>", +"git check-ignore [<options>] --stdin", NULL }; diff --git a/builtin/checkout.c b/builtin/checkout.c index bc703c0f5e..e346f521b0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -37,6 +37,7 @@ struct checkout_opts { int overwrite_ignore; int ignore_skipworktree; int ignore_other_worktrees; + int show_progress; const char *new_branch; const char *new_branch_force; @@ -417,7 +418,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.reset = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = !o->quiet && isatty(2); + opts.verbose_update = o->show_progress; opts.src_index = &the_index; opts.dst_index = &the_index; parse_tree(tree); @@ -501,7 +502,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.update = 1; topts.merge = 1; topts.gently = opts->merge && old->commit; - topts.verbose_update = !opts->quiet && isatty(2); + topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; if (opts->overwrite_ignore) { topts.dir = xcalloc(1, sizeof(*topts.dir)); @@ -1156,6 +1157,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("second guess 'git checkout <no-such-branch>'")), OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), + OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), OPT_END(), }; @@ -1163,6 +1165,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&new, 0, sizeof(new)); opts.overwrite_ignore = 1; opts.prefix = prefix; + opts.show_progress = -1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1172,6 +1175,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + if (opts.show_progress < 0) { + if (opts.quiet) + opts.show_progress = 0; + else + opts.show_progress = isatty(2); + } + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); diff --git a/builtin/clean.c b/builtin/clean.c index df53def63f..d7acb94a95 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -159,8 +159,7 @@ static int is_git_repository(struct strbuf *path) int gitfile_error; size_t orig_path_len = path->len; assert(orig_path_len != 0); - if (path->buf[orig_path_len - 1] != '/') - strbuf_addch(path, '/'); + strbuf_complete(path, '/'); strbuf_addstr(path, ".git"); if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf)) ret = 1; @@ -206,8 +205,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, return res; } - if (path->buf[original_len - 1] != '/') - strbuf_addch(path, '/'); + strbuf_complete(path, '/'); len = path->len; while ((e = readdir(dir)) != NULL) { diff --git a/builtin/clone.c b/builtin/clone.c index 578da85254..caae43e7a3 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -294,9 +294,14 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) char *ref_git_git = mkpathdup("%s/.git", ref_git); free(ref_git); ref_git = ref_git_git; - } else if (!is_directory(mkpath("%s/objects", ref_git))) + } else if (!is_directory(mkpath("%s/objects", ref_git))) { + struct strbuf sb = STRBUF_INIT; + if (get_common_dir(&sb, ref_git)) + die(_("reference repository '%s' as a linked checkout is not supported yet."), + item->string); die(_("reference repository '%s' is not a local repository."), item->string); + } if (!access(mkpath("%s/shallow", ref_git), F_OK)) die(_("reference repository '%s' is shallow"), item->string); @@ -424,8 +429,10 @@ static void clone_local(const char *src_repo, const char *dest_repo) } else { struct strbuf src = STRBUF_INIT; struct strbuf dest = STRBUF_INIT; - strbuf_addf(&src, "%s/objects", src_repo); - strbuf_addf(&dest, "%s/objects", dest_repo); + get_common_dir(&src, src_repo); + get_common_dir(&dest, dest_repo); + strbuf_addstr(&src, "/objects"); + strbuf_addstr(&dest, "/objects"); copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); @@ -794,11 +801,15 @@ static void write_refspec_config(const char *src_ref_prefix, static void dissociate_from_references(void) { static const char* argv[] = { "repack", "-a", "-d", NULL }; + char *alternates = git_pathdup("objects/info/alternates"); - if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN)) - die(_("cannot repack to clean up")); - if (unlink(git_path("objects/info/alternates")) && errno != ENOENT) - die_errno(_("cannot unlink temporary alternates file")); + if (!access(alternates, F_OK)) { + if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN)) + die(_("cannot repack to clean up")); + if (unlink(alternates) && errno != ENOENT) + die_errno(_("cannot unlink temporary alternates file")); + } + free(alternates); } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -947,10 +958,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_reference.nr) setup_reference(); - else if (option_dissociate) { - warning(_("--dissociate given, but there is no --reference")); - option_dissociate = 0; - } fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); @@ -1064,8 +1071,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_unlock_pack(transport); transport_disconnect(transport); - if (option_dissociate) + if (option_dissociate) { + close_all_packs(); dissociate_from_references(); + } junk_mode = JUNK_LEAVE_REPO; err = checkout(); diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 25aa2cdef3..8747c0f2fb 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,7 +10,7 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>"; static const char *sign_commit; diff --git a/builtin/commit.c b/builtin/commit.c index 63772d016a..dca09e2c3b 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -775,7 +775,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, s->hints = 0; if (clean_message_contents) - stripspace(&sb, 0); + strbuf_stripspace(&sb, 0); if (signoff) append_signoff(&sb, ignore_non_trailer(&sb), 0); @@ -1014,7 +1014,7 @@ static int template_untouched(struct strbuf *sb) if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) return 0; - stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); @@ -1726,7 +1726,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_truncate_message_at_cut_line(&sb); if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (template_untouched(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); diff --git a/builtin/config.c b/builtin/config.c index 71acc44143..adc772786a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -246,8 +246,6 @@ free_strings: static char *normalize_value(const char *key, const char *value) { - char *normalized; - if (!value) return NULL; @@ -258,27 +256,21 @@ static char *normalize_value(const char *key, const char *value) * "~/foobar/" in the config file, and to expand the ~ * when retrieving the value. */ - normalized = xstrdup(value); - else { - normalized = xmalloc(64); - if (types == TYPE_INT) { - int64_t v = git_config_int64(key, value); - sprintf(normalized, "%"PRId64, v); - } - else if (types == TYPE_BOOL) - sprintf(normalized, "%s", - git_config_bool(key, value) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool) - sprintf(normalized, "%d", v); - else - sprintf(normalized, "%s", v ? "true" : "false"); - } + return xstrdup(value); + if (types == TYPE_INT) + return xstrfmt("%"PRId64, git_config_int64(key, value)); + if (types == TYPE_BOOL) + return xstrdup(git_config_bool(key, value) ? "true" : "false"); + if (types == TYPE_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool) + return xstrfmt("%d", v); + else + return xstrdup(v ? "true" : "false"); } - return normalized; + die("BUG: cannot normalize type %d", types); } static int get_color_found; diff --git a/builtin/count-objects.c b/builtin/count-objects.c index ad0c79954a..ba9291944f 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -15,9 +15,31 @@ static int verbose; static unsigned long loose, packed, packed_loose; static off_t loose_size; -static void real_report_garbage(const char *desc, const char *path) +static const char *bits_to_msg(unsigned seen_bits) +{ + switch (seen_bits) { + case 0: + return "no corresponding .idx or .pack"; + case PACKDIR_FILE_GARBAGE: + return "garbage found"; + case PACKDIR_FILE_PACK: + return "no corresponding .idx"; + case PACKDIR_FILE_IDX: + return "no corresponding .pack"; + case PACKDIR_FILE_PACK|PACKDIR_FILE_IDX: + default: + return NULL; + } +} + +static void real_report_garbage(unsigned seen_bits, const char *path) { struct stat st; + const char *desc = bits_to_msg(seen_bits); + + if (!desc) + return; + if (!stat(path, &st)) size_garbage += st.st_size; warning("%s: %s", desc, path); @@ -27,7 +49,7 @@ static void real_report_garbage(const char *desc, const char *path) static void loose_garbage(const char *path) { if (verbose) - report_garbage("garbage found", path); + report_garbage(PACKDIR_FILE_GARBAGE, path); } static int count_loose(const unsigned char *sha1, const char *path, void *data) diff --git a/builtin/fetch.c b/builtin/fetch.c index 9a3869f4ff..ed84963a57 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -528,36 +528,38 @@ static int update_local_ref(struct ref *ref, } if (in_merge_bases(current, updated)) { - char quickref[83]; + struct strbuf quickref = STRBUF_INIT; int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, ".."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV); + strbuf_addstr(&quickref, ".."); + strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); strbuf_addf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref, + TRANSPORT_SUMMARY_WIDTH, quickref.buf, REFCOL_WIDTH, remote, pretty_ref, r ? _(" (unable to update local ref)") : ""); + strbuf_release(&quickref); return r; } else if (force || ref->force) { - char quickref[84]; + struct strbuf quickref = STRBUF_INIT; int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, "..."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV); + strbuf_addstr(&quickref, "..."); + strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref, + TRANSPORT_SUMMARY_WIDTH, quickref.buf, REFCOL_WIDTH, remote, pretty_ref, r ? _("unable to update local ref") : _("forced update")); + strbuf_release(&quickref); return r; } else { strbuf_addf(display, "! %-*s %-*s -> %s %s", @@ -637,8 +639,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; if (rm->peer_ref) { - ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); - strcpy(ref->name, rm->peer_ref->name); + ref = alloc_ref(rm->peer_ref->name); hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); hashcpy(ref->new_sha1, rm->old_sha1); ref->force = rm->peer_ref->force; @@ -1156,11 +1157,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) die(_("--depth and --unshallow cannot be used together")); else if (!is_repository_shallow()) die(_("--unshallow on a complete repository does not make sense")); - else { - static char inf_depth[12]; - sprintf(inf_depth, "%d", INFINITE_DEPTH); - depth = inf_depth; - } + else + depth = xstrfmt("%d", INFINITE_DEPTH); } /* no need to be strict, transport_set_option() will validate it again */ diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 4ba7f282a5..846004b833 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -537,7 +537,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) static void find_merge_parents(struct merge_parents *result, struct strbuf *in, unsigned char *head) { - struct commit_list *parents, *next; + struct commit_list *parents; struct commit *head_commit; int pos = 0, i, j; @@ -576,13 +576,10 @@ static void find_merge_parents(struct merge_parents *result, parents = reduce_heads(parents); while (parents) { + struct commit *cmit = pop_commit(&parents); for (i = 0; i < result->nr; i++) - if (!hashcmp(result->item[i].commit, - parents->item->object.sha1)) + if (!hashcmp(result->item[i].commit, cmit->object.sha1)) result->item[i].used = 1; - next = parents->next; - free(parents); - parents = next; } for (i = j = 0; i < result->nr; i++) { diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 40f343b6a3..4e9f6c29bf 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -68,6 +68,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); filter.name_patterns = argv; + filter.match_as_path = 1; filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); ref_array_sort(sorting, &array); diff --git a/builtin/fsck.c b/builtin/fsck.c index 079470342f..8b8bb42c51 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -38,14 +38,7 @@ static int show_dangling = 1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 - -#ifdef NO_D_INO_IN_DIRENT -#define SORT_DIRENT 0 -#define DIRENT_SORT_HINT(de) 0 -#else -#define SORT_DIRENT 1 -#define DIRENT_SORT_HINT(de) ((de)->d_ino) -#endif +#define ERROR_REFS 010 static int fsck_config(const char *var, const char *value, void *cb) { @@ -373,102 +366,6 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, return fsck_obj(obj); } -/* - * This is the sorting chunk size: make it reasonably - * big so that we can sort well.. - */ -#define MAX_SHA1_ENTRIES (1024) - -struct sha1_entry { - unsigned long ino; - unsigned char sha1[20]; -}; - -static struct { - unsigned long nr; - struct sha1_entry *entry[MAX_SHA1_ENTRIES]; -} sha1_list; - -static int ino_compare(const void *_a, const void *_b) -{ - const struct sha1_entry *a = _a, *b = _b; - unsigned long ino1 = a->ino, ino2 = b->ino; - return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0; -} - -static void fsck_sha1_list(void) -{ - int i, nr = sha1_list.nr; - - if (SORT_DIRENT) - qsort(sha1_list.entry, nr, - sizeof(struct sha1_entry *), ino_compare); - for (i = 0; i < nr; i++) { - struct sha1_entry *entry = sha1_list.entry[i]; - unsigned char *sha1 = entry->sha1; - - sha1_list.entry[i] = NULL; - if (fsck_sha1(sha1)) - errors_found |= ERROR_OBJECT; - free(entry); - } - sha1_list.nr = 0; -} - -static void add_sha1_list(unsigned char *sha1, unsigned long ino) -{ - struct sha1_entry *entry = xmalloc(sizeof(*entry)); - int nr; - - entry->ino = ino; - hashcpy(entry->sha1, sha1); - nr = sha1_list.nr; - if (nr == MAX_SHA1_ENTRIES) { - fsck_sha1_list(); - nr = 0; - } - sha1_list.entry[nr] = entry; - sha1_list.nr = ++nr; -} - -static inline int is_loose_object_file(struct dirent *de, - char *name, unsigned char *sha1) -{ - if (strlen(de->d_name) != 38) - return 0; - memcpy(name + 2, de->d_name, 39); - return !get_sha1_hex(name, sha1); -} - -static void fsck_dir(int i, char *path) -{ - DIR *dir = opendir(path); - struct dirent *de; - char name[100]; - - if (!dir) - return; - - if (verbose) - fprintf(stderr, "Checking directory %s\n", path); - - sprintf(name, "%02x", i); - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - - if (is_dot_or_dotdot(de->d_name)) - continue; - if (is_loose_object_file(de, name, sha1)) { - add_sha1_list(sha1, DIRENT_SORT_HINT(de)); - continue; - } - if (starts_with(de->d_name, "tmp_obj_")) - continue; - fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); - } - closedir(dir); -} - static int default_refs; static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) @@ -521,8 +418,10 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, /* We'll continue with the rest despite the error.. */ return 0; } - if (obj->type != OBJ_COMMIT && is_branch(refname)) + if (obj->type != OBJ_COMMIT && is_branch(refname)) { error("%s: not a commit", refname); + errors_found |= ERROR_REFS; + } default_refs++; obj->used = 1; mark_object_reachable(obj); @@ -556,9 +455,28 @@ static void get_default_heads(void) } } +static int fsck_loose(const unsigned char *sha1, const char *path, void *data) +{ + if (fsck_sha1(sha1)) + errors_found |= ERROR_OBJECT; + return 0; +} + +static int fsck_cruft(const char *basename, const char *path, void *data) +{ + if (!starts_with(basename, "tmp_obj_")) + fprintf(stderr, "bad sha1 file: %s\n", path); + return 0; +} + +static int fsck_subdir(int nr, const char *path, void *progress) +{ + display_progress(progress, nr + 1); + return 0; +} + static void fsck_object_dir(const char *path) { - int i; struct progress *progress = NULL; if (verbose) @@ -566,14 +484,11 @@ static void fsck_object_dir(const char *path) if (show_progress) progress = start_progress(_("Checking object directories"), 256); - for (i = 0; i < 256; i++) { - static char dir[4096]; - sprintf(dir, "%s/%02x", path, i); - fsck_dir(i, dir); - display_progress(progress, i+1); - } + + for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, + progress); + display_progress(progress, 256); stop_progress(&progress); - fsck_sha1_list(); } static int fsck_head_link(void) @@ -585,17 +500,23 @@ static int fsck_head_link(void) fprintf(stderr, "Checking HEAD link\n"); head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); - if (!head_points_at) + if (!head_points_at) { + errors_found |= ERROR_REFS; return error("Invalid HEAD"); + } if (!strcmp(head_points_at, "HEAD")) /* detached HEAD */ null_is_error = 1; - else if (!starts_with(head_points_at, "refs/heads/")) + else if (!starts_with(head_points_at, "refs/heads/")) { + errors_found |= ERROR_REFS; return error("HEAD points to something strange (%s)", head_points_at); + } if (is_null_oid(&head_oid)) { - if (null_is_error) + if (null_is_error) { + errors_found |= ERROR_REFS; return error("HEAD: detached HEAD points at nothing"); + } fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", head_points_at + 11); } @@ -615,6 +536,7 @@ static int fsck_cache_tree(struct cache_tree *it) if (!obj) { error("%s: invalid sha1 pointer in cache-tree", sha1_to_hex(it->sha1)); + errors_found |= ERROR_REFS; return 1; } obj->used = 1; @@ -678,16 +600,18 @@ 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) { fsck_object_dir(get_object_directory()); - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); + prepare_alt_odb(); + for (alt = alt_odb_list; alt; alt = alt->next) { + /* directory name, minus trailing slash */ + size_t namelen = alt->name - alt->base - 1; + struct strbuf name = STRBUF_INIT; + strbuf_add(&name, alt->base, namelen); + fsck_object_dir(name.buf); + strbuf_release(&name); + } } if (check_full) { diff --git a/builtin/gc.c b/builtin/gc.c index 0ad8d30b56..c583aad6ec 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -44,6 +44,23 @@ static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; static struct tempfile pidfile; +static struct lock_file log_lock; + +static struct string_list pack_garbage = STRING_LIST_INIT_DUP; + +static void clean_pack_garbage(void) +{ + int i; + for (i = 0; i < pack_garbage.nr; i++) + unlink_or_warn(pack_garbage.items[i].string); + string_list_clear(&pack_garbage, 0); +} + +static void report_pack_garbage(unsigned seen_bits, const char *path) +{ + if (seen_bits == PACKDIR_FILE_IDX) + string_list_append(&pack_garbage, path); +} static void git_config_date_string(const char *key, const char **output) { @@ -56,6 +73,28 @@ static void git_config_date_string(const char *key, const char **output) } } +static void process_log_file(void) +{ + struct stat st; + if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size) + commit_lock_file(&log_lock); + else + rollback_lock_file(&log_lock); +} + +static void process_log_file_at_exit(void) +{ + fflush(stderr); + process_log_file(); +} + +static void process_log_file_on_signal(int signo) +{ + process_log_file(); + sigchain_pop(signo); + raise(signo); +} + static void gc_config(void) { const char *value; @@ -194,7 +233,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) return NULL; if (gethostname(my_host, sizeof(my_host))) - strcpy(my_host, "unknown"); + xsnprintf(my_host, sizeof(my_host), "unknown"); pidfile_path = git_pathdup("gc.pid"); fd = hold_lock_file_for_update(&lock, pidfile_path, @@ -217,7 +256,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) * running. */ time(NULL) - st.st_mtime <= 12 * 3600 && - fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 && + fscanf(fp, "%"SCNuMAX" %127c", &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); if (fp != NULL) @@ -241,6 +280,24 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) return NULL; } +static int report_last_gc_error(void) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = strbuf_read_file(&sb, git_path("gc.log"), 0); + if (ret > 0) + return 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); + strbuf_release(&sb); + return 0; +} + static int gc_before_repack(void) { if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) @@ -262,6 +319,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) int force = 0; const char *name; pid_t pid; + int daemonized = 0; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -318,13 +376,16 @@ int cmd_gc(int argc, const char **argv, const char *prefix) fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n")); } if (detach_auto) { + if (report_last_gc_error()) + return -1; + if (gc_before_repack()) return -1; /* * failure to daemonize is ok, we'll continue * in foreground */ - daemonize(); + daemonized = !daemonize(); } } else add_repack_all_option(); @@ -337,18 +398,29 @@ int cmd_gc(int argc, const char **argv, const char *prefix) name, (uintmax_t)pid); } + if (daemonized) { + hold_lock_file_for_update(&log_lock, + git_path("gc.log"), + LOCK_DIE_ON_ERROR); + dup2(get_lock_file_fd(&log_lock), 2); + sigchain_push_common(process_log_file_on_signal); + atexit(process_log_file_at_exit); + } + if (gc_before_repack()) return -1; - if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, repack.argv[0]); + if (!repository_format_precious_objects) { + if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, repack.argv[0]); - if (prune_expire) { - argv_array_push(&prune, prune_expire); - if (quiet) - argv_array_push(&prune, "--no-progress"); - if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, prune.argv[0]); + if (prune_expire) { + argv_array_push(&prune, prune_expire); + if (quiet) + argv_array_push(&prune, "--no-progress"); + if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune.argv[0]); + } } if (prune_worktrees_expire) { @@ -360,6 +432,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); + report_garbage = report_pack_garbage; + reprepare_packed_git(); + if (pack_garbage.nr > 0) + clean_pack_garbage(); + if (auto_gc && too_many_loose_objects()) warning(_("There are too many unreachable loose objects; " "run 'git prune' to remove them.")); diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index 6f4147ad02..e21c5416cd 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -8,7 +8,7 @@ #include "quote.h" static const char builtin_get_tar_commit_id_usage[] = -"git get-tar-commit-id < <tarfile>"; +"git get-tar-commit-id"; /* ustar header + extended global header content */ #define RECORDSIZE (512) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 07fef3cc6b..43b098b76c 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -78,7 +78,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), - N_("git hash-object --stdin-paths < <list-of-paths>"), + N_("git hash-object --stdin-paths"), NULL }; const char *type = blob_type; diff --git a/builtin/help.c b/builtin/help.c index 3422e73079..1cd0c1ee44 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -140,17 +140,10 @@ static void exec_man_konqueror(const char *path, const char *page) /* It's simpler to launch konqueror using kfmclient. */ if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; + size_t len; + if (strip_suffix(path, "/konqueror", &len)) + path = xstrfmt("%.*s/kfmclient", (int)len, path); + filename = basename((char *)path); } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); @@ -183,7 +176,7 @@ static void add_man_viewer(const char *name) while (*p) p = &((*p)->next); *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); + memcpy((*p)->name, name, len); /* NUL-terminated by xcalloc */ } static int supported_man_viewer(const char *name, size_t len) @@ -199,7 +192,7 @@ static void do_add_man_viewer_info(const char *name, { struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - strncpy(new->name, name, len); + memcpy(new->name, name, len); /* NUL-terminated by xcalloc */ new->info = xstrdup(value); new->next = man_viewer_info_list; man_viewer_info_list = new; @@ -295,16 +288,6 @@ static int is_git_command(const char *s) is_in_cmdlist(&other_cmds, s); } -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - static const char *cmd_to_page(const char *git_cmd) { if (!git_cmd) @@ -312,9 +295,9 @@ static const char *cmd_to_page(const char *git_cmd) else if (starts_with(git_cmd, "git")) return git_cmd; else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); + return xstrfmt("git-%s", git_cmd); else - return prepend("git", git_cmd); + return xstrfmt("git%s", git_cmd); } static void setup_man_path(void) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 3431de2362..1ad1bde696 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -441,7 +441,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1; git_SHA1_Init(&c); git_SHA1_Update(&c, hdr, hdrlen); } else diff --git a/builtin/init-db.c b/builtin/init-db.c index 69323e186c..f59f40768e 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -36,10 +36,11 @@ static void safe_create_dir(const char *dir, int share) die(_("Could not make %s writable by group"), dir); } -static void copy_templates_1(char *path, int baselen, - char *template, int template_baselen, +static void copy_templates_1(struct strbuf *path, struct strbuf *template, DIR *dir) { + size_t path_baselen = path->len; + size_t template_baselen = template->len; struct dirent *de; /* Note: if ".git/hooks" file exists in the repository being @@ -49,77 +50,64 @@ static void copy_templates_1(char *path, int baselen, * with the way the namespace under .git/ is organized, should * be really carefully chosen. */ - safe_create_dir(path, 1); + safe_create_dir(path->buf, 1); while ((de = readdir(dir)) != NULL) { struct stat st_git, st_template; - int namelen; int exists = 0; + strbuf_setlen(path, path_baselen); + strbuf_setlen(template, template_baselen); + if (de->d_name[0] == '.') continue; - namelen = strlen(de->d_name); - if ((PATH_MAX <= baselen + namelen) || - (PATH_MAX <= template_baselen + namelen)) - die(_("insanely long template name %s"), de->d_name); - memcpy(path + baselen, de->d_name, namelen+1); - memcpy(template + template_baselen, de->d_name, namelen+1); - if (lstat(path, &st_git)) { + strbuf_addstr(path, de->d_name); + strbuf_addstr(template, de->d_name); + if (lstat(path->buf, &st_git)) { if (errno != ENOENT) - die_errno(_("cannot stat '%s'"), path); + die_errno(_("cannot stat '%s'"), path->buf); } else exists = 1; - if (lstat(template, &st_template)) - die_errno(_("cannot stat template '%s'"), template); + if (lstat(template->buf, &st_template)) + die_errno(_("cannot stat template '%s'"), template->buf); if (S_ISDIR(st_template.st_mode)) { - DIR *subdir = opendir(template); - int baselen_sub = baselen + namelen; - int template_baselen_sub = template_baselen + namelen; + DIR *subdir = opendir(template->buf); if (!subdir) - die_errno(_("cannot opendir '%s'"), template); - path[baselen_sub++] = - template[template_baselen_sub++] = '/'; - path[baselen_sub] = - template[template_baselen_sub] = 0; - copy_templates_1(path, baselen_sub, - template, template_baselen_sub, - subdir); + die_errno(_("cannot opendir '%s'"), template->buf); + strbuf_addch(path, '/'); + strbuf_addch(template, '/'); + copy_templates_1(path, template, subdir); closedir(subdir); } else if (exists) continue; else if (S_ISLNK(st_template.st_mode)) { - char lnk[256]; - int len; - len = readlink(template, lnk, sizeof(lnk)); - if (len < 0) - die_errno(_("cannot readlink '%s'"), template); - if (sizeof(lnk) <= len) - die(_("insanely long symlink %s"), template); - lnk[len] = 0; - if (symlink(lnk, path)) - die_errno(_("cannot symlink '%s' '%s'"), lnk, path); + struct strbuf lnk = STRBUF_INIT; + if (strbuf_readlink(&lnk, template->buf, 0) < 0) + die_errno(_("cannot readlink '%s'"), template->buf); + if (symlink(lnk.buf, path->buf)) + die_errno(_("cannot symlink '%s' '%s'"), + lnk.buf, path->buf); + strbuf_release(&lnk); } else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path, template, st_template.st_mode)) - die_errno(_("cannot copy '%s' to '%s'"), template, - path); + if (copy_file(path->buf, template->buf, st_template.st_mode)) + die_errno(_("cannot copy '%s' to '%s'"), + template->buf, path->buf); } else - error(_("ignoring template %s"), template); + error(_("ignoring template %s"), template->buf); } } static void copy_templates(const char *template_dir) { - char path[PATH_MAX]; - char template_path[PATH_MAX]; - int template_len; + struct strbuf path = STRBUF_INIT; + struct strbuf template_path = STRBUF_INIT; + size_t template_len; DIR *dir; - const char *git_dir = get_git_dir(); - int len = strlen(git_dir); char *to_free = NULL; if (!template_dir) @@ -132,26 +120,23 @@ static void copy_templates(const char *template_dir) free(to_free); return; } - template_len = strlen(template_dir); - if (PATH_MAX <= (template_len+strlen("/config"))) - die(_("insanely long template path %s"), template_dir); - strcpy(template_path, template_dir); - if (template_path[template_len-1] != '/') { - template_path[template_len++] = '/'; - template_path[template_len] = 0; - } - dir = opendir(template_path); + + strbuf_addstr(&template_path, template_dir); + strbuf_complete(&template_path, '/'); + template_len = template_path.len; + + dir = opendir(template_path.buf); if (!dir) { warning(_("templates not found %s"), template_dir); goto free_return; } /* Make sure that template is from the correct vintage */ - strcpy(template_path + template_len, "config"); + strbuf_addstr(&template_path, "config"); repository_format_version = 0; git_config_from_file(check_repository_format_version, - template_path, NULL); - template_path[template_len] = 0; + template_path.buf, NULL); + strbuf_setlen(&template_path, template_len); if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { @@ -162,17 +147,15 @@ static void copy_templates(const char *template_dir) goto close_free_return; } - memcpy(path, git_dir, len); - if (len && path[len - 1] != '/') - path[len++] = '/'; - path[len] = 0; - copy_templates_1(path, len, - template_path, template_len, - dir); + strbuf_addstr(&path, get_git_dir()); + strbuf_complete(&path, '/'); + copy_templates_1(&path, &template_path, dir); close_free_return: closedir(dir); free_return: free(to_free); + strbuf_release(&path); + strbuf_release(&template_path); } static int git_init_db_config(const char *k, const char *v, void *cb) @@ -199,28 +182,20 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) static int create_default_files(const char *template_path) { - const char *git_dir = get_git_dir(); - unsigned len = strlen(git_dir); - static char path[PATH_MAX]; struct stat st1; + struct strbuf buf = STRBUF_INIT; + char *path; char repo_version_string[10]; char junk[2]; int reinit; int filemode; - if (len > sizeof(path)-50) - die(_("insane git directory %s"), git_dir); - memcpy(path, git_dir, len); - - if (len && path[len-1] != '/') - path[len++] = '/'; - /* * Create .git/refs/{heads,tags} */ - safe_create_dir(git_path("refs"), 1); - safe_create_dir(git_path("refs/heads"), 1); - safe_create_dir(git_path("refs/tags"), 1); + safe_create_dir(git_path_buf(&buf, "refs"), 1); + safe_create_dir(git_path_buf(&buf, "refs/heads"), 1); + safe_create_dir(git_path_buf(&buf, "refs/tags"), 1); /* Just look for `init.templatedir` */ git_config(git_init_db_config, NULL); @@ -244,16 +219,16 @@ static int create_default_files(const char *template_path) */ if (shared_repository) { adjust_shared_perm(get_git_dir()); - adjust_shared_perm(git_path("refs")); - adjust_shared_perm(git_path("refs/heads")); - adjust_shared_perm(git_path("refs/tags")); + adjust_shared_perm(git_path_buf(&buf, "refs")); + adjust_shared_perm(git_path_buf(&buf, "refs/heads")); + adjust_shared_perm(git_path_buf(&buf, "refs/tags")); } /* * Create the default symlink from ".git/HEAD" to the "master" * branch, if it does not exist yet. */ - strcpy(path + len, "HEAD"); + path = git_path_buf(&buf, "HEAD"); reinit = (!access(path, R_OK) || readlink(path, junk, sizeof(junk)-1) != -1); if (!reinit) { @@ -262,13 +237,12 @@ static int create_default_files(const char *template_path) } /* This forces creation of new config file */ - sprintf(repo_version_string, "%d", GIT_REPO_VERSION); + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", GIT_REPO_VERSION); git_config_set("core.repositoryformatversion", repo_version_string); - path[len] = 0; - strcpy(path + len, "config"); - /* Check filemode trustability */ + path = git_path_buf(&buf, "config"); filemode = TEST_FILEMODE; if (TEST_FILEMODE && !lstat(path, &st1)) { struct stat st2; @@ -289,14 +263,13 @@ static int create_default_files(const char *template_path) /* allow template config file to override the default */ if (log_all_ref_updates == -1) git_config_set("core.logallrefupdates", "true"); - if (needs_work_tree_config(git_dir, work_tree)) + if (needs_work_tree_config(get_git_dir(), work_tree)) git_config_set("core.worktree", work_tree); } if (!reinit) { /* Check if symlink is supported in the work tree */ - path[len] = 0; - strcpy(path + len, "tXXXXXX"); + path = git_path_buf(&buf, "tXXXXXX"); if (!close(xmkstemp(path)) && !unlink(path) && !symlink("testing", path) && @@ -307,31 +280,35 @@ static int create_default_files(const char *template_path) git_config_set("core.symlinks", "false"); /* Check if the filesystem is case-insensitive */ - path[len] = 0; - strcpy(path + len, "CoNfIg"); + path = git_path_buf(&buf, "CoNfIg"); if (!access(path, F_OK)) git_config_set("core.ignorecase", "true"); - probe_utf8_pathname_composition(path, len); + probe_utf8_pathname_composition(); } + strbuf_release(&buf); return reinit; } static void create_object_directory(void) { - const char *object_directory = get_object_directory(); - int len = strlen(object_directory); - char *path = xmalloc(len + 40); + struct strbuf path = STRBUF_INIT; + size_t baselen; + + strbuf_addstr(&path, get_object_directory()); + baselen = path.len; + + safe_create_dir(path.buf, 1); - memcpy(path, object_directory, len); + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/pack"); + safe_create_dir(path.buf, 1); - safe_create_dir(object_directory, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/info"); + safe_create_dir(path.buf, 1); - free(path); + strbuf_release(&path); } int set_git_dir_init(const char *git_dir, const char *real_git_dir, @@ -414,13 +391,13 @@ int init_db(const char *template_dir, unsigned int flags) */ if (shared_repository < 0) /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); + xsnprintf(buf, sizeof(buf), "0%o", -shared_repository); else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_PERM_EVERYBODY); + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else - die("oops"); + die("BUG: invalid value for shared_repository"); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } diff --git a/builtin/log.c b/builtin/log.c index a491d3dea0..dda671d975 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -796,8 +796,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, if (filename.len >= PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) return error(_("name of output directory is too long")); - if (filename.buf[filename.len - 1] != '/') - strbuf_addch(&filename, '/'); + strbuf_complete(&filename, '/'); } if (rev->numbered_files) diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 4554dbc8a9..a31024900b 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -4,7 +4,7 @@ #include "remote.h" static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" +"git ls-remote [--heads] [--tags] [--upload-pack=<exec>]\n" " [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; /* @@ -93,12 +93,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (argv[i]) { int j; pattern = xcalloc(argc - i + 1, sizeof(const char *)); - for (j = i; j < argc; j++) { - int len = strlen(argv[j]); - char *p = xmalloc(len + 3); - sprintf(p, "*/%s", argv[j]); - pattern[j - i] = p; - } + for (j = i; j < argc; j++) + pattern[j - i] = xstrfmt("*/%s", argv[j]); } remote = remote_get(dest); if (!remote) { diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3b04a0f082..0e30d86230 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -96,12 +96,13 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base, if (!strcmp(type, blob_type)) { unsigned long size; if (sha1_object_info(sha1, &size) == OBJ_BAD) - strcpy(size_text, "BAD"); + xsnprintf(size_text, sizeof(size_text), + "BAD"); else - snprintf(size_text, sizeof(size_text), - "%lu", size); + xsnprintf(size_text, sizeof(size_text), + "%lu", size); } else - strcpy(size_text, "-"); + xsnprintf(size_text, sizeof(size_text), "-"); printf("%06o %s %s %7s\t", mode, type, find_unique_abbrev(sha1, abbrev), size_text); diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 999a5250fb..f6df274111 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -6,1029 +6,7 @@ #include "builtin.h" #include "utf8.h" #include "strbuf.h" - -static FILE *cmitmsg, *patchfile, *fin, *fout; - -static int keep_subject; -static int keep_non_patch_brackets_in_subject; -static const char *metainfo_charset; -static struct strbuf line = STRBUF_INIT; -static struct strbuf name = STRBUF_INIT; -static struct strbuf email = STRBUF_INIT; -static char *message_id; - -static enum { - TE_DONTCARE, TE_QP, TE_BASE64 -} transfer_encoding; - -static struct strbuf charset = STRBUF_INIT; -static int patch_lines; -static struct strbuf **p_hdr_data, **s_hdr_data; -static int use_scissors; -static int add_message_id; -static int use_inbody_headers = 1; - -#define MAX_HDR_PARSED 10 -#define MAX_BOUNDARIES 5 - -static void cleanup_space(struct strbuf *sb); - - -static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) -{ - struct strbuf *src = name; - if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || - strchr(name->buf, '<') || strchr(name->buf, '>')) - src = email; - else if (name == out) - return; - strbuf_reset(out); - strbuf_addbuf(out, src); -} - -static void parse_bogus_from(const struct strbuf *line) -{ - /* John Doe <johndoe> */ - - char *bra, *ket; - /* This is fallback, so do not bother if we already have an - * e-mail address. - */ - if (email.len) - return; - - bra = strchr(line->buf, '<'); - if (!bra) - return; - ket = strchr(bra, '>'); - if (!ket) - return; - - strbuf_reset(&email); - strbuf_add(&email, bra + 1, ket - bra - 1); - - strbuf_reset(&name); - strbuf_add(&name, line->buf, bra - line->buf); - strbuf_trim(&name); - get_sane_name(&name, &name, &email); -} - -static void handle_from(const struct strbuf *from) -{ - char *at; - size_t el; - struct strbuf f; - - strbuf_init(&f, from->len); - strbuf_addbuf(&f, from); - - at = strchr(f.buf, '@'); - if (!at) { - parse_bogus_from(from); - return; - } - - /* - * If we already have one email, don't take any confusing lines - */ - if (email.len && strchr(at + 1, '@')) { - strbuf_release(&f); - return; - } - - /* Pick up the string around '@', possibly delimited with <> - * pair; that is the email part. - */ - while (at > f.buf) { - char c = at[-1]; - if (isspace(c)) - break; - if (c == '<') { - at[-1] = ' '; - break; - } - at--; - } - el = strcspn(at, " \n\t\r\v\f>"); - strbuf_reset(&email); - strbuf_add(&email, at, el); - strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); - - /* The remainder is name. It could be - * - * - "John Doe <john.doe@xz>" (a), or - * - "john.doe@xz (John Doe)" (b), or - * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) - * - * but we have removed the email part, so - * - * - remove extra spaces which could stay after email (case 'c'), and - * - trim from both ends, possibly removing the () pair at the end - * (cases 'a' and 'b'). - */ - cleanup_space(&f); - strbuf_trim(&f); - if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { - strbuf_remove(&f, 0, 1); - strbuf_setlen(&f, f.len - 1); - } - - get_sane_name(&name, &f, &email); - strbuf_release(&f); -} - -static void handle_header(struct strbuf **out, const struct strbuf *line) -{ - if (!*out) { - *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(*out, line->len); - } else - strbuf_reset(*out); - - strbuf_addbuf(*out, line); -} - -/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt - * to have enough heuristics to grok MIME encoded patches often found - * on our mailing lists. For example, we do not even treat header lines - * case insensitively. - */ - -static int slurp_attr(const char *line, const char *name, struct strbuf *attr) -{ - const char *ends, *ap = strcasestr(line, name); - size_t sz; - - strbuf_setlen(attr, 0); - if (!ap) - return 0; - ap += strlen(name); - if (*ap == '"') { - ap++; - ends = "\""; - } - else - ends = "; \t"; - sz = strcspn(ap, ends); - strbuf_add(attr, ap, sz); - return 1; -} - -static struct strbuf *content[MAX_BOUNDARIES]; - -static struct strbuf **content_top = content; - -static void handle_content_type(struct strbuf *line) -{ - struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); - strbuf_init(boundary, line->len); - - if (slurp_attr(line->buf, "boundary=", boundary)) { - strbuf_insert(boundary, 0, "--", 2); - if (++content_top > &content[MAX_BOUNDARIES]) { - fprintf(stderr, "Too many boundaries to handle\n"); - exit(1); - } - *content_top = boundary; - boundary = NULL; - } - slurp_attr(line->buf, "charset=", &charset); - - if (boundary) { - strbuf_release(boundary); - free(boundary); - } -} - -static void handle_message_id(const struct strbuf *line) -{ - if (add_message_id) - message_id = strdup(line->buf); -} - -static void handle_content_transfer_encoding(const struct strbuf *line) -{ - if (strcasestr(line->buf, "base64")) - transfer_encoding = TE_BASE64; - else if (strcasestr(line->buf, "quoted-printable")) - transfer_encoding = TE_QP; - else - transfer_encoding = TE_DONTCARE; -} - -static int is_multipart_boundary(const struct strbuf *line) -{ - return (((*content_top)->len <= line->len) && - !memcmp(line->buf, (*content_top)->buf, (*content_top)->len)); -} - -static void cleanup_subject(struct strbuf *subject) -{ - size_t at = 0; - - while (at < subject->len) { - char *pos; - size_t remove; - - switch (subject->buf[at]) { - case 'r': case 'R': - if (subject->len <= at + 3) - break; - if ((subject->buf[at + 1] == 'e' || - subject->buf[at + 1] == 'E') && - subject->buf[at + 2] == ':') { - strbuf_remove(subject, at, 3); - continue; - } - at++; - break; - case ' ': case '\t': case ':': - strbuf_remove(subject, at, 1); - continue; - case '[': - pos = strchr(subject->buf + at, ']'); - if (!pos) - break; - remove = pos - subject->buf + at + 1; - if (!keep_non_patch_brackets_in_subject || - (7 <= remove && - memmem(subject->buf + at, remove, "PATCH", 5))) - strbuf_remove(subject, at, remove); - else { - at += remove; - /* - * If the input had a space after the ], keep - * it. We don't bother with finding the end of - * the space, since we later normalize it - * anyway. - */ - if (isspace(subject->buf[at])) - at += 1; - } - continue; - } - break; - } - strbuf_trim(subject); -} - -static void cleanup_space(struct strbuf *sb) -{ - size_t pos, cnt; - for (pos = 0; pos < sb->len; pos++) { - if (isspace(sb->buf[pos])) { - sb->buf[pos] = ' '; - for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); - strbuf_remove(sb, pos + 1, cnt); - } - } -} - -static void decode_header(struct strbuf *line); -static const char *header[MAX_HDR_PARSED] = { - "From","Subject","Date", -}; - -static inline int cmp_header(const struct strbuf *line, const char *hdr) -{ - int len = strlen(hdr); - return !strncasecmp(line->buf, hdr, len) && line->len > len && - line->buf[len] == ':' && isspace(line->buf[len + 1]); -} - -static int is_format_patch_separator(const char *line, int len) -{ - static const char SAMPLE[] = - "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; - const char *cp; - - if (len != strlen(SAMPLE)) - return 0; - if (!skip_prefix(line, "From ", &cp)) - return 0; - if (strspn(cp, "0123456789abcdef") != 40) - return 0; - cp += 40; - return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); -} - -static int check_header(const struct strbuf *line, - struct strbuf *hdr_data[], int overwrite) -{ - int i, ret = 0, len; - struct strbuf sb = STRBUF_INIT; - /* search for the interesting parts */ - for (i = 0; header[i]; i++) { - int len = strlen(header[i]); - if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { - /* Unwrap inline B and Q encoding, and optionally - * normalize the meta information to utf8. - */ - strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); - decode_header(&sb); - handle_header(&hdr_data[i], &sb); - ret = 1; - goto check_header_out; - } - } - - /* Content stuff */ - if (cmp_header(line, "Content-Type")) { - len = strlen("Content-Type: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - strbuf_insert(&sb, 0, "Content-Type: ", len); - handle_content_type(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Content-Transfer-Encoding")) { - len = strlen("Content-Transfer-Encoding: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_content_transfer_encoding(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Message-Id")) { - len = strlen("Message-Id: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_message_id(&sb); - ret = 1; - goto check_header_out; - } - - /* for inbody stuff */ - if (starts_with(line->buf, ">From") && isspace(line->buf[5])) { - ret = is_format_patch_separator(line->buf + 1, line->len - 1); - goto check_header_out; - } - if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { - for (i = 0; header[i]; i++) { - if (!strcmp("Subject", header[i])) { - handle_header(&hdr_data[i], line); - ret = 1; - goto check_header_out; - } - } - } - -check_header_out: - strbuf_release(&sb); - return ret; -} - -static int is_rfc2822_header(const struct strbuf *line) -{ - /* - * The section that defines the loosest possible - * field name is "3.6.8 Optional fields". - * - * optional-field = field-name ":" unstructured CRLF - * field-name = 1*ftext - * ftext = %d33-57 / %59-126 - */ - int ch; - char *cp = line->buf; - - /* Count mbox From headers as headers */ - if (starts_with(cp, "From ") || starts_with(cp, ">From ")) - return 1; - - while ((ch = *cp++)) { - if (ch == ':') - return 1; - if ((33 <= ch && ch <= 57) || - (59 <= ch && ch <= 126)) - continue; - break; - } - return 0; -} - -static int read_one_header_line(struct strbuf *line, FILE *in) -{ - /* Get the first part of the line. */ - if (strbuf_getline(line, in, '\n')) - return 0; - - /* - * Is it an empty line or not a valid rfc2822 header? - * If so, stop here, and return false ("not a header") - */ - strbuf_rtrim(line); - if (!line->len || !is_rfc2822_header(line)) { - /* Re-add the newline */ - strbuf_addch(line, '\n'); - return 0; - } - - /* - * Now we need to eat all the continuation lines.. - * Yuck, 2822 header "folding" - */ - for (;;) { - int peek; - struct strbuf continuation = STRBUF_INIT; - - peek = fgetc(in); ungetc(peek, in); - if (peek != ' ' && peek != '\t') - break; - if (strbuf_getline(&continuation, in, '\n')) - break; - continuation.buf[0] = ' '; - strbuf_rtrim(&continuation); - strbuf_addbuf(line, &continuation); - } - - return 1; -} - -static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) -{ - const char *in = q_seg->buf; - int c; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, q_seg->len); - - while ((c = *in++) != 0) { - if (c == '=') { - int d = *in++; - if (d == '\n' || !d) - break; /* drop trailing newline */ - strbuf_addch(out, (hexval(d) << 4) | hexval(*in++)); - continue; - } - if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ - c = 0x20; - strbuf_addch(out, c); - } - return out; -} - -static struct strbuf *decode_b_segment(const struct strbuf *b_seg) -{ - /* Decode in..ep, possibly in-place to ot */ - int c, pos = 0, acc = 0; - const char *in = b_seg->buf; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, b_seg->len); - - while ((c = *in++) != 0) { - if (c == '+') - c = 62; - else if (c == '/') - c = 63; - else if ('A' <= c && c <= 'Z') - c -= 'A'; - else if ('a' <= c && c <= 'z') - c -= 'a' - 26; - else if ('0' <= c && c <= '9') - c -= '0' - 52; - else - continue; /* garbage */ - switch (pos++) { - case 0: - acc = (c << 2); - break; - case 1: - strbuf_addch(out, (acc | (c >> 4))); - acc = (c & 15) << 4; - break; - case 2: - strbuf_addch(out, (acc | (c >> 2))); - acc = (c & 3) << 6; - break; - case 3: - strbuf_addch(out, (acc | c)); - acc = pos = 0; - break; - } - } - return out; -} - -static void convert_to_utf8(struct strbuf *line, const char *charset) -{ - char *out; - - if (!charset || !*charset) - return; - - if (same_encoding(metainfo_charset, charset)) - return; - out = reencode_string(line->buf, metainfo_charset, charset); - if (!out) - die("cannot convert from %s to %s", - charset, metainfo_charset); - strbuf_attach(line, out, strlen(out), strlen(out)); -} - -static int decode_header_bq(struct strbuf *it) -{ - char *in, *ep, *cp; - struct strbuf outbuf = STRBUF_INIT, *dec; - struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; - int rfc2047 = 0; - - in = it->buf; - while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { - int encoding; - strbuf_reset(&charset_q); - strbuf_reset(&piecebuf); - rfc2047 = 1; - - if (in != ep) { - /* - * We are about to process an encoded-word - * that begins at ep, but there is something - * before the encoded word. - */ - char *scan; - for (scan = in; scan < ep; scan++) - if (!isspace(*scan)) - break; - - if (scan != ep || in == it->buf) { - /* - * We should not lose that "something", - * unless we have just processed an - * encoded-word, and there is only LWS - * before the one we are about to process. - */ - strbuf_add(&outbuf, in, ep - in); - } - } - /* E.g. - * ep : "=?iso-2022-jp?B?GyR...?= foo" - * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" - */ - ep += 2; - - if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) - goto decode_header_bq_out; - - if (cp + 3 - it->buf > it->len) - goto decode_header_bq_out; - strbuf_add(&charset_q, ep, cp - ep); - - encoding = cp[1]; - if (!encoding || cp[2] != '?') - goto decode_header_bq_out; - ep = strstr(cp + 3, "?="); - if (!ep) - goto decode_header_bq_out; - strbuf_add(&piecebuf, cp + 3, ep - cp - 3); - switch (tolower(encoding)) { - default: - goto decode_header_bq_out; - case 'b': - dec = decode_b_segment(&piecebuf); - break; - case 'q': - dec = decode_q_segment(&piecebuf, 1); - break; - } - if (metainfo_charset) - convert_to_utf8(dec, charset_q.buf); - - strbuf_addbuf(&outbuf, dec); - strbuf_release(dec); - free(dec); - in = ep + 2; - } - strbuf_addstr(&outbuf, in); - strbuf_reset(it); - strbuf_addbuf(it, &outbuf); -decode_header_bq_out: - strbuf_release(&outbuf); - strbuf_release(&charset_q); - strbuf_release(&piecebuf); - return rfc2047; -} - -static void decode_header(struct strbuf *it) -{ - if (decode_header_bq(it)) - return; - /* otherwise "it" is a straight copy of the input. - * This can be binary guck but there is no charset specified. - */ - if (metainfo_charset) - convert_to_utf8(it, ""); -} - -static void decode_transfer_encoding(struct strbuf *line) -{ - struct strbuf *ret; - - switch (transfer_encoding) { - case TE_QP: - ret = decode_q_segment(line, 0); - break; - case TE_BASE64: - ret = decode_b_segment(line); - break; - case TE_DONTCARE: - default: - return; - } - strbuf_reset(line); - strbuf_addbuf(line, ret); - strbuf_release(ret); - free(ret); -} - -static void handle_filter(struct strbuf *line); - -static int find_boundary(void) -{ - while (!strbuf_getline(&line, fin, '\n')) { - if (*content_top && is_multipart_boundary(&line)) - return 1; - } - return 0; -} - -static int handle_boundary(void) -{ - struct strbuf newline = STRBUF_INIT; - - strbuf_addch(&newline, '\n'); -again: - if (line.len >= (*content_top)->len + 2 && - !memcmp(line.buf + (*content_top)->len, "--", 2)) { - /* we hit an end boundary */ - /* pop the current boundary off the stack */ - strbuf_release(*content_top); - free(*content_top); - *content_top = NULL; - - /* technically won't happen as is_multipart_boundary() - will fail first. But just in case.. - */ - if (--content_top < content) { - fprintf(stderr, "Detected mismatched boundaries, " - "can't recover\n"); - exit(1); - } - handle_filter(&newline); - strbuf_release(&newline); - - /* skip to the next boundary */ - if (!find_boundary()) - return 0; - goto again; - } - - /* set some defaults */ - transfer_encoding = TE_DONTCARE; - strbuf_reset(&charset); - - /* slurp in this section's info */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 0); - - strbuf_release(&newline); - /* replenish line */ - if (strbuf_getline(&line, fin, '\n')) - return 0; - strbuf_addch(&line, '\n'); - return 1; -} - -static inline int patchbreak(const struct strbuf *line) -{ - size_t i; - - /* Beginning of a "diff -" header? */ - if (starts_with(line->buf, "diff -")) - return 1; - - /* CVS "Index: " line? */ - if (starts_with(line->buf, "Index: ")) - return 1; - - /* - * "--- <filename>" starts patches without headers - * "---<sp>*" is a manual separator - */ - if (line->len < 4) - return 0; - - if (starts_with(line->buf, "---")) { - /* space followed by a filename? */ - if (line->buf[3] == ' ' && !isspace(line->buf[4])) - return 1; - /* Just whitespace? */ - for (i = 3; i < line->len; i++) { - unsigned char c = line->buf[i]; - if (c == '\n') - return 1; - if (!isspace(c)) - break; - } - return 0; - } - return 0; -} - -static int is_scissors_line(const struct strbuf *line) -{ - size_t i, len = line->len; - int scissors = 0, gap = 0; - int first_nonblank = -1; - int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; - const char *buf = line->buf; - - for (i = 0; i < len; i++) { - if (isspace(buf[i])) { - if (in_perforation) { - perforation++; - gap++; - } - continue; - } - last_nonblank = i; - if (first_nonblank < 0) - first_nonblank = i; - if (buf[i] == '-') { - in_perforation = 1; - perforation++; - continue; - } - if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) || - !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) { - in_perforation = 1; - perforation += 2; - scissors += 2; - i++; - continue; - } - in_perforation = 0; - } - - /* - * The mark must be at least 8 bytes long (e.g. "-- >8 --"). - * Even though there can be arbitrary cruft on the same line - * (e.g. "cut here"), in order to avoid misidentification, the - * perforation must occupy more than a third of the visible - * width of the line, and dashes and scissors must occupy more - * than half of the perforation. - */ - - visible = last_nonblank - first_nonblank + 1; - return (scissors && 8 <= visible && - visible < perforation * 3 && - gap * 2 < perforation); -} - -static int handle_commit_msg(struct strbuf *line) -{ - static int still_looking = 1; - - if (!cmitmsg) - return 0; - - if (still_looking) { - if (!line->len || (line->len == 1 && line->buf[0] == '\n')) - return 0; - } - - if (use_inbody_headers && still_looking) { - still_looking = check_header(line, s_hdr_data, 0); - if (still_looking) - return 0; - } else - /* Only trim the first (blank) line of the commit message - * when ignoring in-body headers. - */ - still_looking = 0; - - /* normalize the log message to UTF-8. */ - if (metainfo_charset) - convert_to_utf8(line, charset.buf); - - if (use_scissors && is_scissors_line(line)) { - int i; - if (fseek(cmitmsg, 0L, SEEK_SET)) - die_errno("Could not rewind output message file"); - if (ftruncate(fileno(cmitmsg), 0)) - die_errno("Could not truncate output message file at scissors"); - still_looking = 1; - - /* - * We may have already read "secondary headers"; purge - * them to give ourselves a clean restart. - */ - for (i = 0; header[i]; i++) { - if (s_hdr_data[i]) - strbuf_release(s_hdr_data[i]); - s_hdr_data[i] = NULL; - } - return 0; - } - - if (patchbreak(line)) { - if (message_id) - fprintf(cmitmsg, "Message-Id: %s\n", message_id); - fclose(cmitmsg); - cmitmsg = NULL; - return 1; - } - - fputs(line->buf, cmitmsg); - return 0; -} - -static void handle_patch(const struct strbuf *line) -{ - fwrite(line->buf, 1, line->len, patchfile); - patch_lines++; -} - -static void handle_filter(struct strbuf *line) -{ - static int filter = 0; - - /* filter tells us which part we left off on */ - switch (filter) { - case 0: - if (!handle_commit_msg(line)) - break; - filter++; - case 1: - handle_patch(line); - break; - } -} - -static void handle_body(void) -{ - struct strbuf prev = STRBUF_INIT; - - /* Skip up to the first boundary */ - if (*content_top) { - if (!find_boundary()) - goto handle_body_out; - } - - do { - /* process any boundary lines */ - if (*content_top && is_multipart_boundary(&line)) { - /* flush any leftover */ - if (prev.len) { - handle_filter(&prev); - strbuf_reset(&prev); - } - if (!handle_boundary()) - goto handle_body_out; - } - - /* Unwrap transfer encoding */ - decode_transfer_encoding(&line); - - switch (transfer_encoding) { - case TE_BASE64: - case TE_QP: - { - struct strbuf **lines, **it, *sb; - - /* Prepend any previous partial lines */ - strbuf_insert(&line, 0, prev.buf, prev.len); - strbuf_reset(&prev); - - /* - * This is a decoded line that may contain - * multiple new lines. Pass only one chunk - * at a time to handle_filter() - */ - lines = strbuf_split(&line, '\n'); - for (it = lines; (sb = *it); it++) { - if (*(it + 1) == NULL) /* The last line */ - if (sb->buf[sb->len - 1] != '\n') { - /* Partial line, save it for later. */ - strbuf_addbuf(&prev, sb); - break; - } - handle_filter(sb); - } - /* - * The partial chunk is saved in "prev" and will be - * appended by the next iteration of read_line_with_nul(). - */ - strbuf_list_free(lines); - break; - } - default: - handle_filter(&line); - } - - } while (!strbuf_getwholeline(&line, fin, '\n')); - -handle_body_out: - strbuf_release(&prev); -} - -static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) -{ - const char *sp = data->buf; - while (1) { - char *ep = strchr(sp, '\n'); - int len; - if (!ep) - len = strlen(sp); - else - len = ep - sp; - fprintf(fout, "%s: %.*s\n", hdr, len, sp); - if (!ep) - break; - sp = ep + 1; - } -} - -static void handle_info(void) -{ - struct strbuf *hdr; - int i; - - for (i = 0; header[i]; i++) { - /* only print inbody headers if we output a patch file */ - if (patch_lines && s_hdr_data[i]) - hdr = s_hdr_data[i]; - else if (p_hdr_data[i]) - hdr = p_hdr_data[i]; - else - continue; - - if (!strcmp(header[i], "Subject")) { - if (!keep_subject) { - cleanup_subject(hdr); - cleanup_space(hdr); - } - output_header_lines(fout, "Subject", hdr); - } else if (!strcmp(header[i], "From")) { - cleanup_space(hdr); - handle_from(hdr); - fprintf(fout, "Author: %s\n", name.buf); - fprintf(fout, "Email: %s\n", email.buf); - } else { - cleanup_space(hdr); - fprintf(fout, "%s: %s\n", header[i], hdr->buf); - } - } - fprintf(fout, "\n"); -} - -static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) -{ - int peek; - fin = in; - fout = out; - - cmitmsg = fopen(msg, "w"); - if (!cmitmsg) { - perror(msg); - return -1; - } - patchfile = fopen(patch, "w"); - if (!patchfile) { - perror(patch); - fclose(cmitmsg); - return -1; - } - - p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data)); - s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data)); - - do { - peek = fgetc(in); - } while (isspace(peek)); - ungetc(peek, in); - - /* process the email header */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 1); - - handle_body(); - handle_info(); - - return 0; -} - -static int git_mailinfo_config(const char *var, const char *value, void *unused) -{ - if (!starts_with(var, "mailinfo.")) - return git_default_config(var, value, unused); - if (!strcmp(var, "mailinfo.scissors")) { - use_scissors = git_config_bool(var, value); - return 0; - } - /* perhaps others here */ - return 0; -} +#include "mailinfo.h" static const char mailinfo_usage[] = "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; @@ -1036,34 +14,36 @@ static const char mailinfo_usage[] = int cmd_mailinfo(int argc, const char **argv, const char *prefix) { const char *def_charset; + struct mailinfo mi; + int status; /* NEEDSWORK: might want to do the optional .git/ directory * discovery */ - git_config(git_mailinfo_config, NULL); + setup_mailinfo(&mi); def_charset = get_commit_output_encoding(); - metainfo_charset = def_charset; + mi.metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { if (!strcmp(argv[1], "-k")) - keep_subject = 1; + mi.keep_subject = 1; else if (!strcmp(argv[1], "-b")) - keep_non_patch_brackets_in_subject = 1; + mi.keep_non_patch_brackets_in_subject = 1; else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id")) - add_message_id = 1; + mi.add_message_id = 1; else if (!strcmp(argv[1], "-u")) - metainfo_charset = def_charset; + mi.metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) - metainfo_charset = NULL; + mi.metainfo_charset = NULL; else if (starts_with(argv[1], "--encoding=")) - metainfo_charset = argv[1] + 11; + mi.metainfo_charset = argv[1] + 11; else if (!strcmp(argv[1], "--scissors")) - use_scissors = 1; + mi.use_scissors = 1; else if (!strcmp(argv[1], "--no-scissors")) - use_scissors = 0; + mi.use_scissors = 0; else if (!strcmp(argv[1], "--no-inbody-headers")) - use_inbody_headers = 0; + mi.use_inbody_headers = 0; else usage(mailinfo_usage); argc--; argv++; @@ -1072,5 +52,10 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (argc != 3) usage(mailinfo_usage); - return !!mailinfo(stdin, stdout, argv[1], argv[2]); + mi.input = stdin; + mi.output = stdout; + status = !!mailinfo(&mi, argv[1], argv[2]); + clear_mailinfo(&mi); + + return status; } diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 8e02ea109a..104277acc4 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -98,30 +98,37 @@ static int populate_maildir_list(struct string_list *list, const char *path) { DIR *dir; struct dirent *dent; - char name[PATH_MAX]; + char *name = NULL; char *subs[] = { "cur", "new", NULL }; char **sub; + int ret = -1; for (sub = subs; *sub; ++sub) { - snprintf(name, sizeof(name), "%s/%s", path, *sub); + free(name); + name = xstrfmt("%s/%s", path, *sub); if ((dir = opendir(name)) == NULL) { if (errno == ENOENT) continue; error("cannot opendir %s (%s)", name, strerror(errno)); - return -1; + goto out; } while ((dent = readdir(dir)) != NULL) { if (dent->d_name[0] == '.') continue; - snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); + free(name); + name = xstrfmt("%s/%s", *sub, dent->d_name); string_list_insert(list, name); } closedir(dir); } - return 0; + ret = 0; + +out: + free(name); + return ret; } static int maildir_filename_cmp(const char *a, const char *b) @@ -148,8 +155,8 @@ static int maildir_filename_cmp(const char *a, const char *b) static int split_maildir(const char *maildir, const char *dir, int nr_prec, int skip) { - char file[PATH_MAX]; - char name[PATH_MAX]; + char *file = NULL; + FILE *f = NULL; int ret = -1; int i; struct string_list list = STRING_LIST_INIT_DUP; @@ -160,8 +167,11 @@ static int split_maildir(const char *maildir, const char *dir, goto out; for (i = 0; i < list.nr; i++) { - FILE *f; - snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); + char *name; + + free(file); + file = xstrfmt("%s/%s", maildir, list.items[i].string); + f = fopen(file, "r"); if (!f) { error("cannot open mail %s (%s)", file, strerror(errno)); @@ -173,14 +183,19 @@ static int split_maildir(const char *maildir, const char *dir, goto out; } - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); split_one(f, name, 1); + free(name); fclose(f); + f = NULL; } ret = skip; out: + if (f) + fclose(f); + free(file); string_list_clear(&list, 1); return ret; } @@ -188,7 +203,6 @@ out: static int split_mbox(const char *file, const char *dir, int allow_bare, int nr_prec, int skip) { - char name[PATH_MAX]; int ret = -1; int peek; @@ -215,8 +229,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, } while (!file_done) { - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); file_done = split_one(f, name, allow_bare); + free(name); } if (f != stdin) diff --git a/builtin/merge-file.c b/builtin/merge-file.c index ea8093f676..55447053f2 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -75,7 +75,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) names[i] = argv[i]; if (read_mmfile(mmfs + i, fname)) return -1; - if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) + if (mmfs[i].size > MAX_XDIFF_SIZE || + buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s", argv[i]); } @@ -103,5 +104,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) free(result.ptr); } + if (ret > 127) + ret = 127; + return ret; } diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 1a1eafa6fd..1c3427c36c 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -9,7 +9,7 @@ static int merge_entry(int pos, const char *path) { int found; const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL }; - char hexbuf[4][60]; + char hexbuf[4][GIT_SHA1_HEXSZ + 1]; char ownbuf[4][60]; if (pos >= active_nr) @@ -22,8 +22,8 @@ static int merge_entry(int pos, const char *path) if (strcmp(ce->name, path)) break; found++; - strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ce->ce_mode); + sha1_to_hex_r(hexbuf[stage], ce->sha1); + xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index a90f28f34d..491efd556e 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -14,7 +14,7 @@ static const char *better_branch_name(const char *branch) if (strlen(branch) != 40) return branch; - sprintf(githead_env, "GITHEAD_%s", branch); + xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); return name ? name : branch; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index f9ab48597e..2a4aafec6a 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -118,7 +118,8 @@ static void show_diff(struct merge_list *entry) if (!dst.ptr) size = 0; dst.size = size; - xdi_diff(&src, &dst, &xpp, &xecfg, &ecb); + if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb)) + die("unable to generate diff"); free(src.ptr); free(dst.ptr); } diff --git a/builtin/merge.c b/builtin/merge.c index a0edacab20..bbf3110f88 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -806,7 +806,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) abort_commit(remoteheads, NULL); } read_merge_msg(&msg); - stripspace(&msg, 0 < option_edit); + strbuf_stripspace(&msg, 0 < option_edit); if (!msg.len) abort_commit(remoteheads, _("Empty commit message.")); strbuf_release(&merge_msg); @@ -1019,7 +1019,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit, int *head_subsumed, struct commit_list *remoteheads) { - struct commit_list *parents, *next, **remotes = &remoteheads; + struct commit_list *parents, **remotes; /* * Is the current HEAD reachable from another commit being @@ -1033,16 +1033,14 @@ static struct commit_list *reduce_parents(struct commit *head_commit, /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - for (remoteheads = NULL, remotes = &remoteheads; - parents; - parents = next) { - struct commit *commit = parents->item; - next = parents->next; + remoteheads = NULL; + remotes = &remoteheads; + while (parents) { + struct commit *commit = pop_commit(&parents); if (commit == head_commit) *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; - free(parents); } return remoteheads; } @@ -1319,13 +1317,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (verify_signatures) { for (p = remoteheads; p; p = p->next) { struct commit *commit = p->item; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; struct signature_check signature_check; memset(&signature_check, 0, sizeof(signature_check)); check_commit_signature(commit, &signature_check); - strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + find_unique_abbrev_r(hex, commit->object.sha1, DEFAULT_ABBREV); switch (signature_check.result) { case 'G': break; @@ -1415,15 +1413,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; struct commit *commit; - char hex[41]; - strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); - - if (verbosity >= 0) - printf(_("Updating %s..%s\n"), - hex, - find_unique_abbrev(remoteheads->item->object.sha1, - DEFAULT_ABBREV)); + if (verbosity >= 0) { + char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1]; + find_unique_abbrev_r(from, head_commit->object.sha1, + DEFAULT_ABBREV); + find_unique_abbrev_r(to, remoteheads->item->object.sha1, + DEFAULT_ABBREV); + printf(_("Updating %s..%s\n"), from, to); + } strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, diff --git a/builtin/mktag.c b/builtin/mktag.c index 640ab64f41..031b750f06 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -154,7 +154,7 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) unsigned char result_sha1[20]; if (argc != 1) - usage("git mktag < signaturefile"); + usage("git mktag"); if (strbuf_read(&buf, 0, 4096) < 0) { die_errno("could not read from stdin"); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 248a3eb260..0377fc1142 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -55,20 +55,16 @@ copy_data: parents; parents = parents->next, parent_number++) { if (parent_number > 1) { - int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~<n> */ - 1 + 2 + /* ^NN */ - 1); - - if (len > 2 && !strcmp(tip_name + len - 2, "^0")) - len -= 2; + size_t len; + char *new_name; + + strip_suffix(tip_name, "^0", &len); if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); + new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, + generation, parent_number); else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); + new_name = xstrfmt("%.*s^%d", (int)len, tip_name, + parent_number); name_rev(parents->item, new_name, 0, distance + MERGE_TRAVERSAL_WEIGHT, 0); diff --git a/builtin/notes.c b/builtin/notes.c index 3608c64785..515cebbeb8 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -19,7 +19,7 @@ #include "string-list.h" #include "notes-merge.h" #include "notes-utils.h" -#include "branch.h" +#include "worktree.h" static const char * const git_notes_usage[] = { N_("git notes [--ref <notes-ref>] [list [<object>]]"), @@ -192,7 +192,7 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d, if (launch_editor(d->edit_path, &d->buf, NULL)) { die(_("Please supply the note contents using either -m or -F option")); } - stripspace(&d->buf, 1); + strbuf_stripspace(&d->buf, 1); } } @@ -215,7 +215,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) if (d->buf.len) strbuf_addch(&d->buf, '\n'); strbuf_addstr(&d->buf, arg); - stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0); d->given = 1; return 0; @@ -232,7 +232,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) die_errno(_("cannot read '%s'"), arg); } else if (strbuf_read_file(&d->buf, arg, 1024) < 0) die_errno(_("could not open or read '%s'"), arg); - stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0); d->given = 1; return 0; diff --git a/builtin/patch-id.c b/builtin/patch-id.c index ba34dac4d2..366ce5a5d4 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -165,7 +165,7 @@ static void generate_id_list(int stable) strbuf_release(&line_buf); } -static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch"; +static const char patch_id_usage[] = "git patch-id [--stable | --unstable]"; static int git_patch_id_config(const char *var, const char *value, void *cb) { diff --git a/builtin/prune.c b/builtin/prune.c index 10b03d3e4c..8f4f052285 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -119,6 +119,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + if (repository_format_precious_objects) + die(_("cannot prune in a precious-objects repo")); + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/pull.c b/builtin/pull.c index a39bb0a11f..bf3fd3f9c8 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -66,7 +66,7 @@ static int parse_opt_rebase(const struct option *opt, const char *arg, int unset } static const char * const pull_usage[] = { - N_("git pull [options] [<repository> [<refspec>...]]"), + N_("git pull [<options>] [<repository> [<refspec>...]]"), NULL }; diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 2379e11069..8c693e7568 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -90,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages, debug_stage("index", stages[0], o); for (i = 1; i <= o->merge_size; i++) { char buf[24]; - sprintf(buf, "ent#%d", i); + xsnprintf(buf, sizeof(buf), "ent#%d", i); debug_stage(buf, stages[i], o); } return 0; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e6b93d0264..f06f70a75f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -195,9 +195,6 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static void show_ref(const char *path, const unsigned char *sha1) { - if (ref_is_hidden(path)) - return; - if (sent_capabilities) { packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); } else { @@ -219,9 +216,14 @@ static void show_ref(const char *path, const unsigned char *sha1) } } -static int show_ref_cb(const char *path, const struct object_id *oid, int flag, void *unused) +static int show_ref_cb(const char *path_full, const struct object_id *oid, + int flag, void *unused) { - path = strip_namespace(path); + const char *path = strip_namespace(path_full); + + if (ref_is_hidden(path, path_full)) + return 0; + /* * Advertise refs outside our current namespace as ".have" * refs, so that the client can use them to minimize data @@ -280,10 +282,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2 static void report_message(const char *prefix, const char *err, va_list params) { - int sz = strlen(prefix); + int sz; char msg[4096]; - strncpy(msg, prefix, sz); + sz = xsnprintf(msg, sizeof(msg), "%s", prefix); sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params); if (sz > (sizeof(msg) - 1)) sz = sizeof(msg) - 1; @@ -1071,8 +1073,11 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) const char *dst_name; struct string_list_item *item; struct command *dst_cmd; - unsigned char sha1[20]; - char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41]; + unsigned char sha1[GIT_SHA1_RAWSZ]; + char cmd_oldh[GIT_SHA1_HEXSZ + 1], + cmd_newh[GIT_SHA1_HEXSZ + 1], + dst_oldh[GIT_SHA1_HEXSZ + 1], + dst_newh[GIT_SHA1_HEXSZ + 1]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); @@ -1103,10 +1108,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) dst_cmd->skip_update = 1; - strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV)); - strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV)); - strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV)); - strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); + find_unique_abbrev_r(cmd_oldh, cmd->old_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(cmd_newh, cmd->new_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(dst_oldh, dst_cmd->old_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(dst_newh, dst_cmd->new_sha1, DEFAULT_ABBREV); rp_error("refusing inconsistent update between symref '%s' (%s..%s) and" " its target '%s' (%s..%s)", cmd->ref_name, cmd_oldh, cmd_newh, @@ -1192,16 +1197,29 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) static void reject_updates_to_hidden(struct command *commands) { + struct strbuf refname_full = STRBUF_INIT; + size_t prefix_len; struct command *cmd; + strbuf_addstr(&refname_full, get_git_namespace()); + prefix_len = refname_full.len; + for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string || !ref_is_hidden(cmd->ref_name)) + if (cmd->error_string) + continue; + + strbuf_setlen(&refname_full, prefix_len); + strbuf_addstr(&refname_full, cmd->ref_name); + + if (!ref_is_hidden(cmd->ref_name, refname_full.buf)) continue; if (is_null_sha1(cmd->new_sha1)) cmd->error_string = "deny deleting a hidden ref"; else cmd->error_string = "deny updating a hidden ref"; } + + strbuf_release(&refname_full); } static int should_process_cmd(struct command *cmd) @@ -1521,15 +1539,18 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (status) return "unpack-objects abnormal exit"; } else { - int s; - char keep_arg[256]; - - s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); + char hostname[256]; argv_array_pushl(&child.args, "index-pack", - "--stdin", hdr_arg, keep_arg, NULL); + "--stdin", hdr_arg, NULL); + + if (gethostname(hostname, sizeof(hostname))) + xsnprintf(hostname, sizeof(hostname), "localhost"); + argv_array_pushf(&child.args, + "--keep=receive-pack %"PRIuMAX" on %s", + (uintmax_t)getpid(), + hostname); + if (fsck_objects) argv_array_pushf(&child.args, "--strict%s", fsck_msg_types.buf); diff --git a/builtin/reflog.c b/builtin/reflog.c index f96ca2a27d..cf1145e635 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -218,7 +218,6 @@ static int keep_entry(struct commit **it, unsigned char *sha1) */ static void mark_reachable(struct expire_reflog_policy_cb *cb) { - struct commit *commit; struct commit_list *pending; unsigned long expire_limit = cb->mark_limit; struct commit_list *leftover = NULL; @@ -228,11 +227,8 @@ static void mark_reachable(struct expire_reflog_policy_cb *cb) pending = cb->mark_list; while (pending) { - struct commit_list *entry = pending; struct commit_list *parent; - pending = entry->next; - commit = entry->item; - free(entry); + struct commit *commit = pop_commit(&pending); if (commit->object.flags & REACHABLE) continue; if (parse_commit(commit)) diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 3b8c22cc75..e3cd25d580 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "transport.h" #include "run-command.h" +#include "pkt-line.h" /* * URL syntax: @@ -142,36 +143,11 @@ static const char **parse_argv(const char *arg, const char *service) static void send_git_request(int stdin_fd, const char *serv, const char *repo, const char *vhost) { - size_t bufferspace; - size_t wpos = 0; - char *buffer; - - /* - * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and - * 6 bytes extra (xxxx \0) if there is no vhost. - */ - if (vhost) - bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; + if (!vhost) + packet_write(stdin_fd, "%s %s%c", serv, repo, 0); else - bufferspace = strlen(serv) + strlen(repo) + 6; - - if (bufferspace > 0xFFFF) - die("Request too large to send"); - buffer = xmalloc(bufferspace); - - /* Make the packet. */ - wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, - serv, repo, 0); - - /* Add vhost if any. */ - if (vhost) - sprintf(buffer + wpos, "host=%s%c", vhost, 0); - - /* Send the request */ - if (write_in_full(stdin_fd, buffer, bufferspace) < 0) - die_errno("Failed to send request"); - - free(buffer); + packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, + vhost, 0); } static int run_child(const char *arg, const char *service) diff --git a/builtin/remote.c b/builtin/remote.c index 181668dedd..e4c3ea130c 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = { N_("git remote prune [-n | --dry-run] <name>"), N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), N_("git remote set-branches [--add] <name> <branch>..."), + N_("git remote get-url [--push] [--all] <name>"), N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), N_("git remote set-url --add <name> <newurl>"), N_("git remote set-url --delete <name> <url>"), @@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = { NULL }; +static const char * const builtin_remote_geturl_usage[] = { + N_("git remote get-url [--push] [--all] <name>"), + NULL +}; + static const char * const builtin_remote_seturl_usage[] = { N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), N_("git remote set-url --add <name> <newurl>"), @@ -1467,6 +1473,57 @@ static int set_branches(int argc, const char **argv) return set_remote_branches(argv[0], argv + 1, add_mode); } +static int get_url(int argc, const char **argv) +{ + int i, push_mode = 0, all_mode = 0; + const char *remotename = NULL; + struct remote *remote; + const char **url; + int url_nr; + struct option options[] = { + OPT_BOOL('\0', "push", &push_mode, + N_("query push URLs rather than fetch URLs")), + OPT_BOOL('\0', "all", &all_mode, + N_("return all URLs")), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0); + + if (argc != 1) + usage_with_options(builtin_remote_geturl_usage, options); + + remotename = argv[0]; + + if (!remote_is_configured(remotename)) + die(_("No such remote '%s'"), remotename); + remote = remote_get(remotename); + + url_nr = 0; + if (push_mode) { + url = remote->pushurl; + url_nr = remote->pushurl_nr; + } + /* else fetch mode */ + + /* Use the fetch URL when no push URLs were found or requested. */ + if (!url_nr) { + url = remote->url; + url_nr = remote->url_nr; + } + + if (!url_nr) + die(_("no URLs configured for remote '%s'"), remotename); + + if (all_mode) { + for (i = 0; i < url_nr; i++) + printf_ln("%s", url[i]); + } else { + printf_ln("%s", *url); + } + + return 0; +} + static int set_url(int argc, const char **argv) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; @@ -1576,6 +1633,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = set_head(argc, argv); else if (!strcmp(argv[0], "set-branches")) result = set_branches(argc, argv); + else if (!strcmp(argv[0], "get-url")) + result = get_url(argc, argv); else if (!strcmp(argv[0], "set-url")) result = set_url(argc, argv); else if (!strcmp(argv[0], "show")) diff --git a/builtin/repack.c b/builtin/repack.c index 70b9b1eaf1..945611006a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -193,6 +193,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_repack_options, git_repack_usage, 0); + if (delete_redundant && repository_format_precious_objects) + die(_("cannot delete packs in a precious-objects repo")); + if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; diff --git a/builtin/rerere.c b/builtin/rerere.c index 8c6cb6cdc6..1bf72423bf 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -29,9 +29,10 @@ static int diff_two(const char *file1, const char *label1, xdemitconf_t xecfg; xdemitcb_t ecb; mmfile_t minus, plus; + int ret; if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2)) - return 1; + return -1; printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); @@ -40,11 +41,11 @@ static int diff_two(const char *file1, const char *label1, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; - xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); + ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); - return 0; + return ret; } int cmd_rerere(int argc, const char **argv, const char *prefix) @@ -104,7 +105,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const struct rerere_id *id = merge_rr.items[i].util; - diff_two(rerere_path(id, "preimage"), path, path, path); + if (diff_two(rerere_path(id, "preimage"), path, path, path)) + die("unable to generate diff for %s", rerere_path(id, NULL)); } } else usage_with_options(rerere_usage, options); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index d80d1ed359..491d298fa2 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -217,7 +217,7 @@ static void print_var_int(const char *var, int val) static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { int cnt, flags = info->flags; - char hex[41] = ""; + char hex[GIT_SHA1_HEXSZ + 1] = ""; struct commit_list *tried; struct rev_info *revs = info->revs; @@ -242,7 +242,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) cnt = reaches; if (revs->commits) - strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); + sha1_to_hex_r(hex, revs->commits->item->object.sha1); 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 02d747dcb1..e92a782f77 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -281,11 +281,8 @@ static int try_difference(const char *arg) b = lookup_commit_reference(end); exclude = get_merge_bases(a, b); while (exclude) { - struct commit_list *n = exclude->next; - show_rev(REVERSED, - exclude->item->object.sha1,NULL); - free(exclude); - exclude = n; + struct commit *commit = pop_commit(&exclude); + show_rev(REVERSED, commit->object.sha1, NULL); } } *dotdot = '.'; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 408ce70307..e17744bc0d 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -3,6 +3,7 @@ #include "refs.h" #include "builtin.h" #include "color.h" +#include "argv-array.h" #include "parse-options.h" static const char* show_branch_usage[] = { @@ -16,9 +17,7 @@ static const char* show_branch_usage[] = { static int showbranch_use_color = -1; -static int default_num; -static int default_alloc; -static const char **default_arg; +static struct argv_array default_args = ARGV_ARRAY_INIT; #define UNINTERESTING 01 @@ -53,17 +52,6 @@ static struct commit *interesting(struct commit_list *list) return NULL; } -static struct commit *pop_one_commit(struct commit_list **list_p) -{ - struct commit *commit; - struct commit_list *list; - list = *list_p; - commit = list->item; - *list_p = list->next; - free(list); - return commit; -} - struct commit_name { const char *head_name; /* which head's ancestor? */ int generation; /* how many parents away from head_name */ @@ -213,7 +201,7 @@ static void join_revs(struct commit_list **list_p, while (*list_p) { struct commit_list *parents; int still_interesting = !!interesting(*list_p); - struct commit *commit = pop_one_commit(list_p); + struct commit *commit = pop_commit(list_p); int flags = commit->object.flags & all_mask; if (!still_interesting && extra <= 0) @@ -504,7 +492,7 @@ static int show_merge_base(struct commit_list *seen, int num_rev) int exit_status = 1; while (seen) { - struct commit *commit = pop_one_commit(&seen); + struct commit *commit = pop_commit(&seen); int flags = commit->object.flags & all_mask; if (!(flags & UNINTERESTING) && ((flags & all_revs) == all_revs)) { @@ -567,16 +555,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) * default_arg is now passed to parse_options(), so we need to * mimic the real argv a bit better. */ - if (!default_num) { - default_alloc = 20; - default_arg = xcalloc(default_alloc, sizeof(*default_arg)); - default_arg[default_num++] = "show-branch"; - } else if (default_alloc <= default_num + 1) { - default_alloc = default_alloc * 3 / 2 + 20; - REALLOC_ARRAY(default_arg, default_alloc); - } - default_arg[default_num++] = xstrdup(value); - default_arg[default_num] = NULL; + if (!default_args.argc) + argv_array_push(&default_args, "show-branch"); + argv_array_push(&default_args, value); return 0; } @@ -696,9 +677,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); /* If nothing is specified, try the default first */ - if (ac == 1 && default_num) { - ac = default_num; - av = default_arg; + if (ac == 1 && default_args.argc) { + ac = default_args.argc; + av = default_args.argv; } ac = parse_options(ac, av, prefix, builtin_show_branch_options, @@ -743,6 +724,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) fake_av[1] = NULL; av = fake_av; ac = 1; + if (!*av) + die("no branches given, and HEAD is not valid"); } if (ac != 1) die("--reflog option needs one branch name"); @@ -927,7 +910,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) all_revs = all_mask & ~((1u << REV_SHIFT) - 1); while (seen) { - struct commit *commit = pop_one_commit(&seen); + struct commit *commit = pop_commit(&seen); int this_flag = commit->object.flags; int is_merge_point = ((this_flag & all_revs) == all_revs); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 131ef28e5c..264c392007 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -8,7 +8,7 @@ static const char * const show_ref_usage[] = { N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"), - N_("git show-ref --exclude-existing[=<pattern>] < <ref-list>"), + N_("git show-ref --exclude-existing[=<pattern>]"), NULL }; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 1259ed708b..7ff8434f7c 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -1,71 +1,7 @@ #include "builtin.h" #include "cache.h" - -/* - * Returns the length of a line, without trailing spaces. - * - * If the line ends with newline, it will be removed too. - */ -static size_t cleanup(char *line, size_t len) -{ - while (len) { - unsigned char c = line[len - 1]; - if (!isspace(c)) - break; - len--; - } - - return len; -} - -/* - * Remove empty lines from the beginning and end - * and also trailing spaces from every line. - * - * Turn multiple consecutive empty lines between paragraphs - * into just one empty line. - * - * If the input has only empty lines and spaces, - * no output will be produced. - * - * If last line does not have a newline at the end, one is added. - * - * Enable skip_comments to skip every line starting with comment - * character. - */ -void stripspace(struct strbuf *sb, int skip_comments) -{ - int empties = 0; - size_t i, j, len, newlen; - char *eol; - - /* We may have to add a newline. */ - strbuf_grow(sb, 1); - - for (i = j = 0; i < sb->len; i += len, j += newlen) { - eol = memchr(sb->buf + i, '\n', sb->len - i); - len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; - - if (skip_comments && len && sb->buf[i] == comment_line_char) { - newlen = 0; - continue; - } - newlen = cleanup(sb->buf + i, len); - - /* Not just an empty line? */ - if (newlen) { - if (empties > 0 && j > 0) - sb->buf[j++] = '\n'; - empties = 0; - memmove(sb->buf + j, sb->buf + i, newlen); - sb->buf[newlen + j++] = '\n'; - } else { - empties++; - } - } - - strbuf_setlen(sb, j); -} +#include "parse-options.h" +#include "strbuf.h" static void comment_lines(struct strbuf *buf) { @@ -77,41 +13,45 @@ static void comment_lines(struct strbuf *buf) free(msg); } -static const char *usage_msg = "\n" -" git stripspace [-s | --strip-comments] < input\n" -" git stripspace [-c | --comment-lines] < input"; +static const char * const stripspace_usage[] = { + N_("git stripspace [-s | --strip-comments]"), + N_("git stripspace [-c | --comment-lines]"), + NULL +}; + +enum stripspace_mode { + STRIP_DEFAULT = 0, + STRIP_COMMENTS, + COMMENT_LINES +}; int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; - int strip_comments = 0; - enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE; - - if (argc == 2) { - if (!strcmp(argv[1], "-s") || - !strcmp(argv[1], "--strip-comments")) { - strip_comments = 1; - } else if (!strcmp(argv[1], "-c") || - !strcmp(argv[1], "--comment-lines")) { - mode = COMMENT_LINES; - } else { - mode = INVAL; - } - } else if (argc > 1) { - mode = INVAL; - } - - if (mode == INVAL) - usage(usage_msg); - - if (strip_comments || mode == COMMENT_LINES) + enum stripspace_mode mode = STRIP_DEFAULT; + + const struct option options[] = { + OPT_CMDMODE('s', "strip-comments", &mode, + N_("skip and remove all lines starting with comment character"), + STRIP_COMMENTS), + OPT_CMDMODE('c', "comment-lines", &mode, + N_("prepend comment character and blank to each line"), + COMMENT_LINES), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, stripspace_usage, 0); + if (argc) + usage_with_options(stripspace_usage, options); + + if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) git_config(git_default_config, NULL); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); - if (mode == STRIP_SPACE) - stripspace(&buf, strip_comments); + if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS) + strbuf_stripspace(&buf, mode == STRIP_COMMENTS); else comment_lines(&buf); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c new file mode 100644 index 0000000000..f4c3eff179 --- /dev/null +++ b/builtin/submodule--helper.c @@ -0,0 +1,282 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "quote.h" +#include "pathspec.h" +#include "dir.h" +#include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" +#include "string-list.h" +#include "run-command.h" + +struct module_list { + const struct cache_entry **entries; + int alloc, nr; +}; +#define MODULE_LIST_INIT { NULL, 0, 0 } + +static int module_list_compute(int argc, const char **argv, + const char *prefix, + struct pathspec *pathspec, + struct module_list *list) +{ + int i, result = 0; + char *max_prefix, *ps_matched = NULL; + int max_prefix_len; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); + + /* Find common prefix for all pathspec's */ + max_prefix = common_prefix(pathspec); + max_prefix_len = max_prefix ? strlen(max_prefix) : 0; + + if (pathspec->nr) + ps_matched = xcalloc(pathspec->nr, 1); + + if (read_cache() < 0) + die(_("index file corrupt")); + + for (i = 0; i < active_nr; i++) { + const struct cache_entry *ce = active_cache[i]; + + if (!S_ISGITLINK(ce->ce_mode) || + !match_pathspec(pathspec, ce->name, ce_namelen(ce), + max_prefix_len, ps_matched, 1)) + continue; + + ALLOC_GROW(list->entries, list->nr + 1, list->alloc); + list->entries[list->nr++] = ce; + while (i + 1 < active_nr && + !strcmp(ce->name, active_cache[i + 1]->name)) + /* + * Skip entries with the same name in different stages + * to make sure an entry is returned only once. + */ + i++; + } + free(max_prefix); + + if (ps_matched && report_path_error(ps_matched, pathspec, prefix)) + result = -1; + + free(ps_matched); + + return result; +} + +static int module_list(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + + struct option module_list_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper list [--prefix=<path>] [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_list_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) { + printf("#unmatched\n"); + return 1; + } + + for (i = 0; i < list.nr; i++) { + const struct cache_entry *ce = list.entries[i]; + + if (ce_stage(ce)) + printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); + else + printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce)); + + utf8_fprintf(stdout, "%s\n", ce->name); + } + return 0; +} + +static int module_name(int argc, const char **argv, const char *prefix) +{ + const struct submodule *sub; + + if (argc != 2) + usage(_("git submodule--helper name <path>")); + + gitmodules_config(); + sub = submodule_from_path(null_sha1, argv[1]); + + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + argv[1]); + + printf("%s\n", sub->name); + + return 0; +} +static int clone_submodule(const char *path, const char *gitdir, const char *url, + const char *depth, const char *reference, int quiet) +{ + struct child_process cp; + child_process_init(&cp); + + argv_array_push(&cp.args, "clone"); + argv_array_push(&cp.args, "--no-checkout"); + if (quiet) + argv_array_push(&cp.args, "--quiet"); + if (depth && *depth) + argv_array_pushl(&cp.args, "--depth", depth, NULL); + if (reference && *reference) + argv_array_pushl(&cp.args, "--reference", reference, NULL); + if (gitdir && *gitdir) + argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + + argv_array_push(&cp.args, url); + argv_array_push(&cp.args, path); + + cp.git_cmd = 1; + cp.env = local_repo_env; + cp.no_stdin = 1; + + return run_command(&cp); +} + +static int module_clone(int argc, const char **argv, const char *prefix) +{ + const char *path = NULL, *name = NULL, *url = NULL; + const char *reference = NULL, *depth = NULL; + int quiet = 0; + FILE *submodule_dot_git; + char *sm_gitdir, *cwd, *p; + struct strbuf rel_path = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + + struct option module_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_STRING(0, "path", &path, + N_("path"), + N_("where the new submodule will be cloned to")), + OPT_STRING(0, "name", &name, + N_("string"), + N_("name of the new submodule")), + OPT_STRING(0, "url", &url, + N_("string"), + N_("url where to clone the submodule from")), + OPT_STRING(0, "reference", &reference, + N_("string"), + N_("reference repository")), + OPT_STRING(0, "depth", &depth, + N_("string"), + N_("depth for shallow clones")), + OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper clone [--prefix=<path>] [--quiet] " + "[--reference <repository>] [--name <name>] [--url <url>]" + "[--depth <depth>] [--] [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_clone_options, + git_submodule_helper_usage, 0); + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); + sm_gitdir = strbuf_detach(&sb, NULL); + + if (!file_exists(sm_gitdir)) { + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) + die(_("clone of '%s' into submodule path '%s' failed"), + url, path); + } else { + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + strbuf_addf(&sb, "%s/index", sm_gitdir); + unlink_or_warn(sb.buf); + strbuf_reset(&sb); + } + + /* Write a .git file in the submodule to redirect to the superproject. */ + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + + if (path && *path) + strbuf_addf(&sb, "%s/.git", path); + else + strbuf_addstr(&sb, ".git"); + + if (safe_create_leading_directories_const(sb.buf) < 0) + die(_("could not create leading directories of '%s'"), sb.buf); + submodule_dot_git = fopen(sb.buf, "w"); + if (!submodule_dot_git) + die_errno(_("cannot open file '%s'"), sb.buf); + + fprintf(submodule_dot_git, "gitdir: %s\n", + relative_path(sm_gitdir, path, &rel_path)); + if (fclose(submodule_dot_git)) + die(_("could not close file %s"), sb.buf); + strbuf_reset(&sb); + strbuf_reset(&rel_path); + + cwd = xgetcwd(); + /* Redirect the worktree of the submodule in the superproject's config */ + if (!is_absolute_path(sm_gitdir)) { + strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir); + free(sm_gitdir); + sm_gitdir = strbuf_detach(&sb, NULL); + } + + strbuf_addf(&sb, "%s/%s", cwd, path); + p = git_pathdup_submodule(path, "config"); + if (!p) + die(_("could not get submodule directory for '%s'"), path); + git_config_set_in_file(p, "core.worktree", + relative_path(sb.buf, sm_gitdir, &rel_path)); + strbuf_release(&sb); + strbuf_release(&rel_path); + free(sm_gitdir); + free(cwd); + free(p); + return 0; +} + +struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, const char *); +}; + +static struct cmd_struct commands[] = { + {"list", module_list}, + {"name", module_name}, + {"clone", module_clone}, +}; + +int cmd_submodule__helper(int argc, const char **argv, const char *prefix) +{ + int i; + if (argc < 2) + die(_("fatal: submodule--helper subcommand must be " + "called with a subcommand")); + + for (i = 0; i < ARRAY_SIZE(commands); i++) + if (!strcmp(argv[1], commands[i].cmd)) + return commands[i].fn(argc - 1, argv + 1, prefix); + + die(_("fatal: '%s' is not a valid submodule--helper " + "subcommand"), argv[1]); +} diff --git a/builtin/tag.c b/builtin/tag.c index aaae358c2e..8db8c87e57 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,280 +17,50 @@ #include "gpg-interface.h" #include "sha1-array.h" #include "column.h" +#include "ref-filter.h" static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" - "\n\t\t[<pattern>...]"), + "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), N_("git tag -v <tagname>..."), NULL }; -#define STRCMP_SORT 0 /* must be zero */ -#define VERCMP_SORT 1 -#define SORT_MASK 0x7fff -#define REVERSE_SORT 0x8000 - -static int tag_sort; - -struct tag_filter { - const char **patterns; - int lines; - int sort; - struct string_list tags; - struct commit_list *with_commit; -}; - -static struct sha1_array points_at; static unsigned int colopts; -static int match_pattern(const char **patterns, const char *ref) -{ - /* no pattern means match everything */ - if (!*patterns) - return 1; - for (; *patterns; patterns++) - if (!wildmatch(*patterns, ref, 0, NULL)) - return 1; - return 0; -} - -/* - * This is currently duplicated in ref-filter.c, and will eventually be - * removed as we port tag.c to use the ref-filter APIs. - */ -static const unsigned char *match_points_at(const char *refname, - const unsigned char *sha1) -{ - const unsigned char *tagged_sha1 = NULL; - struct object *obj; - - if (sha1_array_lookup(&points_at, sha1) >= 0) - return sha1; - obj = parse_object(sha1); - if (!obj) - die(_("malformed object at '%s'"), refname); - if (obj->type == OBJ_TAG) - tagged_sha1 = ((struct tag *)obj)->tagged->sha1; - if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0) - return tagged_sha1; - return NULL; -} - -static int in_commit_list(const struct commit_list *want, struct commit *c) -{ - for (; want; want = want->next) - if (!hashcmp(want->item->object.sha1, c->object.sha1)) - return 1; - return 0; -} - -/* - * The entire code segment for supporting the --contains option has been - * copied over to ref-filter.{c,h}. This will be deleted evetually when - * we port tag.c to use ref-filter APIs. - */ -enum contains_result { - CONTAINS_UNKNOWN = -1, - CONTAINS_NO = 0, - CONTAINS_YES = 1 -}; - -/* - * Test whether the candidate or one of its parents is contained in the list. - * Do not recurse to find out, though, but return -1 if inconclusive. - */ -static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want) -{ - /* was it previously marked as containing a want commit? */ - if (candidate->object.flags & TMP_MARK) - return 1; - /* or marked as not possibly containing a want commit? */ - if (candidate->object.flags & UNINTERESTING) - return 0; - /* or are we it? */ - if (in_commit_list(want, candidate)) { - candidate->object.flags |= TMP_MARK; - return 1; - } - - if (parse_commit(candidate) < 0) - return 0; - - return -1; -} - -/* - * Mimicking the real stack, this stack lives on the heap, avoiding stack - * overflows. - * - * At each recursion step, the stack items points to the commits whose - * ancestors are to be inspected. - */ -struct stack { - int nr, alloc; - struct stack_entry { - struct commit *commit; - struct commit_list *parents; - } *stack; -}; - -static void push_to_stack(struct commit *candidate, struct stack *stack) -{ - int index = stack->nr++; - ALLOC_GROW(stack->stack, stack->nr, stack->alloc); - stack->stack[index].commit = candidate; - stack->stack[index].parents = candidate->parents; -} - -static enum contains_result contains(struct commit *candidate, - const struct commit_list *want) -{ - struct stack stack = { 0, 0, NULL }; - int result = contains_test(candidate, want); - - if (result != CONTAINS_UNKNOWN) - return result; - - push_to_stack(candidate, &stack); - while (stack.nr) { - struct stack_entry *entry = &stack.stack[stack.nr - 1]; - struct commit *commit = entry->commit; - struct commit_list *parents = entry->parents; - - if (!parents) { - commit->object.flags |= UNINTERESTING; - stack.nr--; - } - /* - * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful 0 or 1. - */ - else switch (contains_test(parents->item, want)) { - case CONTAINS_YES: - commit->object.flags |= TMP_MARK; - stack.nr--; - break; - case CONTAINS_NO: - entry->parents = parents->next; - break; - case CONTAINS_UNKNOWN: - push_to_stack(parents->item, &stack); - break; - } - } - free(stack.stack); - return contains_test(candidate, want); -} - -static void show_tag_lines(const struct object_id *oid, int lines) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { + struct ref_array array; + char *to_free = NULL; int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - - buf = read_sha1_file(oid->hash, &type, &size); - if (!buf) - die_errno("unable to read object %s", oid_to_hex(oid)); - if (type != OBJ_COMMIT && type != OBJ_TAG) - goto free_return; - if (!size) - die("an empty %s object %s?", - typename(type), oid_to_hex(oid)); - - /* skip header */ - sp = strstr(buf, "\n\n"); - if (!sp) - goto free_return; - - /* only take up to "lines" lines, and strip the signature from a tag */ - if (type == OBJ_TAG) - size = parse_signature(buf, size); - for (i = 0, sp += 2; i < lines && sp < buf + size; i++) { - if (i) - printf("\n "); - eol = memchr(sp, '\n', size - (sp - buf)); - len = eol ? eol - sp : size - (sp - buf); - fwrite(sp, len, 1, stdout); - if (!eol) - break; - sp = eol + 1; - } -free_return: - free(buf); -} - -static int show_reference(const char *refname, const struct object_id *oid, - int flag, void *cb_data) -{ - struct tag_filter *filter = cb_data; - - if (match_pattern(filter->patterns, refname)) { - if (filter->with_commit) { - struct commit *commit; - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) - return 0; - if (!contains(commit, filter->with_commit)) - return 0; - } + memset(&array, 0, sizeof(array)); - if (points_at.nr && !match_points_at(refname, oid->hash)) - return 0; + if (filter->lines == -1) + filter->lines = 0; - if (!filter->lines) { - if (filter->sort) - string_list_append(&filter->tags, refname); - else - printf("%s\n", refname); - return 0; - } - printf("%-15s ", refname); - show_tag_lines(oid, filter->lines); - putchar('\n'); + if (!format) { + if (filter->lines) { + to_free = xstrfmt("%s %%(contents:lines=%d)", + "%(align:15)%(refname:short)%(end)", + filter->lines); + format = to_free; + } else + format = "%(refname:short)"; } - return 0; -} + verify_ref_format(format); + filter->with_commit_tag_algo = 1; + filter_refs(&array, filter, FILTER_REFS_TAGS); + ref_array_sort(sorting, &array); -static int sort_by_version(const void *a_, const void *b_) -{ - const struct string_list_item *a = a_; - const struct string_list_item *b = b_; - return versioncmp(a->string, b->string); -} + for (i = 0; i < array.nr; i++) + show_ref_array_item(array.items[i], format, 0); + ref_array_clear(&array); + free(to_free); -static int list_tags(const char **patterns, int lines, - struct commit_list *with_commit, int sort) -{ - struct tag_filter filter; - - filter.patterns = patterns; - filter.lines = lines; - filter.sort = sort; - filter.with_commit = with_commit; - memset(&filter.tags, 0, sizeof(filter.tags)); - filter.tags.strdup_strings = 1; - - for_each_tag_ref(show_reference, (void *)&filter); - if (sort) { - int i; - if ((sort & SORT_MASK) == VERCMP_SORT) - qsort(filter.tags.items, filter.tags.nr, - sizeof(struct string_list_item), sort_by_version); - if (sort & REVERSE_SORT) - for (i = filter.tags.nr - 1; i >= 0; i--) - printf("%s\n", filter.tags.items[i].string); - else - for (i = 0; i < filter.tags.nr; i++) - printf("%s\n", filter.tags.items[i].string); - string_list_clear(&filter.tags, 0); - } return 0; } @@ -357,35 +127,26 @@ static const char tag_template_nocleanup[] = "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); -/* - * Parse a sort string, and return 0 if parsed successfully. Will return - * non-zero when the sort string does not parse into a known type. If var is - * given, the error message becomes a warning and includes information about - * the configuration value. - */ -static int parse_sort_string(const char *var, const char *arg, int *sort) +/* Parse arg given and add it the ref_sorting array */ +static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail) { - int type = 0, flags = 0; + struct ref_sorting *s; + int len; - if (skip_prefix(arg, "-", &arg)) - flags |= REVERSE_SORT; + s = xcalloc(1, sizeof(*s)); + s->next = *sorting_tail; + *sorting_tail = s; - if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) - type = VERCMP_SORT; - else - type = STRCMP_SORT; - - if (strcmp(arg, "refname")) { - if (!var) - return error(_("unsupported sort specification '%s'"), arg); - else { - warning(_("unsupported sort specification '%s' in variable '%s'"), - var, arg); - return -1; - } + if (*arg == '-') { + s->reverse = 1; + arg++; } + if (skip_prefix(arg, "version:", &arg) || + skip_prefix(arg, "v:", &arg)) + s->version = 1; - *sort = (type | flags); + len = strlen(arg); + s->atom = parse_ref_filter_atom(arg, arg+len); return 0; } @@ -393,11 +154,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort) static int git_tag_config(const char *var, const char *value, void *cb) { int status; + struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_sort_string(var, value, &tag_sort); + parse_sorting_string(value, sorting_tail); return 0; } @@ -507,7 +269,7 @@ static void create_tag(const unsigned char *object, const char *tag, } if (opt->cleanup_mode != CLEANUP_NONE) - stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); if (!opt->message_given && !buf->len) die(_("no tag message?")); @@ -555,13 +317,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } -static int parse_opt_sort(const struct option *opt, const char *arg, int unset) -{ - int *sort = opt->value; - - return parse_sort_string(NULL, arg, sort); -} - int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; @@ -570,17 +325,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix) const char *object_ref, *tag; struct create_tag_options opt; char *cleanup_arg = NULL; - int annotate = 0, force = 0, lines = -1; int create_reflog = 0; + int annotate = 0, force = 0; int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; - struct commit_list *with_commit = NULL; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + const char *format = NULL; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), - { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), + { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), N_("print <n> lines of each tag message"), PARSE_OPT_OPTARG, NULL, 1 }, OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), @@ -602,22 +359,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), - OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")), - OPT_WITH(&with_commit, N_("print only tags that contain the commit")), - { - OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), - PARSE_OPT_NONEG, parse_opt_sort - }, + OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_MERGED(&filter, N_("print only tags that are merged")), + OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), { - OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"), + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), 0, parse_opt_object_name }, + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_END() }; - git_config(git_tag_config, NULL); + git_config(git_tag_config, sorting_tail); memset(&opt, 0, sizeof(opt)); + memset(&filter, 0, sizeof(filter)); + filter.lines = -1; argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); @@ -634,11 +394,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); - if (cmdmode == 'l' && lines != -1) { + if (cmdmode == 'l' && filter.lines != -1) { if (explicitly_enable_column(colopts)) die(_("--column and -n are incompatible")); colopts = 0; } + if (!sorting) + sorting = ref_default_sorting(); if (cmdmode == 'l') { int ret; if (column_active(colopts)) { @@ -647,19 +409,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (lines != -1 && tag_sort) - die(_("--sort and -n are incompatible")); - ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); + filter.name_patterns = argv; + ret = list_tags(&filter, sorting, format); if (column_active(colopts)) stop_column_filter(); return ret; } - if (lines != -1) + if (filter.lines != -1) die(_("-n option is only allowed with -l.")); - if (with_commit) + if (filter.with_commit) die(_("--contains option is only allowed with -l.")); - if (points_at.nr) + if (filter.points_at.nr) die(_("--points-at option is only allowed with -l.")); + 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') diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 19200291a2..6fc6bcdf7f 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1) if (!buf || type != OBJ_BLOB) die("unable to read blob object %s", sha1_to_hex(sha1)); - strcpy(path, ".merge_file_XXXXXX"); + xsnprintf(path, sizeof(path), ".merge_file_XXXXXX"); fd = xmkstemp(path); if (write_in_full(fd, buf, size) != size) die_errno("unable to write temp-file"); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 7cc086f5f2..7c3e79c48d 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -13,7 +13,7 @@ #include "fsck.h" static int dry_run, quiet, recover, has_errors, strict; -static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file"; +static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 32ab94cd06..dbfe14f3fe 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -49,15 +49,14 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) __attribute__((format (printf, 1, 2))) static void error_clnt(const char *fmt, ...) { - char buf[1024]; + struct strbuf buf = STRBUF_INIT; va_list params; - int len; va_start(params, fmt); - len = vsprintf(buf, fmt, params); + strbuf_vaddf(&buf, fmt, params); va_end(params); - send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); - die("sent error to the client: %s", buf); + send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf.buf); } static ssize_t process_input(int child_fd, int band) diff --git a/builtin/worktree.c b/builtin/worktree.c index 71bb770f7a..d281f6d887 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -8,10 +8,13 @@ #include "run-command.h" #include "sigchain.h" #include "refs.h" +#include "utf8.h" +#include "worktree.h" static const char * const worktree_usage[] = { - N_("git worktree add [<options>] <path> <branch>"), + N_("git worktree add [<options>] <path> [<branch>]"), N_("git worktree prune [<options>]"), + N_("git worktree list [<options>]"), NULL }; @@ -359,6 +362,89 @@ static int add(int ac, const char **av, const char *prefix) return add_worktree(path, branch, &opts); } +static void show_worktree_porcelain(struct worktree *wt) +{ + printf("worktree %s\n", wt->path); + if (wt->is_bare) + printf("bare\n"); + else { + printf("HEAD %s\n", sha1_to_hex(wt->head_sha1)); + if (wt->is_detached) + printf("detached\n"); + else + printf("branch %s\n", wt->head_ref); + } + printf("\n"); +} + +static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) +{ + struct strbuf sb = STRBUF_INIT; + int cur_path_len = strlen(wt->path); + int path_adj = cur_path_len - utf8_strwidth(wt->path); + + strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path); + if (wt->is_bare) + strbuf_addstr(&sb, "(bare)"); + else { + strbuf_addf(&sb, "%-*s ", abbrev_len, + find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV)); + if (!wt->is_detached) + strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0)); + else + strbuf_addstr(&sb, "(detached HEAD)"); + } + printf("%s\n", sb.buf); + + strbuf_release(&sb); +} + +static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) +{ + int i; + + for (i = 0; wt[i]; i++) { + int sha1_len; + int path_len = strlen(wt[i]->path); + + if (path_len > *maxlen) + *maxlen = path_len; + sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev)); + if (sha1_len > *abbrev) + *abbrev = sha1_len; + } +} + +static int list(int ac, const char **av, const char *prefix) +{ + int porcelain = 0; + + struct option options[] = { + OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + else { + struct worktree **worktrees = get_worktrees(); + int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; + + if (!porcelain) + measure_widths(worktrees, &abbrev, &path_maxlen); + + for (i = 0; worktrees[i]; i++) { + if (porcelain) + show_worktree_porcelain(worktrees[i]); + else + show_worktree(worktrees[i], path_maxlen, abbrev); + } + free_worktrees(worktrees); + } + return 0; +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -371,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return add(ac - 1, av + 1, prefix); if (!strcmp(av[1], "prune")) return prune(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "list")) + return list(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } |