diff options
Diffstat (limited to 'builtin')
54 files changed, 1960 insertions, 840 deletions
diff --git a/builtin/add.c b/builtin/add.c index 8266a9cb70..226f758869 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -166,14 +166,16 @@ static void update_callback(struct diff_queue_struct *q, } } -static void update_files_in_cache(const char *prefix, const char **pathspec, +static void update_files_in_cache(const char *prefix, + const struct pathspec *pathspec, struct update_callback_data *data) { struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - init_pathspec(&rev.prune_data, pathspec); + if (pathspec) + copy_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = data; @@ -181,7 +183,8 @@ static void update_files_in_cache(const char *prefix, const char **pathspec, run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); } -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +int add_files_to_cache(const char *prefix, + const struct pathspec *pathspec, int flags) { struct update_callback_data data; @@ -192,23 +195,21 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) } #define WARN_IMPLICIT_DOT (1u << 0) -static char *prune_directory(struct dir_struct *dir, const char **pathspec, +static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix, unsigned flag) { char *seen; - int i, specs; + int i; struct dir_entry **src, **dst; - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); + seen = xcalloc(pathspec->nr, 1); src = dst = dir->entries; i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - if (match_pathspec(pathspec, entry->name, entry->len, - prefix, seen)) + if (match_pathspec_depth(pathspec, entry->name, entry->len, + prefix, seen)) *dst++ = entry; else if (flag & WARN_IMPLICIT_DOT) /* @@ -222,72 +223,33 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, warn_pathless_add(); } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, seen, specs); + add_pathspec_matches_against_index(pathspec, seen); return seen; } -/* - * Checks the index to see whether any path in pathspec refers to - * something inside a submodule. If so, dies with an error message. - */ -static void treat_gitlinks(const char **pathspec) -{ - int i; - - if (!pathspec || !*pathspec) - return; - - for (i = 0; pathspec[i]; i++) - pathspec[i] = check_path_for_gitlink(pathspec[i]); -} - -static void refresh(int verbose, const char **pathspec) +static void refresh(int verbose, const struct pathspec *pathspec) { char *seen; - int i, specs; + int i; - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); + seen = xcalloc(pathspec->nr, 1); refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, pathspec, seen, _("Unstaged changes after refreshing the index:")); - for (i = 0; i < specs; i++) { + for (i = 0; i < pathspec->nr; i++) { if (!seen[i]) - die(_("pathspec '%s' did not match any files"), pathspec[i]); + die(_("pathspec '%s' did not match any files"), + pathspec->items[i].match); } free(seen); } -/* - * Normalizes argv relative to prefix, via get_pathspec(), and then - * runs die_if_path_beyond_symlink() on each path in the normalized - * list. - */ -static const char **validate_pathspec(const char **argv, const char *prefix) -{ - const char **pathspec = get_pathspec(prefix, argv); - - if (pathspec) { - const char **p; - for (p = pathspec; *p; p++) { - die_if_path_beyond_symlink(*p, prefix); - } - } - - return pathspec; -} - int run_add_interactive(const char *revision, const char *patch_mode, - const char **pathspec) + const struct pathspec *pathspec) { - int status, ac, pc = 0; + int status, ac, i; const char **args; - if (pathspec) - while (pathspec[pc]) - pc++; - - args = xcalloc(sizeof(const char *), (pc + 5)); + args = xcalloc(sizeof(const char *), (pathspec->nr + 6)); ac = 0; args[ac++] = "add--interactive"; if (patch_mode) @@ -295,11 +257,9 @@ int run_add_interactive(const char *revision, const char *patch_mode, if (revision) args[ac++] = revision; args[ac++] = "--"; - if (pc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); - ac += pc; - } - args[ac] = NULL; + for (i = 0; i < pathspec->nr; i++) + /* pass original pathspec, to be re-parsed */ + args[ac++] = pathspec->items[i].original; status = run_command_v_opt(args, RUN_GIT_CMD); free(args); @@ -308,17 +268,17 @@ int run_add_interactive(const char *revision, const char *patch_mode, int interactive_add(int argc, const char **argv, const char *prefix, int patch) { - const char **pathspec = NULL; + struct pathspec pathspec; - if (argc) { - pathspec = validate_pathspec(argv, prefix); - if (!pathspec) - return -1; - } + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_PREFIX_ORIGIN, + prefix, argv); return run_add_interactive(NULL, patch ? "--patch" : NULL, - pathspec); + &pathspec); } static int edit_patch(int argc, const char **argv, const char *prefix) @@ -336,7 +296,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) - die (_("Could not read the index")); + die(_("Could not read the index")); init_revisions(&rev, prefix); rev.diffopt.context = 7; @@ -347,11 +307,11 @@ static int edit_patch(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) - die (_("Could not open '%s' for writing."), file); + die(_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) - die (_("Could not write patch")); + die(_("Could not write patch")); launch_editor(file, NULL, NULL); @@ -364,7 +324,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) child.git_cmd = 1; child.argv = apply_argv; if (run_command(&child)) - die (_("Could not apply '%s'"), file); + die(_("Could not apply '%s'"), file); unlink(file); free(file); @@ -446,7 +406,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; int newfd; - const char **pathspec; + struct pathspec pathspec; struct dir_struct dir; int flags; int add_new_files; @@ -527,14 +487,23 @@ int cmd_add(int argc, const char **argv, const char *prefix) fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); return 0; } - pathspec = validate_pathspec(argv, prefix); if (read_cache() < 0) die(_("index file corrupt")); - treat_gitlinks(pathspec); + + /* + * Check the "pathspec '%s' did not match any files" block + * below before enabling new magic. + */ + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE, + prefix, argv); if (add_new_files) { int baselen; + struct pathspec empty_pathspec; /* Set up the default git porcelain excludes */ memset(&dir, 0, sizeof(dir)); @@ -543,35 +512,49 @@ int cmd_add(int argc, const char **argv, const char *prefix) setup_standard_excludes(&dir); } + memset(&empty_pathspec, 0, sizeof(empty_pathspec)); /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec); - if (pathspec) - seen = prune_directory(&dir, pathspec, baselen, + baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec); + if (pathspec.nr) + seen = prune_directory(&dir, &pathspec, baselen, implicit_dot ? WARN_IMPLICIT_DOT : 0); } if (refresh_only) { - refresh(verbose, pathspec); + refresh(verbose, &pathspec); goto finish; } if (implicit_dot && prefix) refresh_cache(REFRESH_QUIET); - if (pathspec) { + if (pathspec.nr) { int i; if (!seen) - seen = find_pathspecs_matching_against_index(pathspec); - for (i = 0; pathspec[i]; i++) { - if (!seen[i] && pathspec[i][0] - && !file_exists(pathspec[i])) { + seen = find_pathspecs_matching_against_index(&pathspec); + + /* + * file_exists() assumes exact match + */ + GUARD_PATHSPEC(&pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE); + + for (i = 0; i < pathspec.nr; i++) { + const char *path = pathspec.items[i].match; + if (!seen[i] && + ((pathspec.items[i].magic & + (PATHSPEC_GLOB | PATHSPEC_ICASE)) || + !file_exists(path))) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, pathspec[i], &dtype)) - dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); + if (is_excluded(&dir, path, &dtype)) + dir_add_ignored(&dir, path, pathspec.items[i].len); } else die(_("pathspec '%s' did not match any files"), - pathspec[i]); + pathspec.items[i].original); } } free(seen); @@ -587,10 +570,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) */ update_data.implicit_dot = prefix; update_data.implicit_dot_len = strlen(prefix); - pathspec = NULL; + free_pathspec(&pathspec); + memset(&pathspec, 0, sizeof(pathspec)); } update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT; - update_files_in_cache(prefix, pathspec, &update_data); + update_files_in_cache(prefix, &pathspec, &update_data); exit_status |= !!update_data.add_errors; if (add_new_files) @@ -598,7 +582,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) unplug_bulk_checkin(); - finish: +finish: if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) diff --git a/builtin/blame.c b/builtin/blame.c index f932112e72..4916eb2bd2 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -22,6 +22,7 @@ #include "utf8.h" #include "userdiff.h" #include "line-range.h" +#include "line-log.h" static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); @@ -408,7 +409,9 @@ static struct origin *find_origin(struct scoreboard *sb, paths[0] = origin->path; paths[1] = NULL; - diff_tree_setup_paths(paths, &diff_opts); + parse_pathspec(&diff_opts.pathspec, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, + PATHSPEC_LITERAL_PATH, "", paths); diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) @@ -458,7 +461,7 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); + free_pathspec(&diff_opts.pathspec); if (porigin) { /* * Create a freestanding copy that is not part of @@ -486,15 +489,12 @@ static struct origin *find_rename(struct scoreboard *sb, struct origin *porigin = NULL; struct diff_options diff_opts; int i; - const char *paths[2]; diff_setup(&diff_opts); DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = origin->path; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) @@ -516,7 +516,7 @@ static struct origin *find_rename(struct scoreboard *sb, } } diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); + free_pathspec(&diff_opts.pathspec); return porigin; } @@ -1064,7 +1064,6 @@ static int find_copy_in_parent(struct scoreboard *sb, int opt) { struct diff_options diff_opts; - const char *paths[1]; int i, j; int retval; struct blame_list *blame_list; @@ -1078,8 +1077,6 @@ static int find_copy_in_parent(struct scoreboard *sb, DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); diff_setup_done(&diff_opts); /* Try "find copies harder" on new path if requested; @@ -1162,7 +1159,7 @@ static int find_copy_in_parent(struct scoreboard *sb, } reset_scanned_flag(sb); diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); + free_pathspec(&diff_opts.pathspec); return retval; } @@ -1554,8 +1551,7 @@ static void assign_blame(struct scoreboard *sb, int opt) */ origin_incref(suspect); commit = suspect->commit; - if (!commit->object.parsed) - parse_commit(commit); + parse_commit(commit); if (reverse || (!(commit->object.flags & UNINTERESTING) && !(revs->max_age != -1 && commit->date < revs->max_age))) @@ -1937,18 +1933,6 @@ static const char *add_prefix(const char *prefix, const char *path) return prefix_path(prefix, prefix ? strlen(prefix) : 0, path); } -/* - * Parsing of -L option - */ -static void prepare_blame_range(struct scoreboard *sb, - const char *bottomtop, - long lno, - long *bottom, long *top) -{ - if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path)) - usage(blame_usage); -} - static int git_blame_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "blame.showroot")) { @@ -2245,29 +2229,18 @@ static int blame_move_callback(const struct option *option, const char *arg, int return 0; } -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset) -{ - const char **bottomtop = option->value; - if (!arg) - return -1; - if (*bottomtop) - die("More than one '-L n,m' option given"); - *bottomtop = arg; - return 0; -} - int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; const char *path; struct scoreboard sb; struct origin *o; - struct blame_entry *ent; - long dashdash_pos, bottom, top, lno; + struct blame_entry *ent = NULL; + long dashdash_pos, lno; const char *final_commit_name = NULL; enum object_type type; - static const char *bottomtop = NULL; + static struct string_list range_list; static int output_option = 0, opt = 0; static int show_stats = 0; static const char *revs_file = NULL; @@ -2293,13 +2266,16 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback }, - OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback), + OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")), OPT__ABBREV(&abbrev), OPT_END() }; struct parse_opt_ctx_t ctx; int cmd_is_annotate = !strcmp(argv[0], "annotate"); + struct range_set ranges; + unsigned int range_i; + long anchor; git_config(git_blame_config, NULL); init_revisions(&revs, NULL); @@ -2492,22 +2468,48 @@ parse_done: num_read_blob++; lno = prepare_lines(&sb); - bottom = top = 0; - if (bottomtop) - prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom < 1) - bottom = 1; - if (top < 1) - top = lno; - bottom--; - if (lno < top || lno < bottom) - die("file %s has only %lu lines", path, lno); - - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; + if (lno && !range_list.nr) + string_list_append(&range_list, xstrdup("1")); + + anchor = 1; + range_set_init(&ranges, range_list.nr); + for (range_i = 0; range_i < range_list.nr; ++range_i) { + long bottom, top; + if (parse_range_arg(range_list.items[range_i].string, + nth_line_cb, &sb, lno, anchor, + &bottom, &top, sb.path)) + usage(blame_usage); + if (lno < top || ((lno || bottom) && lno < bottom)) + die("file %s has only %lu lines", path, lno); + if (bottom < 1) + bottom = 1; + if (top < 1) + top = lno; + bottom--; + range_set_append_unsafe(&ranges, bottom, top); + anchor = top + 1; + } + sort_and_merge_range_set(&ranges); + + for (range_i = ranges.nr; range_i > 0; --range_i) { + const struct range *r = &ranges.ranges[range_i - 1]; + long bottom = r->start; + long top = r->end; + struct blame_entry *next = ent; + ent = xcalloc(1, sizeof(*ent)); + ent->lno = bottom; + ent->num_lines = top - bottom; + ent->suspect = o; + ent->s_lno = bottom; + ent->next = next; + if (next) + next->prev = ent; + origin_incref(o); + } + origin_decref(o); + + range_set_release(&ranges); + string_list_clear(&range_list, 0); sb.ent = ent; sb.path = path; diff --git a/builtin/branch.c b/builtin/branch.c index 4daed0b230..636a16ea4e 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -423,19 +423,20 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, char *ref = NULL; struct branch *branch = branch_get(branch_name); struct strbuf fancy = STRBUF_INIT; + int upstream_is_gone = 0; + int added_decoration = 1; - if (!stat_tracking_info(branch, &ours, &theirs)) { - if (branch && branch->merge && branch->merge[0]->dst && - show_upstream_ref) { - ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); - if (want_color(branch_use_color)) - strbuf_addf(stat, "[%s%s%s] ", - branch_get_color(BRANCH_COLOR_UPSTREAM), - ref, branch_get_color(BRANCH_COLOR_RESET)); - else - strbuf_addf(stat, "[%s] ", ref); - } + switch (stat_tracking_info(branch, &ours, &theirs)) { + case 0: + /* no base */ return; + case -1: + /* with "gone" base */ + upstream_is_gone = 1; + break; + default: + /* with base */ + break; } if (show_upstream_ref) { @@ -448,19 +449,29 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, strbuf_addstr(&fancy, ref); } - if (!ours) { - if (ref) + if (upstream_is_gone) { + if (show_upstream_ref) + strbuf_addf(stat, _("[%s: gone]"), fancy.buf); + else + added_decoration = 0; + } else if (!ours && !theirs) { + if (show_upstream_ref) + strbuf_addf(stat, _("[%s]"), fancy.buf); + else + added_decoration = 0; + } else if (!ours) { + if (show_upstream_ref) strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs); else strbuf_addf(stat, _("[behind %d]"), theirs); } else if (!theirs) { - if (ref) + if (show_upstream_ref) strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours); else strbuf_addf(stat, _("[ahead %d]"), ours); } else { - if (ref) + if (show_upstream_ref) strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), fancy.buf, ours, theirs); else @@ -468,7 +479,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, ours, theirs); } strbuf_release(&fancy); - strbuf_addch(stat, ' '); + if (added_decoration) + strbuf_addch(stat, ' '); free(ref); } @@ -490,7 +502,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, const char *sub = _(" **** invalid ref ****"); struct commit *commit = item->commit; - if (commit && !parse_commit(commit)) { + if (!parse_commit(commit)) { pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); sub = subject.buf; } @@ -872,7 +884,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (with_commit || merge_filter != NO_FILTER) list = 1; - if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1) + if (!!delete + !!rename + !!force_create + !!new_upstream + + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); if (abbrev == -1) @@ -968,9 +981,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("no such branch '%s'"), argv[0]); } - if (!branch_has_merge_config(branch)) { + if (!branch_has_merge_config(branch)) die(_("Branch '%s' has no upstream information"), branch->name); - } strbuf_addf(&buf, "branch.%s.remote", branch->name); git_config_set_multivar(buf.buf, NULL, NULL, 1); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 163ce6c77c..b2ca775a80 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -45,6 +45,14 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) case 'e': return !has_sha1_file(sha1); + case 'c': + if (!obj_context.path[0]) + die("git cat-file --textconv %s: <object> must be <sha1:path>", + obj_name); + + if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size)) + break; + case 'p': type = sha1_object_info(sha1, NULL); if (type < 0) @@ -67,16 +75,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) /* otherwise just spit out the data */ break; - case 'c': - if (!obj_context.path[0]) - die("git cat-file --textconv %s: <object> must be <sha1:path>", - obj_name); - - if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size)) - die("git cat-file --textconv: unable to run textconv on %s", - obj_name); - break; - case 0: if (type_from_string(exp_type) == OBJ_BLOB) { unsigned char blob_sha1[20]; @@ -128,6 +126,13 @@ struct expand_data { int mark_query; /* + * Whether to split the input on whitespace before feeding it to + * get_sha1; this is decided during the mark_query phase based on + * whether we have a %(rest) token in our format. + */ + int split_on_whitespace; + + /* * After a mark_query run, this object_info is set up to be * passed to sha1_object_info_extended. It will point to the data * elements above, so you can retrieve the response from there. @@ -165,7 +170,9 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, else strbuf_addf(sb, "%lu", data->disk_size); } else if (is_atom("rest", atom, len)) { - if (!data->mark_query && data->rest) + if (data->mark_query) + data->split_on_whitespace = 1; + else if (data->rest) strbuf_addstr(sb, data->rest); } else die("unknown format element: %.*s", len, atom); @@ -277,19 +284,21 @@ static int batch_objects(struct batch_options *opt) warn_on_object_refname_ambiguity = 0; while (strbuf_getline(&buf, stdin, '\n') != EOF) { - char *p; int error; - /* - * Split at first whitespace, tying off the beginning of the - * string and saving the remainder (or NULL) in data.rest. - */ - p = strpbrk(buf.buf, " \t"); - if (p) { - while (*p && strchr(" \t", *p)) - *p++ = '\0'; + if (data.split_on_whitespace) { + /* + * Split at first whitespace, tying off the beginning + * of the string and saving the remainder (or NULL) in + * data.rest. + */ + char *p = strpbrk(buf.buf, " \t"); + if (p) { + while (*p && strchr(" \t", *p)) + *p++ = '\0'; + } + data.rest = p; } - data.rest = p; error = batch_one_object(buf.buf, opt, &data); if (error) diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 6e5cd886af..e9af7b2bfb 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -13,14 +13,14 @@ N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"), NULL }; -static int null_term_line; +static int nul_term_line; static const struct option check_attr_options[] = { OPT_BOOL('a', "all", &all_attrs, N_("report all attributes set on file")), OPT_BOOL(0, "cached", &cached_attrs, N_("use .gitattributes only from the index")), OPT_BOOL(0 , "stdin", &stdin_paths, N_("read file names from stdin")), - OPT_BOOL('z', NULL, &null_term_line, - N_("input paths are terminated by a null character")), + OPT_BOOL('z', NULL, &nul_term_line, + N_("terminate input and output records by a NUL character")), OPT_END() }; @@ -38,8 +38,16 @@ static void output_attr(int cnt, struct git_attr_check *check, else if (ATTR_UNSET(value)) value = "unspecified"; - quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", git_attr_name(check[j].attr), value); + if (nul_term_line) { + printf("%s%c" /* path */ + "%s%c" /* attrname */ + "%s%c" /* attrvalue */, + file, 0, git_attr_name(check[j].attr), 0, value, 0); + } else { + quote_c_style(file, NULL, stdout, 0); + printf(": %s: %s\n", git_attr_name(check[j].attr), value); + } + } } @@ -65,7 +73,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt, struct git_attr_check *check) { struct strbuf buf, nbuf; - int line_termination = null_term_line ? 0 : '\n'; + int line_termination = nul_term_line ? 0 : '\n'; strbuf_init(&buf, 0); strbuf_init(&nbuf, 0); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index c9f0c9bd6a..594463a11b 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -5,14 +5,14 @@ #include "pathspec.h" #include "parse-options.h" -static int quiet, verbose, stdin_paths, show_non_matching; +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>", NULL }; -static int null_term_line; +static int nul_term_line; static const struct option check_ignore_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -20,10 +20,12 @@ static const struct option check_ignore_options[] = { OPT_GROUP(""), OPT_BOOL(0, "stdin", &stdin_paths, N_("read file names from stdin")), - OPT_BOOL('z', NULL, &null_term_line, - N_("input paths are terminated by a null character")), + OPT_BOOL('z', NULL, &nul_term_line, + N_("terminate input and output records by a NUL character")), OPT_BOOL('n', "non-matching", &show_non_matching, N_("show non-matching input paths")), + OPT_BOOL(0, "no-index", &no_index, + N_("ignore index when checking")), OPT_END() }; @@ -31,7 +33,7 @@ static void output_exclude(const char *path, struct exclude *exclude) { char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : ""; char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : ""; - if (!null_term_line) { + if (!nul_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); } else { @@ -64,37 +66,45 @@ static void output_exclude(const char *path, struct exclude *exclude) } static int check_ignore(struct dir_struct *dir, - const char *prefix, const char **pathspec) + const char *prefix, int argc, const char **argv) { - const char *path, *full_path; + const char *full_path; char *seen; int num_ignored = 0, dtype = DT_UNKNOWN, i; struct exclude *exclude; + struct pathspec pathspec; - if (!pathspec || !*pathspec) { + if (!argc) { if (!quiet) fprintf(stderr, "no pathspec given.\n"); return 0; } /* + * check-ignore just needs paths. Magic beyond :/ is really + * irrelevant. + */ + parse_pathspec(&pathspec, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE | + PATHSPEC_KEEP_ORDER, + prefix, argv); + + /* * look for pathspecs matching entries in the index, since these * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(pathspec); - for (i = 0; pathspec[i]; i++) { - path = pathspec[i]; - full_path = prefix_path(prefix, prefix - ? strlen(prefix) : 0, path); - full_path = check_path_for_gitlink(full_path); - die_if_path_beyond_symlink(full_path, prefix); + seen = find_pathspecs_matching_against_index(&pathspec); + for (i = 0; i < pathspec.nr; i++) { + full_path = pathspec.items[i].match; exclude = NULL; if (!seen[i]) { exclude = last_exclude_matching(dir, full_path, &dtype); } if (!quiet && (exclude || show_non_matching)) - output_exclude(path, exclude); + output_exclude(pathspec.items[i].original, exclude); if (exclude) num_ignored++; } @@ -107,7 +117,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) { struct strbuf buf, nbuf; char *pathspec[2] = { NULL, NULL }; - int line_termination = null_term_line ? 0 : '\n'; + int line_termination = nul_term_line ? 0 : '\n'; int num_ignored = 0; strbuf_init(&buf, 0); @@ -120,7 +130,8 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) strbuf_swap(&buf, &nbuf); } pathspec[0] = buf.buf; - num_ignored += check_ignore(dir, prefix, (const char **)pathspec); + num_ignored += check_ignore(dir, prefix, + 1, (const char **)pathspec); maybe_flush_or_die(stdout, "check-ignore to stdout"); } strbuf_release(&buf); @@ -142,7 +153,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (argc > 0) die(_("cannot specify pathnames with --stdin")); } else { - if (null_term_line) + if (nul_term_line) die(_("-z only makes sense with --stdin")); if (argc == 0) die(_("no path specified")); @@ -157,7 +168,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) die(_("--non-matching is only valid with --verbose")); /* read_cache() is only necessary so we can watch out for submodules. */ - if (read_cache() < 0) + if (!no_index && read_cache() < 0) die(_("index file corrupt")); memset(&dir, 0, sizeof(dir)); @@ -166,7 +177,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (stdin_paths) { num_ignored = check_ignore_stdin_paths(&dir, prefix); } else { - num_ignored = check_ignore(&dir, prefix, argv); + num_ignored = check_ignore(&dir, prefix, argc, argv); maybe_flush_or_die(stdout, "ignore to stdout"); } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index aa922edf94..61e75eb60c 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -14,7 +14,7 @@ static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][PATH_MAX + 1]; +static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; static struct checkout state; @@ -188,7 +188,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) OPT__FORCE(&force, N_("force overwrite of existing files")), OPT__QUIET(&quiet, N_("no warning for existing files and files not in index")), - OPT_BOOLEAN('n', "no-create", ¬_new, + OPT_BOOL('n', "no-create", ¬_new, N_("don't checkout new files")), { OPTION_CALLBACK, 'u', "index", &newfd, NULL, N_("update stat information in the index file"), diff --git a/builtin/checkout.c b/builtin/checkout.c index 8b48f4adbc..904fd715f0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -46,7 +46,7 @@ struct checkout_opts { int branch_exists; const char *prefix; - const char **pathspec; + struct pathspec pathspec; struct tree *source_tree; }; @@ -83,12 +83,9 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, return 0; } -static int read_tree_some(struct tree *tree, const char **pathspec) +static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - struct pathspec ps; - init_pathspec(&ps, pathspec); - read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); - free_pathspec(&ps); + read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -228,8 +225,6 @@ static int checkout_paths(const struct checkout_opts *opts, int flag; struct commit *head; int errs = 0; - int stage = opts->writeout_stage; - int merge = opts->merge; int newfd; struct lock_file *lock_file; @@ -257,20 +252,18 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->patch_mode) return run_add_interactive(revision, "--patch=checkout", - opts->pathspec); + &opts->pathspec); lock_file = xcalloc(1, sizeof(struct lock_file)); newfd = hold_locked_index(lock_file, 1); - if (read_cache_preload(opts->pathspec) < 0) + if (read_cache_preload(&opts->pathspec) < 0) return error(_("corrupt index file")); if (opts->source_tree) - read_tree_some(opts->source_tree, opts->pathspec); + read_tree_some(opts->source_tree, &opts->pathspec); - for (pos = 0; opts->pathspec[pos]; pos++) - ; - ps_matched = xcalloc(1, pos); + ps_matched = xcalloc(1, opts->pathspec.nr); /* * Make sure all pathspecs participated in locating the paths @@ -304,12 +297,12 @@ static int checkout_paths(const struct checkout_opts *opts, * match_pathspec() for _all_ entries when * opts->source_tree != NULL. */ - if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), + if (match_pathspec_depth(&opts->pathspec, ce->name, ce_namelen(ce), 0, ps_matched)) ce->ce_flags |= CE_MATCHED; } - if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) { + if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { free(ps_matched); return 1; } @@ -327,8 +320,8 @@ static int checkout_paths(const struct checkout_opts *opts, continue; if (opts->force) { warning(_("path '%s' is unmerged"), ce->name); - } else if (stage) { - errs |= check_stage(stage, ce, pos); + } else if (opts->writeout_stage) { + errs |= check_stage(opts->writeout_stage, ce, pos); } else if (opts->merge) { errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { @@ -352,9 +345,9 @@ static int checkout_paths(const struct checkout_opts *opts, errs |= checkout_entry(ce, &state, NULL); continue; } - if (stage) - errs |= checkout_stage(stage, ce, pos, &state); - else if (merge) + if (opts->writeout_stage) + errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); + else if (opts->merge) errs |= checkout_merged(pos, &state); pos = skip_same_name(ce, pos) - 1; } @@ -387,8 +380,8 @@ static void show_local_changes(struct object *head, static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; - parse_commit(commit); - pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); + if (!parse_commit(commit)) + pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@ -684,12 +677,12 @@ static int add_pending_uninteresting_ref(const char *refname, static void describe_one_orphan(struct strbuf *sb, struct commit *commit) { - parse_commit(commit); strbuf_addstr(sb, " "); strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); strbuf_addch(sb, ' '); - pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); + if (!parse_commit(commit)) + pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); strbuf_addch(sb, '\n'); } @@ -796,7 +789,7 @@ static int switch_branches(const struct checkout_opts *opts, new->commit = old.commit; if (!new->commit) die(_("You are on a branch yet to be born")); - parse_commit(new->commit); + parse_commit_or_die(new->commit); } ret = merge_working_tree(opts, &old, new, &writeout_error); @@ -880,7 +873,9 @@ static int parse_branchname_arg(int argc, const char **argv, int argcount = 0; unsigned char branch_rev[20]; const char *arg; - int has_dash_dash; + int dash_dash_pos; + int has_dash_dash = 0; + int i; /* * case 1: git checkout <ref> -- [<paths>] @@ -892,20 +887,30 @@ static int parse_branchname_arg(int argc, const char **argv, * * everything after the '--' must be paths. * - * case 3: git checkout <something> [<paths>] + * case 3: git checkout <something> [--] * - * With no paths, if <something> is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if <something> is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. + * (a) If <something> is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if <something> is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. * - * With no paths, if <something> is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * <something> in one and only one remote, then this is a short-hand - * to fork local <something> from that remote-tracking branch. + * (b) If <something> is _not_ a commit, either "--" is present + * or <something> is not a path, no -t nor -b was given, and + * and there is a tracking branch whose name is <something> + * in one and only one remote, then this is a short-hand to + * fork local <something> from that remote-tracking branch. * - * Otherwise <something> shall not be ambiguous. + * (c) Otherwise, if "--" is present, treat it like case (1). + * + * (d) Otherwise : + * - if it's a reference, treat it like case (1) + * - else if it's a path, treat it like case (2) + * - else: fail. + * + * case 4: git checkout <something> <paths> + * + * The first argument must not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). * - else: fail. @@ -914,28 +919,59 @@ static int parse_branchname_arg(int argc, const char **argv, if (!argc) return 0; - if (!strcmp(argv[0], "--")) /* case (2) */ - return 1; - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + dash_dash_pos = -1; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + dash_dash_pos = i; + break; + } + } + if (dash_dash_pos == 0) + return 1; /* case (2) */ + else if (dash_dash_pos == 1) + has_dash_dash = 1; /* case (3) or (1) */ + else if (dash_dash_pos >= 2) + die(_("only one reference expected, %d given."), dash_dash_pos); if (!strcmp(arg, "-")) arg = "@{-1}"; if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die(_("invalid reference: %s"), arg); - if (dwim_new_local_branch_ok && - !check_filename(NULL, arg) && - argc == 1) { + /* + * Either case (3) or (4), with <something> not being + * a commit, or an attempt to use case (1) with an + * invalid ref. + * + * It's likely an error, but we need to find out if + * we should auto-create the branch, case (3).(b). + */ + int recover_with_dwim = dwim_new_local_branch_ok; + + if (check_filename(NULL, arg) && !has_dash_dash) + recover_with_dwim = 0; + /* + * Accept "git checkout foo" and "git checkout foo --" + * as candidates for dwim. + */ + if (!(argc == 1 && !has_dash_dash) && + !(argc == 2 && has_dash_dash)) + recover_with_dwim = 0; + + if (recover_with_dwim) { const char *remote = unique_tracking_name(arg, rev); - if (!remote) - return argcount; - *new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } else { + if (remote) { + *new_branch = arg; + arg = remote; + /* DWIMmed to create local branch, case (3).(b) */ + } else { + recover_with_dwim = 0; + } + } + + if (!recover_with_dwim) { + if (has_dash_dash) + die(_("invalid reference: %s"), arg); return argcount; } } @@ -959,13 +995,13 @@ static int parse_branchname_arg(int argc, const char **argv, /* not a commit */ *source_tree = parse_tree_indirect(rev); } else { - parse_commit(new->commit); + parse_commit_or_die(new->commit); *source_tree = new->commit->tree; } if (!*source_tree) /* case (1): want a tree */ die(_("reference is not a tree: %s"), arg); - if (!has_dash_dash) {/* case (3 -> 1) */ + if (!has_dash_dash) {/* case (3).(d) -> (1) */ /* * Do not complain the most common case * git checkout branch @@ -1002,7 +1038,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) static int checkout_branch(struct checkout_opts *opts, struct branch_info *new) { - if (opts->pathspec) + if (opts->pathspec.nr) die(_("paths cannot be used with switching branches")); if (opts->patch_mode) @@ -1153,9 +1189,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } if (argc) { - opts.pathspec = get_pathspec(prefix, argv); + parse_pathspec(&opts.pathspec, 0, + opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, + prefix, argv); - if (!opts.pathspec) + if (!opts.pathspec.nr) die(_("invalid path specification")); /* @@ -1187,7 +1225,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } - if (opts.patch_mode || opts.pathspec) + if (opts.patch_mode || opts.pathspec.nr) return checkout_paths(&opts, new.name); else return checkout_branch(&opts, &new); diff --git a/builtin/clean.c b/builtin/clean.c index df0f61e74e..615cd57caf 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -15,6 +15,7 @@ #include "quote.h" #include "column.h" #include "color.h" +#include "pathspec.h" static int force = -1; /* unset */ static int interactive; @@ -365,6 +366,56 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) string_list_clear(&menu_list, 0); } +static int find_unique(const char *choice, struct menu_stuff *menu_stuff) +{ + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int i, len, found = 0; + + len = strlen(choice); + switch (menu_stuff->type) { + default: + die("Bad type of menu_stuff when parse choice"); + case MENU_STUFF_TYPE_MENU_ITEM: + + menu_item = (struct menu_item *)menu_stuff->stuff; + for (i = 0; i < menu_stuff->nr; i++, menu_item++) { + if (len == 1 && *choice == menu_item->hotkey) { + found = i + 1; + break; + } + if (!strncasecmp(choice, menu_item->title, len)) { + if (found) { + if (len == 1) { + /* continue for hotkey matching */ + found = -1; + } else { + found = 0; + break; + } + } else { + found = i + 1; + } + } + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; + for (i = 0; i < menu_stuff->nr; i++, string_list_item++) { + if (!strncasecmp(choice, string_list_item->string, len)) { + if (found) { + found = 0; + break; + } + found = i + 1; + } + } + break; + } + return found; +} + + /* * Parse user input, and return choice(s) for menu (menu_stuff). * @@ -392,8 +443,6 @@ static int parse_choice(struct menu_stuff *menu_stuff, int **chosen) { struct strbuf **choice_list, **ptr; - struct menu_item *menu_item; - struct string_list_item *string_list_item; int nr = 0; int i; @@ -457,32 +506,8 @@ static int parse_choice(struct menu_stuff *menu_stuff, bottom = 1; top = menu_stuff->nr; } else { - switch (menu_stuff->type) { - default: - die("Bad type of menu_stuff when parse choice"); - case MENU_STUFF_TYPE_MENU_ITEM: - menu_item = (struct menu_item *)menu_stuff->stuff; - for (i = 0; i < menu_stuff->nr; i++, menu_item++) { - if (((*ptr)->len == 1 && - *(*ptr)->buf == menu_item->hotkey) || - !strcasecmp((*ptr)->buf, menu_item->title)) { - bottom = i + 1; - top = bottom; - break; - } - } - break; - case MENU_STUFF_TYPE_STRING_LIST: - string_list_item = ((struct string_list *)menu_stuff->stuff)->items; - for (i = 0; i < menu_stuff->nr; i++, string_list_item++) { - if (!strcasecmp((*ptr)->buf, string_list_item->string)) { - bottom = i + 1; - top = bottom; - break; - } - } - break; - } + bottom = find_unique((*ptr)->buf, menu_stuff); + top = bottom; } if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || @@ -839,13 +864,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf abs_path = STRBUF_INIT; struct dir_struct dir; - static const char **pathspec; + struct pathspec pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct exclude_list *el; struct string_list_item *item; const char *qname; - char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), OPT__DRY_RUN(&dry_run, N_("dry run")), @@ -901,12 +925,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix) for (i = 0; i < exclude_list.nr; i++) add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1)); - pathspec = get_pathspec(prefix, argv); + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, argv); - fill_directory(&dir, pathspec); - - if (pathspec) - seen = xmalloc(argc > 0 ? argc : 1); + fill_directory(&dir, &pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; @@ -937,11 +960,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (lstat(ent->name, &st)) die_errno("Cannot lstat '%s'", ent->name); - if (pathspec) { - memset(seen, 0, argc > 0 ? argc : 1); - matches = match_pathspec(pathspec, ent->name, len, - 0, seen); - } + if (pathspec.nr) + matches = match_pathspec_depth(&pathspec, ent->name, + len, 0, NULL); if (S_ISDIR(st.st_mode)) { if (remove_directories || (matches == MATCHED_EXACTLY)) { @@ -949,7 +970,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) string_list_append(&del_list, rel); } } else { - if (pathspec && !matches) + if (pathspec.nr && !matches) continue; rel = relative_path(ent->name, prefix, &buf); string_list_append(&del_list, rel); @@ -995,7 +1016,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } strbuf_reset(&abs_path); } - free(seen); strbuf_release(&abs_path); strbuf_release(&buf); diff --git a/builtin/clone.c b/builtin/clone.c index ca3eb68d72..874e0fd0b6 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -379,7 +379,7 @@ static void clone_local(const char *src_repo, const char *dest_repo) } if (0 <= option_verbosity) - printf(_("done.\n")); + fprintf(stderr, _("done.\n")); } static const char *junk_work_tree; @@ -550,13 +550,13 @@ static void update_remote_refs(const struct ref *refs, const struct ref *rm = mapped_refs; if (check_connectivity) { - if (0 <= option_verbosity) - printf(_("Checking connectivity... ")); + if (transport->progress) + fprintf(stderr, _("Checking connectivity... ")); if (check_everything_connected_with_transport(iterate_ref_map, 0, &rm, transport)) die(_("remote did not send all necessary objects")); - if (0 <= option_verbosity) - printf(_("done\n")); + if (transport->progress) + fprintf(stderr, _("done.\n")); } if (refs) { @@ -849,9 +849,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (0 <= option_verbosity) { if (option_bare) - printf(_("Cloning into bare repository '%s'...\n"), dir); + fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir); else - printf(_("Cloning into '%s'...\n"), dir); + fprintf(stderr, _("Cloning into '%s'...\n"), dir); } init_db(option_template, INIT_DB_QUIET); write_config(&option_config); @@ -884,27 +884,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); - if (!is_local) { - if (!transport->get_refs_list || !transport->fetch) - die(_("Don't know how to clone %s"), transport->url); + if (!transport->get_refs_list || (!is_local && !transport->fetch)) + die(_("Don't know how to clone %s"), transport->url); - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); - if (option_depth) - transport_set_option(transport, TRANS_OPT_DEPTH, - option_depth); - if (option_single_branch) - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); + if (option_depth) + transport_set_option(transport, TRANS_OPT_DEPTH, + option_depth); + if (option_single_branch) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - transport_set_verbosity(transport, option_verbosity, option_progress); + transport_set_verbosity(transport, option_verbosity, option_progress); - if (option_upload_pack) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, - option_upload_pack); + if (option_upload_pack) + transport_set_option(transport, TRANS_OPT_UPLOADPACK, + option_upload_pack); - if (transport->smart_options && !option_depth) - transport->smart_options->check_self_contained_and_connected = 1; - } + if (transport->smart_options && !option_depth) + transport->smart_options->check_self_contained_and_connected = 1; refs = transport_get_remote_refs(transport); @@ -945,6 +943,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { + if (option_branch) + die(_("Remote branch %s not found in upstream %s"), + option_branch, option_origin); + warning(_("You appear to have cloned an empty repository.")); mapped_refs = NULL; our_head_points_at = NULL; diff --git a/builtin/commit.c b/builtin/commit.c index c20426b766..e89c519192 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -30,6 +30,7 @@ #include "column.h" #include "sequencer.h" #include "notes-utils.h" +#include "mailmap.h" static const char * const builtin_commit_usage[] = { N_("git commit [options] [--] <pathspec>..."), @@ -63,8 +64,18 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ "If you wish to commit it anyway, use:\n" "\n" " git commit --allow-empty\n" +"\n"); + +static const char empty_cherry_pick_advice_single[] = +N_("Otherwise, please use 'git reset'\n"); + +static const char empty_cherry_pick_advice_multi[] = +N_("If you wish to skip this commit, use:\n" +"\n" +" git reset\n" "\n" -"Otherwise, please use 'git reset'\n"); +"Then \"git cherry-pick --continue\" will resume cherry-picking\n" +"the remaining commits.\n"); static const char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -107,6 +118,7 @@ static enum { static const char *cleanup_arg; static enum commit_whence whence; +static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; @@ -141,14 +153,26 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path("MERGE_HEAD"))) whence = FROM_MERGE; - else if (file_exists(git_path("CHERRY_PICK_HEAD"))) + else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; + if (file_exists(git_path("sequencer"))) + sequencer_in_use = 1; + } else whence = FROM_COMMIT; if (s) s->whence = whence; } +static void status_init_config(struct wt_status *s, config_fn_t fn) +{ + wt_status_prepare(s); + gitmodules_config(); + git_config(fn, s); + determine_whence(s); + s->hints = advice_status_hints; /* must come after git_config() */ +} + static void rollback_index_files(void) { switch (commit_style) { @@ -188,17 +212,15 @@ static int commit_index_files(void) * and return the paths that match the given pattern in list. */ static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const char **pattern) + const char *prefix, const struct pathspec *pattern) { int i; char *m; - if (!pattern) + if (!pattern->nr) return 0; - for (i = 0; pattern[i]; i++) - ; - m = xcalloc(1, i); + m = xcalloc(1, pattern->nr); if (with_tree) { char *max_prefix = common_prefix(pattern); @@ -212,7 +234,7 @@ static int list_paths(struct string_list *list, const char *with_tree, if (ce->ce_flags & CE_UPDATE) continue; - if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) + if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m)) continue; item = string_list_insert(list, ce->name); if (ce_skip_worktree(ce)) @@ -284,17 +306,17 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, { int fd; struct string_list partial; - const char **pathspec = NULL; + struct pathspec pathspec; char *old_index_env = NULL; int refresh_flags = REFRESH_QUIET; if (is_status) refresh_flags |= REFRESH_UNMERGED; + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, argv); - if (*argv) - pathspec = get_pathspec(prefix, argv); - - if (read_cache_preload(pathspec) < 0) + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); if (interactive) { @@ -336,9 +358,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * (A) if all goes well, commit the real index; * (B) on failure, rollback the real index. */ - if (all || (also && pathspec && *pathspec)) { + if (all || (also && pathspec.nr)) { fd = hold_locked_index(&index_lock, 1); - add_files_to_cache(also ? prefix : NULL, pathspec, 0); + add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); if (write_cache(fd, active_cache, active_nr) || @@ -357,7 +379,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * and create commit from the_index. * We still need to refresh the index here. */ - if (!only && (!pathspec || !*pathspec)) { + if (!only && !pathspec.nr) { fd = hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { @@ -402,7 +424,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, memset(&partial, 0, sizeof(partial)); partial.strdup_strings = 1; - if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); discard_cache(); @@ -585,6 +607,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, const char *hook_arg2 = NULL; int ident_shown = 0; int clean_message_contents = (cleanup_mode != CLEANUP_NONE); + int old_display_comment_prefix; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); @@ -682,6 +705,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (s->fp == NULL) die_errno(_("could not open '%s'"), git_path(commit_editmsg)); + /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ + old_display_comment_prefix = s->display_comment_prefix; + s->display_comment_prefix = 1; + + /* + * Most hints are counter-productive when the commit has + * already started. + */ + s->hints = 0; + if (clean_message_contents) stripspace(&sb, 0); @@ -807,11 +840,17 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!commitable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) + else if (whence == FROM_CHERRY_PICK) { fputs(_(empty_cherry_pick_advice), stderr); + if (!sequencer_in_use) + fputs(_(empty_cherry_pick_advice_single), stderr); + else + fputs(_(empty_cherry_pick_advice_multi), stderr); + } return 0; } @@ -916,6 +955,7 @@ static const char *find_author_by_nickname(const char *name) struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; + struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@ -926,13 +966,17 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); + revs.mailmap = &mailmap; + read_mailmap(revs.mailmap, NULL); + prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, &ctx); + format_commit_message(commit, "%aN <%aE>", &buf, &ctx); + clear_mailmap(&mailmap); return strbuf_detach(&buf, NULL); } die(_("No existing author found with '%s'"), name); @@ -1072,7 +1116,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (patch_interactive) interactive = 1; - if (!!also + !!only + !!all + !!interactive > 1) + if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend))) die(_("No paths with --include/--only does not make sense.")); @@ -1163,6 +1207,10 @@ static int git_status_config(const char *k, const char *v, void *cb) s->use_color = git_config_colorbool(k, v); return 0; } + if (!strcmp(k, "status.displaycommentprefix")) { + s->display_comment_prefix = git_config_bool(k, v); + return 0; + } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { int slot = parse_status_slot(k, 13); if (slot < 0) @@ -1227,10 +1275,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_status_usage, builtin_status_options); - wt_status_prepare(&s); - gitmodules_config(); - git_config(git_status_config, &s); - determine_whence(&s); + status_init_config(&s, git_status_config); argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); @@ -1240,11 +1285,12 @@ int cmd_status(int argc, const char **argv, const char *prefix) handle_untracked_files_arg(&s); if (show_ignored_in_status) s.show_ignored_files = 1; - if (*argv) - s.pathspec = get_pathspec(prefix, argv); + parse_pathspec(&s.pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, argv); - read_cache_preload(s.pathspec); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + read_cache_preload(&s.pathspec); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); fd = hold_locked_index(&index_lock, 0); if (0 <= fd) @@ -1292,7 +1338,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, commit = lookup_commit(sha1); if (!commit) die(_("couldn't look up newly created commit")); - if (!commit || parse_commit(commit)) + if (parse_commit(commit)) die(_("could not parse newly created commit")); strbuf_addstr(&format, "format:%h] %s"); @@ -1471,18 +1517,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); - wt_status_prepare(&s); - gitmodules_config(); - git_config(git_commit_config, &s); + status_init_config(&s, git_commit_config); status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ - determine_whence(&s); s.colopts = 0; if (get_sha1("HEAD", sha1)) current_head = NULL; else { current_head = lookup_commit_or_die(sha1, "HEAD"); - if (!current_head || parse_commit(current_head)) + if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } argc = parse_and_validate_options(argc, argv, builtin_commit_options, @@ -1597,7 +1640,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) !current_head ? NULL : current_head->object.sha1, - 0); + 0, NULL); nl = strchr(sb.buf, '\n'); if (nl) diff --git a/builtin/config.c b/builtin/config.c index da12fdba96..20e89fe4e0 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -2,6 +2,7 @@ #include "cache.h" #include "color.h" #include "parse-options.h" +#include "urlmatch.h" static const char *const builtin_config_usage[] = { N_("git config [options]"), @@ -42,6 +43,7 @@ static int respect_includes = -1; #define ACTION_SET_ALL (1<<12) #define ACTION_GET_COLOR (1<<13) #define ACTION_GET_COLORBOOL (1<<14) +#define ACTION_GET_URLMATCH (1<<15) #define TYPE_BOOL (1<<0) #define TYPE_INT (1<<1) @@ -50,15 +52,16 @@ static int respect_includes = -1; static struct option builtin_config_options[] = { OPT_GROUP(N_("Config file location")), - OPT_BOOLEAN(0, "global", &use_global_config, N_("use global config file")), - OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")), - OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")), + OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), + OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), + OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")), OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP), + OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL), OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), @@ -102,25 +105,13 @@ struct strbuf_list { int alloc; }; -static int collect_config(const char *key_, const char *value_, void *cb) +static int format_config(struct strbuf *buf, const char *key_, const char *value_) { - struct strbuf_list *values = cb; - struct strbuf *buf; - char value[256]; - const char *vptr = value; int must_free_vptr = 0; int must_print_delim = 0; + char value[256]; + const char *vptr = value; - if (!use_key_regexp && strcmp(key_, key)) - return 0; - if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) - return 0; - if (regexp != NULL && - (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) - return 0; - - ALLOC_GROW(values->items, values->nr + 1, values->alloc); - buf = &values->items[values->nr++]; strbuf_init(buf, 0); if (show_keys) { @@ -128,7 +119,8 @@ static int collect_config(const char *key_, const char *value_, void *cb) must_print_delim = 1; } if (types == TYPE_INT) - sprintf(value, "%d", git_config_int(key_, value_?value_:"")); + sprintf(value, "%"PRId64, + git_config_int64(key_, value_ ? value_ : "")); else if (types == TYPE_BOOL) vptr = git_config_bool(key_, value_) ? "true" : "false"; else if (types == TYPE_BOOL_OR_INT) { @@ -156,15 +148,27 @@ static int collect_config(const char *key_, const char *value_, void *cb) strbuf_addch(buf, term); if (must_free_vptr) - /* If vptr must be freed, it's a pointer to a - * dynamically allocated buffer, it's safe to cast to - * const. - */ free((char *)vptr); - return 0; } +static int collect_config(const char *key_, const char *value_, void *cb) +{ + struct strbuf_list *values = cb; + + if (!use_key_regexp && strcmp(key_, key)) + return 0; + if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) + return 0; + if (regexp != NULL && + (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) + return 0; + + ALLOC_GROW(values->items, values->nr + 1, values->alloc); + + return format_config(&values->items[values->nr++], key_, value_); +} + static int get_value(const char *key_, const char *regex_) { int ret = CONFIG_GENERIC_ERROR; @@ -265,8 +269,8 @@ static char *normalize_value(const char *key, const char *value) else { normalized = xmalloc(64); if (types == TYPE_INT) { - int v = git_config_int(key, value); - sprintf(normalized, "%d", v); + int64_t v = git_config_int64(key, value); + sprintf(normalized, "%"PRId64, v); } else if (types == TYPE_BOOL) sprintf(normalized, "%s", @@ -364,6 +368,97 @@ static void check_blob_write(void) die("writing config blobs is not supported"); } +struct urlmatch_current_candidate_value { + char value_is_null; + struct strbuf value; +}; + +static int urlmatch_collect_fn(const char *var, const char *value, void *cb) +{ + struct string_list *values = cb; + struct string_list_item *item = string_list_insert(values, var); + struct urlmatch_current_candidate_value *matched = item->util; + + if (!matched) { + matched = xmalloc(sizeof(*matched)); + strbuf_init(&matched->value, 0); + item->util = matched; + } else { + strbuf_reset(&matched->value); + } + + if (value) { + strbuf_addstr(&matched->value, value); + matched->value_is_null = 0; + } else { + matched->value_is_null = 1; + } + return 0; +} + +static char *dup_downcase(const char *string) +{ + char *result; + size_t len, i; + + len = strlen(string); + result = xmalloc(len + 1); + for (i = 0; i < len; i++) + result[i] = tolower(string[i]); + result[i] = '\0'; + return result; +} + +static int get_urlmatch(const char *var, const char *url) +{ + char *section_tail; + struct string_list_item *item; + struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct string_list values = STRING_LIST_INIT_DUP; + + config.collect_fn = urlmatch_collect_fn; + config.cascade_fn = NULL; + config.cb = &values; + + if (!url_normalize(url, &config.url)) + die("%s", config.url.err); + + config.section = dup_downcase(var); + section_tail = strchr(config.section, '.'); + if (section_tail) { + *section_tail = '\0'; + config.key = section_tail + 1; + show_keys = 0; + } else { + config.key = NULL; + show_keys = 1; + } + + git_config_with_options(urlmatch_config_entry, &config, + given_config_file, NULL, respect_includes); + + for_each_string_list_item(item, &values) { + struct urlmatch_current_candidate_value *matched = item->util; + struct strbuf key = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&key, item->string); + format_config(&buf, key.buf, + matched->value_is_null ? NULL : matched->value.buf); + fwrite(buf.buf, 1, buf.len, stdout); + strbuf_release(&key); + strbuf_release(&buf); + + strbuf_release(&matched->value); + } + string_list_clear(&config.vars, 1); + string_list_clear(&values, 1); + free(config.url.url); + + free((void *)config.section); + return 0; +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; @@ -523,6 +618,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 1, 2); return get_value(argv[0], argv[1]); } + else if (actions == ACTION_GET_URLMATCH) { + check_argc(argc, 2, 2); + return get_urlmatch(argv[0], argv[1]); + } else if (actions == ACTION_UNSET) { check_blob_write(); check_argc(argc, 1, 2); diff --git a/builtin/describe.c b/builtin/describe.c index c94e5c30d0..6f62109887 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -9,11 +9,11 @@ #include "hash.h" #include "argv-array.h" -#define SEEN (1u<<0) +#define SEEN (1u << 0) #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { - N_("git describe [options] <committish>*"), + N_("git describe [options] <commit-ish>*"), N_("git describe [options] --dirty"), NULL }; @@ -36,7 +36,6 @@ static const char *diff_index_args[] = { "diff-index", "--quiet", "HEAD", "--", NULL }; - struct commit_name { struct commit_name *next; unsigned char peeled[20]; @@ -46,6 +45,7 @@ struct commit_name { unsigned char sha1[20]; char *path; }; + static const char *prio_names[] = { "head", "lightweight", "annotated", }; @@ -486,11 +486,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) } describe("HEAD", 1); } else if (dirty) { - die(_("--dirty is incompatible with committishes")); + die(_("--dirty is incompatible with commit-ishes")); } else { - while (argc-- > 0) { + while (argc-- > 0) describe(*argv++, argc == 0); - } } return 0; } diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 46085f862f..9200069363 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) rev.combine_merges = rev.dense_combined_merges = 1; - if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { + if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 1c737f7921..ce15b23042 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -43,7 +43,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) usage(diff_cache_usage); if (!cached) { setup_work_tree(); - if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { + if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff.c b/builtin/diff.c index 9fc273d8cd..adb93a9efa 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -140,7 +140,7 @@ static int builtin_diff_index(struct rev_info *revs, usage(builtin_diff_usage); if (!cached) { setup_work_tree(); - if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { + if (read_cache_preload(&revs->diffopt.pathspec) < 0) { perror("read_cache_preload"); return -1; } @@ -169,7 +169,7 @@ static int builtin_diff_tree(struct rev_info *revs, if (ent1->item->flags & UNINTERESTING) swap = 1; sha1[swap] = ent0->item->sha1; - sha1[1-swap] = ent1->item->sha1; + sha1[1 - swap] = ent1->item->sha1; diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -242,7 +242,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->combine_merges = revs->dense_combined_merges = 1; setup_work_tree(); - if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { + if (read_cache_preload(&revs->diffopt.pathspec) < 0) { perror("read_cache_preload"); return -1; } @@ -367,6 +367,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } } if (rev.prune_data.nr) { + /* builtin_diff_b_f() */ + GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL); if (!path) path = rev.prune_data.items[0].match; paths += rev.prune_data.nr; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 32b0c3fd24..ea6305258d 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -30,6 +30,7 @@ static int fake_missing_tagger; static int use_done_feature; static int no_data; static int full_tree; +static struct string_list extra_refs = STRING_LIST_INIT_NODUP; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -286,7 +287,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; - parse_commit(commit); + parse_commit_or_die(commit); author = strstr(commit->buffer, "\nauthor "); if (!author) die ("Could not find author in commit %s", @@ -307,7 +308,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) if (commit->parents && get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { - parse_commit(commit->parents->item); + parse_commit_or_die(commit->parents->item); diff_tree_sha1(commit->parents->item->tree->object.sha1, commit->tree->object.sha1, "", &rev->diffopt); } @@ -379,7 +380,7 @@ static void handle_tag(const char *name, struct tag *tag) int tagged_mark; struct commit *p; - /* Trees have no identifer in fast-export output, thus we have no way + /* Trees have no identifier in fast-export output, thus we have no way * to output tags of trees, tags of tags of trees, etc. Simply omit * such tags. */ @@ -484,10 +485,32 @@ static void handle_tag(const char *name, struct tag *tag) (int)message_size, (int)message_size, message ? message : ""); } -static void get_tags_and_duplicates(struct rev_cmdline_info *info, - struct string_list *extra_refs) +static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) +{ + switch (e->item->type) { + case OBJ_COMMIT: + return (struct commit *)e->item; + case OBJ_TAG: { + struct tag *tag = (struct tag *)e->item; + + /* handle nested tags */ + while (tag && tag->object.type == OBJ_TAG) { + parse_object(tag->object.sha1); + string_list_append(&extra_refs, full_name)->util = tag; + tag = (struct tag *)tag->tagged; + } + if (!tag) + die("Tag %s points nowhere?", e->name); + return (struct commit *)tag; + break; + } + default: + return NULL; + } +} + +static void get_tags_and_duplicates(struct rev_cmdline_info *info) { - struct tag *tag; int i; for (i = 0; i < info->nr; i++) { @@ -502,60 +525,45 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info, if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) continue; - switch (e->item->type) { - case OBJ_COMMIT: - commit = (struct commit *)e->item; - break; - case OBJ_TAG: - tag = (struct tag *)e->item; - - /* handle nested tags */ - while (tag && tag->object.type == OBJ_TAG) { - parse_object(tag->object.sha1); - string_list_append(extra_refs, full_name)->util = tag; - tag = (struct tag *)tag->tagged; - } - if (!tag) - die ("Tag %s points nowhere?", e->name); - switch(tag->object.type) { - case OBJ_COMMIT: - commit = (struct commit *)tag; - break; - case OBJ_BLOB: - export_blob(tag->object.sha1); - continue; - default: /* OBJ_TAG (nested tags) is already handled */ - warning("Tag points to object of unexpected type %s, skipping.", - typename(tag->object.type)); - continue; - } - break; - default: + commit = get_commit(e, full_name); + if (!commit) { warning("%s: Unexpected object of type %s, skipping.", e->name, typename(e->item->type)); continue; } + switch(commit->object.type) { + case OBJ_COMMIT: + break; + case OBJ_BLOB: + export_blob(commit->object.sha1); + continue; + default: /* OBJ_TAG (nested tags) is already handled */ + warning("Tag points to object of unexpected type %s, skipping.", + typename(commit->object.type)); + continue; + } + /* * This ref will not be updated through a commit, lets make * sure it gets properly updated eventually. */ if (commit->util || commit->object.flags & SHOWN) - string_list_append(extra_refs, full_name)->util = commit; + string_list_append(&extra_refs, full_name)->util = commit; if (!commit->util) commit->util = full_name; } } -static void handle_tags_and_duplicates(struct string_list *extra_refs) +static void handle_tags_and_duplicates(void) { struct commit *commit; int i; - for (i = extra_refs->nr - 1; i >= 0; i--) { - const char *name = extra_refs->items[i].string; - struct object *object = extra_refs->items[i].util; + for (i = extra_refs.nr - 1; i >= 0; i--) { + const char *name = extra_refs.items[i].string; + struct object *object = extra_refs.items[i].util; switch (object->type) { case OBJ_TAG: handle_tag(name, (struct tag *)object); @@ -657,7 +665,6 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct object_array commits = OBJECT_ARRAY_INIT; - struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; uint32_t lastimportid; @@ -709,7 +716,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename && revs.prune_data.nr) full_tree = 1; - get_tags_and_duplicates(&revs.cmdline, &extra_refs); + get_tags_and_duplicates(&revs.cmdline); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); @@ -725,7 +732,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) } } - handle_tags_and_duplicates(&extra_refs); + handle_tags_and_duplicates(); if (export_filename && lastimportid != last_idnum) export_marks(export_filename); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index aba4465552..c8e858232a 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,6 +1,8 @@ #include "builtin.h" #include "pkt-line.h" #include "fetch-pack.h" +#include "remote.h" +#include "connect.h" static const char fetch_pack_usage[] = "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " @@ -100,6 +102,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) pack_lockfile_ptr = &pack_lockfile; continue; } + if (!strcmp("--check-self-contained-and-connected", arg)) { + args.check_self_contained_and_connected = 1; + continue; + } usage(fetch_pack_usage); } @@ -152,6 +158,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) printf("lock %s\n", pack_lockfile); fflush(stdout); } + if (args.check_self_contained_and_connected && + args.self_contained_and_connected) { + printf("connectivity-ok\n"); + fflush(stdout); + } close(fd[0]); close(fd[1]); if (finish_connect(conn)) diff --git a/builtin/fetch.c b/builtin/fetch.c index 99afed03d4..bd7a10164f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -30,13 +30,18 @@ enum { TAGS_SET = 2 }; -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; +static int fetch_prune_config = -1; /* unspecified */ +static int prune = -1; /* unspecified */ +#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ + +static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT, unshallow; static const char *depth; static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; -static struct transport *transport; +static struct transport *gtransport; +static struct transport *gsecondary; static const char *submodule_prefix = ""; static const char *recurse_submodules_default; @@ -54,6 +59,15 @@ static int option_parse_recurse_submodules(const struct option *opt, return 0; } +static int git_fetch_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "fetch.prune")) { + fetch_prune_config = git_config_bool(k, v); + return 0; + } + return 0; +} + static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "all", &all, @@ -95,8 +109,10 @@ static struct option builtin_fetch_options[] = { static void unlock_pack(void) { - if (transport) - transport_unlock_pack(transport); + if (gtransport) + transport_unlock_pack(gtransport); + if (gsecondary) + transport_unlock_pack(gsecondary); } static void unlock_pack_on_signal(int signo) @@ -246,7 +262,8 @@ static int s_update_ref(const char *action, rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, 0); + check_old ? ref->old_sha1 : NULL, + 0, NULL); if (!lock) return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : STORE_REF_ERROR_OTHER; @@ -720,6 +737,48 @@ static int truncate_fetch_head(void) return 0; } +static void set_option(struct transport *transport, const char *name, const char *value) +{ + int r = transport_set_option(transport, name, value); + if (r < 0) + die(_("Option \"%s\" value \"%s\" is not valid for %s"), + name, value, transport->url); + if (r > 0) + warning(_("Option \"%s\" is ignored for %s\n"), + name, transport->url); +} + +static struct transport *prepare_transport(struct remote *remote) +{ + struct transport *transport; + transport = transport_get(remote, NULL); + transport_set_verbosity(transport, verbosity, progress); + if (upload_pack) + set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack); + if (keep) + set_option(transport, TRANS_OPT_KEEP, "yes"); + if (depth) + set_option(transport, TRANS_OPT_DEPTH, depth); + return transport; +} + +static void backfill_tags(struct transport *transport, struct ref *ref_map) +{ + if (transport->cannot_reuse) { + gsecondary = prepare_transport(transport->remote); + transport = gsecondary; + } + + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + fetch_refs(transport, ref_map); + + if (gsecondary) { + transport_disconnect(gsecondary); + gsecondary = NULL; + } +} + static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { @@ -771,7 +830,10 @@ static int do_fetch(struct transport *transport, goto cleanup; } if (prune) { - /* If --tags was specified, pretend the user gave us the canonical tags refspec */ + /* + * If --tags was specified, pretend that the user gave us + * the canonical tags refspec + */ if (tags == TAGS_SET) { const char *tags_str = "refs/tags/*:refs/tags/*"; struct refspec *tags_refspec, *refspec; @@ -803,11 +865,8 @@ static int do_fetch(struct transport *transport, struct ref **tail = &ref_map; ref_map = NULL; find_non_local_tags(transport, &ref_map, &tail); - if (ref_map) { - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); - transport_set_option(transport, TRANS_OPT_DEPTH, "0"); - fetch_refs(transport, ref_map); - } + if (ref_map) + backfill_tags(transport, ref_map); free_refs(ref_map); } @@ -816,17 +875,6 @@ static int do_fetch(struct transport *transport, return retcode; } -static void set_option(const char *name, const char *value) -{ - int r = transport_set_option(transport, name, value); - if (r < 0) - die(_("Option \"%s\" value \"%s\" is not valid for %s"), - name, value, transport->url); - if (r > 0) - warning(_("Option \"%s\" is ignored for %s\n"), - name, transport->url); -} - static int get_one_remote_for_fetch(struct remote *remote, void *priv) { struct string_list *list = priv; @@ -882,7 +930,7 @@ static void add_options_to_argv(struct argv_array *argv) { if (dry_run) argv_array_push(argv, "--dry-run"); - if (prune) + if (prune > 0) argv_array_push(argv, "--prune"); if (update_head_ok) argv_array_push(argv, "--update-head-ok"); @@ -949,14 +997,17 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) die(_("No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched.")); - transport = transport_get(remote, NULL); - transport_set_verbosity(transport, verbosity, progress); - if (upload_pack) - set_option(TRANS_OPT_UPLOADPACK, upload_pack); - if (keep) - set_option(TRANS_OPT_KEEP, "yes"); - if (depth) - set_option(TRANS_OPT_DEPTH, depth); + gtransport = prepare_transport(remote); + + if (prune < 0) { + /* no command line request */ + if (0 <= gtransport->remote->prune) + prune = gtransport->remote->prune; + else if (0 <= fetch_prune_config) + prune = fetch_prune_config; + else + prune = PRUNE_BY_DEFAULT; + } if (argc > 0) { int j = 0; @@ -983,10 +1034,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); refspec = parse_fetch_refspec(ref_nr, refs); - exit_code = do_fetch(transport, refspec, ref_nr); + exit_code = do_fetch(gtransport, refspec, ref_nr); free_refspec(ref_nr, refspec); - transport_disconnect(transport); - transport = NULL; + transport_disconnect(gtransport); + gtransport = NULL; return exit_code; } @@ -1007,6 +1058,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); + git_config(git_fetch_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 7f059c31df..d096051b15 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -205,6 +205,22 @@ static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned lo return buf; } +static int grab_objectname(const char *name, const unsigned char *sha1, + struct atom_value *v) +{ + if (!strcmp(name, "objectname")) { + char *s = xmalloc(41); + strcpy(s, sha1_to_hex(sha1)); + v->s = s; + return 1; + } + if (!strcmp(name, "objectname:short")) { + v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); + return 1; + } + return 0; +} + /* See grab_values */ static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -225,15 +241,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object v->ul = sz; v->s = s; } - else if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(obj->sha1)); - v->s = s; - } - else if (!strcmp(name, "objectname:short")) { - v->s = xstrdup(find_unique_abbrev(obj->sha1, - DEFAULT_ABBREV)); - } + else if (deref) + grab_objectname(name, obj->sha1, v); } } @@ -676,6 +685,8 @@ static void populate_value(struct refinfo *ref) } continue; } + else if (!deref && grab_objectname(name, ref->objectname, v)) + continue; else continue; @@ -867,24 +878,29 @@ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs static void print_value(struct refinfo *ref, int atom, int quote_style) { struct atom_value *v; + struct strbuf sb = STRBUF_INIT; get_value(ref, atom, &v); switch (quote_style) { case QUOTE_NONE: fputs(v->s, stdout); break; case QUOTE_SHELL: - sq_quote_print(stdout, v->s); + sq_quote_buf(&sb, v->s); break; case QUOTE_PERL: - perl_quote_print(stdout, v->s); + perl_quote_buf(&sb, v->s); break; case QUOTE_PYTHON: - python_quote_print(stdout, v->s); + python_quote_buf(&sb, v->s); break; case QUOTE_TCL: - tcl_quote_print(stdout, v->s); + tcl_quote_buf(&sb, v->s); break; } + if (quote_style != QUOTE_NONE) { + fputs(sb.buf, stdout); + strbuf_release(&sb); + } } static int hex1(char ch) diff --git a/builtin/fsck.c b/builtin/fsck.c index 39fa5e86d4..97ce678c6b 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -16,6 +16,7 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 +#define HAS_OBJ 0x0004 static int show_root; static int show_tags; @@ -101,7 +102,7 @@ static int mark_object(struct object *obj, int type, void *data) if (obj->flags & REACHABLE) return 0; obj->flags |= REACHABLE; - if (!obj->parsed) { + if (!(obj->flags & HAS_OBJ)) { if (parent && !has_sha1_file(obj->sha1)) { printf("broken link from %7s %s\n", typename(parent->type), sha1_to_hex(parent->sha1)); @@ -127,16 +128,13 @@ static int traverse_one_object(struct object *obj) struct tree *tree = NULL; if (obj->type == OBJ_TREE) { - obj->parsed = 0; tree = (struct tree *)obj; if (parse_tree(tree) < 0) return 1; /* error already displayed */ } result = fsck_walk(obj, mark_object, obj); - if (tree) { - free(tree->buffer); - tree->buffer = NULL; - } + if (tree) + free_tree_buffer(tree); return result; } @@ -178,7 +176,7 @@ static void check_reachable_object(struct object *obj) * except if it was in a pack-file and we didn't * do a full fsck */ - if (!obj->parsed) { + if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->sha1)) return; /* it is in pack - forget about it */ printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); @@ -306,8 +304,7 @@ static int fsck_obj(struct object *obj) if (obj->type == OBJ_TREE) { struct tree *item = (struct tree *) obj; - free(item->buffer); - item->buffer = NULL; + free_tree_buffer(item); } if (obj->type == OBJ_COMMIT) { @@ -340,6 +337,7 @@ static int fsck_sha1(const unsigned char *sha1) return error("%s: object corrupt or missing", sha1_to_hex(sha1)); } + obj->flags |= HAS_OBJ; return fsck_obj(obj); } @@ -352,6 +350,7 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, errors_found |= ERROR_OBJECT; return error("%s: object corrupt or missing", sha1_to_hex(sha1)); } + obj->flags = HAS_OBJ; return fsck_obj(obj); } diff --git a/builtin/gc.c b/builtin/gc.c index c4f7390bcf..c14190f840 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -14,6 +14,7 @@ #include "cache.h" #include "parse-options.h" #include "run-command.h" +#include "sigchain.h" #include "argv-array.h" #define FAILED_RUN "failed to run %s" @@ -35,6 +36,21 @@ static struct argv_array repack = ARGV_ARRAY_INIT; static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; +static char *pidfile; + +static void remove_pidfile(void) +{ + if (pidfile) + unlink(pidfile); +} + +static void remove_pidfile_on_signal(int signo) +{ + remove_pidfile(); + sigchain_pop(signo); + raise(signo); +} + static int gc_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "gc.packrefs")) { @@ -167,11 +183,77 @@ static int need_to_gc(void) return 1; } +/* return NULL on success, else hostname running the gc */ +static const char *lock_repo_for_gc(int force, pid_t* ret_pid) +{ + static struct lock_file lock; + static char locking_host[128]; + char my_host[128]; + struct strbuf sb = STRBUF_INIT; + struct stat st; + uintmax_t pid; + FILE *fp; + int fd, should_exit; + + if (pidfile) + /* already locked */ + return NULL; + + if (gethostname(my_host, sizeof(my_host))) + strcpy(my_host, "unknown"); + + fd = hold_lock_file_for_update(&lock, git_path("gc.pid"), + LOCK_DIE_ON_ERROR); + if (!force) { + fp = fopen(git_path("gc.pid"), "r"); + memset(locking_host, 0, sizeof(locking_host)); + should_exit = + fp != NULL && + !fstat(fileno(fp), &st) && + /* + * 12 hour limit is very generous as gc should + * never take that long. On the other hand we + * don't really need a strict limit here, + * running gc --auto one day late is not a big + * problem. --force can be used in manual gc + * after the user verifies that no gc is + * running. + */ + time(NULL) - st.st_mtime <= 12 * 3600 && + fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 && + /* be gentle to concurrent "gc" on remote hosts */ + (strcmp(locking_host, my_host) || !kill(pid, 0)); + if (fp != NULL) + fclose(fp); + if (should_exit) { + if (fd >= 0) + rollback_lock_file(&lock); + *ret_pid = pid; + return locking_host; + } + } + + strbuf_addf(&sb, "%"PRIuMAX" %s", + (uintmax_t) getpid(), my_host); + write_in_full(fd, sb.buf, sb.len); + strbuf_release(&sb); + commit_lock_file(&lock); + + pidfile = git_pathdup("gc.pid"); + sigchain_push_common(remove_pidfile_on_signal); + atexit(remove_pidfile); + + return NULL; +} + int cmd_gc(int argc, const char **argv, const char *prefix) { int aggressive = 0; int auto_gc = 0; int quiet = 0; + int force = 0; + const char *name; + pid_t pid; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -180,6 +262,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")), + OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")), OPT_END() }; @@ -225,6 +308,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix) } else add_repack_all_option(); + name = lock_repo_for_gc(force, &pid); + if (name) { + if (auto_gc) + return 0; /* be quiet on --auto */ + die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"), + name, (uintmax_t)pid); + } + if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) return error(FAILED_RUN, pack_refs_cmd.argv[0]); diff --git a/builtin/grep.c b/builtin/grep.c index 7877e7755c..63f86032d9 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,6 +17,7 @@ #include "grep.h" #include "quote.h" #include "dir.h" +#include "pathspec.h" static char const * const grep_usage[] = { N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"), @@ -457,10 +458,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, } static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, - struct object *obj, const char *name) + struct object *obj, const char *name, struct object_context *oc) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0, NULL); + return grep_sha1(opt, obj->sha1, name, 0, oc ? oc->path : NULL); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -502,7 +503,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); - if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].context)) { hit = 1; if (opt->status_only) break; @@ -521,7 +522,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, if (exc_std) setup_standard_excludes(&dir); - fill_directory(&dir, pathspec->raw); + fill_directory(&dir, pathspec); for (i = 0; i < dir.nr; i++) { const char *name = dir.entries[i]->name; int namelen = strlen(name); @@ -629,7 +630,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) const char *show_in_pager = NULL, *default_pager = "dummy"; struct grep_opt opt; struct object_array list = OBJECT_ARRAY_INIT; - const char **paths = NULL; struct pathspec pathspec; struct string_list path_list = STRING_LIST_INIT_NODUP; int i; @@ -658,6 +658,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_SET_INT('I', NULL, &opt.binary, N_("don't match patterns in binary files"), GREP_BINARY_NOMATCH), + OPT_BOOL(0, "textconv", &opt.allow_textconv, + N_("process binary files with textconv filters")), { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"), N_("descend at most <depth> levels"), PARSE_OPT_NONEG, NULL, 1 }, @@ -817,12 +819,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { const char *arg = argv[i]; unsigned char sha1[20]; + struct object_context oc; /* Is it a rev? */ - if (!get_sha1(arg, sha1)) { + if (!get_sha1_with_context(arg, 0, sha1, &oc)) { struct object *object = parse_object_or_die(sha1, arg); if (!seen_dashdash) verify_non_filename(prefix, arg); - add_object_array(object, arg, &list); + add_object_array_with_context(object, arg, &list, xmemdupz(&oc, sizeof(struct object_context))); continue; } if (!strcmp(arg, "--")) { @@ -856,8 +859,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j], j == i); } - paths = get_pathspec(prefix, argv + i); - init_pathspec(&pathspec, paths); + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD | + (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), + prefix, argv + i); pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 4aea5bbd80..d7fcf4c13c 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -71,7 +71,7 @@ static const char *vpath; static const struct option hash_object_options[] = { OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), OPT_BOOL('w', NULL, &write_object, N_("write the object into the object database")), - OPT_BOOLEAN( 0 , "stdin", &hashstdin, N_("read the object from stdin")), + OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")), OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")), OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")), OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")), diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 9c1cfac442..9e9eb4b74e 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -770,6 +770,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (obj->type == OBJ_TREE) { struct tree *item = (struct tree *) obj; item->buffer = NULL; + obj->parsed = 0; } if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; diff --git a/builtin/log.c b/builtin/log.c index 1dafbd03e0..b708517a35 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -111,6 +111,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) if (default_date_mode) rev->date_mode = parse_date_format(default_date_mode); + rev->diffopt.touched_flags = 0; } static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, @@ -121,7 +122,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; const struct option builtin_log_options[] = { - OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")), + OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), @@ -436,10 +437,29 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) strbuf_release(&out); } -static int show_blob_object(const unsigned char *sha1, struct rev_info *rev) +static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name) { + unsigned char sha1c[20]; + struct object_context obj_context; + char *buf; + unsigned long size; + fflush(stdout); - return stream_blob_to_fd(1, sha1, NULL, 0); + if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) || + !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) + return stream_blob_to_fd(1, sha1, NULL, 0); + + if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context)) + die("Not a valid object name %s", obj_name); + if (!obj_context.path[0] || + !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size)) + return stream_blob_to_fd(1, sha1, NULL, 0); + + if (!buf) + die("git show %s: bad file", obj_name); + + write_or_die(1, buf, size); + return 0; } static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) @@ -503,7 +523,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) init_grep_defaults(); git_config(git_log_config, NULL); - init_pathspec(&match_all, NULL); + memset(&match_all, 0, sizeof(match_all)); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; @@ -525,7 +545,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) const char *name = objects[i].name; switch (o->type) { case OBJ_BLOB: - ret = show_blob_object(o->sha1, NULL); + ret = show_blob_object(o->sha1, &rev, name); break; case OBJ_TAG: { struct tag *t = (struct tag *)o; @@ -1210,8 +1230,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), - OPT_BOOLEAN(0, "quiet", &quiet, - N_("don't print the patch filenames")), + OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_END() }; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 963ccc9742..e1cf6d8547 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -13,6 +13,7 @@ #include "parse-options.h" #include "resolve-undo.h" #include "string-list.h" +#include "pathspec.h" static int abbrev; static int show_deleted; @@ -30,7 +31,7 @@ static int debug_mode; static const char *prefix; static int max_prefix_len; static int prefix_len; -static const char **pathspec; +static struct pathspec pathspec; static int error_unmatch; static char *ps_matched; static const char *with_tree; @@ -63,7 +64,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) if (len >= ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); - if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched)) + if (!match_pathspec_depth(&pathspec, ent->name, ent->len, len, ps_matched)) return; fputs(tag, stdout); @@ -138,7 +139,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched)) + if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), len, ps_matched)) return; if (tag && *tag && show_valid_bit && @@ -194,7 +195,7 @@ static void show_ru_info(void) len = strlen(path); if (len < max_prefix_len) continue; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) + if (!match_pathspec_depth(&pathspec, path, len, max_prefix_len, ps_matched)) continue; /* uninterested */ for (i = 0; i < 3; i++) { if (!ui->mode[i]) @@ -219,7 +220,9 @@ static void show_files(struct dir_struct *dir) /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { - fill_directory(dir, pathspec); + if (!show_others) + dir->flags |= DIR_COLLECT_KILLED_ONLY; + fill_directory(dir, &pathspec); if (show_others) show_other_files(dir); if (show_killed) @@ -287,21 +290,6 @@ static void prune_cache(const char *prefix) active_nr = last; } -static void strip_trailing_slash_from_submodules(void) -{ - const char **p; - - for (p = pathspec; *p != NULL; p++) { - int len = strlen(*p), pos; - - if (len < 1 || (*p)[len - 1] != '/') - continue; - pos = cache_name_pos(*p, len - 1); - if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode)) - *p = xstrndup(*p, len - 1); - } -} - /* * Read the tree specified with --with-tree option * (typically, HEAD) into stage #1 and then @@ -333,13 +321,12 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } if (prefix) { - static const char *(matchbuf[2]); - matchbuf[0] = prefix; - matchbuf[1] = NULL; - init_pathspec(&pathspec, matchbuf); - pathspec.items[0].nowildcard_len = pathspec.items[0].len; + static const char *(matchbuf[1]); + matchbuf[0] = NULL; + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC, + PATHSPEC_PREFER_CWD, prefix, matchbuf); } else - init_pathspec(&pathspec, NULL); + memset(&pathspec, 0, sizeof(pathspec)); if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); @@ -364,15 +351,16 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix) +int report_path_error(const char *ps_matched, + const struct pathspec *pathspec, + const char *prefix) { /* * Make sure all pathspec matched; otherwise it is an error. */ struct strbuf sb = STRBUF_INIT; - const char *name; int num, errors = 0; - for (num = 0; pathspec[num]; num++) { + for (num = 0; num < pathspec->nr; num++) { int other, found_dup; if (ps_matched[num]) @@ -380,13 +368,16 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char /* * The caller might have fed identical pathspec * twice. Do not barf on such a mistake. + * FIXME: parse_pathspec should have eliminated + * duplicate pathspec. */ for (found_dup = other = 0; - !found_dup && pathspec[other]; + !found_dup && other < pathspec->nr; other++) { if (other == num || !ps_matched[other]) continue; - if (!strcmp(pathspec[other], pathspec[num])) + if (!strcmp(pathspec->items[other].original, + pathspec->items[num].original)) /* * Ok, we have a match already. */ @@ -395,9 +386,8 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char if (found_dup) continue; - name = quote_path_relative(pathspec[num], prefix, &sb); error("pathspec '%s' did not match any file(s) known to git.", - name); + pathspec->items[num].original); errors++; } strbuf_release(&sb); @@ -555,23 +545,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); - pathspec = get_pathspec(prefix, argv); - - /* be nice with submodule paths ending in a slash */ - if (pathspec) - strip_trailing_slash_from_submodules(); + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); /* Find common prefix for all pathspec's */ - max_prefix = common_prefix(pathspec); + max_prefix = common_prefix(&pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ - if (pathspec && error_unmatch) { - int num; - for (num = 0; pathspec[num]; num++) - ; - ps_matched = xcalloc(1, num); - } + if (pathspec.nr && error_unmatch) + ps_matched = xcalloc(1, pathspec.nr); if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) die("ls-files --ignored needs some exclude pattern"); @@ -598,7 +583,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix); + bad = report_path_error(ps_matched, &pathspec, prefix); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index de88563edf..65ec931846 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -10,6 +10,7 @@ #include "quote.h" #include "builtin.h" #include "parse-options.h" +#include "pathspec.h" static int line_termination = '\n'; #define LS_RECURSIVE 1 @@ -35,7 +36,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec.raw; + s = pathspec._raw; if (!s) return 0; @@ -166,7 +167,15 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (get_sha1(argv[0], sha1)) die("Not a valid object name %s", argv[0]); - init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); + /* + * show_recursive() rolls its own matching code and is + * generally ignorant of 'struct pathspec'. The magic mask + * cannot be lifted until it is converted to use + * match_pathspec_depth() or tree_entry_interesting() + */ + parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE, + PATHSPEC_PREFER_CWD, + prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) pathspec.items[i].nowildcard_len = pathspec.items[i].len; pathspec.has_wildcard = 0; diff --git a/builtin/merge.c b/builtin/merge.c index a8cf4a2484..41fb66dec2 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -186,13 +186,6 @@ static int option_parse_n(const struct option *opt, return 0; } -static int option_parse_ff_only(const struct option *opt, - const char *arg, int unset) -{ - fast_forward = FF_ONLY; - return 0; -} - static struct option builtin_merge_options[] = { { OPTION_CALLBACK, 'n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), @@ -210,9 +203,9 @@ static struct option builtin_merge_options[] = { OPT_BOOL('e', "edit", &option_edit, N_("edit message before committing")), OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), - { OPTION_CALLBACK, 0, "ff-only", NULL, NULL, + { OPTION_SET_INT, 0, "ff-only", &fast_forward, NULL, N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY }, OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("Verify that the named commit has a valid GPG signature")), @@ -1193,7 +1186,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * This could be traditional "merge <msg> HEAD <commit>..." and * the way we can tell it is to see if the second token is HEAD, * but some people might have misused the interface and used a - * committish that is the same as HEAD there instead. + * commit-ish that is the same as HEAD there instead. * Traditional format never would have "-m" so it is an * additional safety measure to check for it. */ diff --git a/builtin/mv.c b/builtin/mv.c index be6fa77d04..2e0e61b651 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -9,14 +9,16 @@ #include "cache-tree.h" #include "string-list.h" #include "parse-options.h" +#include "submodule.h" static const char * const builtin_mv_usage[] = { N_("git mv [options] <source>... <destination>"), NULL }; -static const char **copy_pathspec(const char *prefix, const char **pathspec, - int count, int base_name) +static const char **internal_copy_pathspec(const char *prefix, + const char **pathspec, + int count, int base_name) { int i; const char **result = xmalloc((count + 1) * sizeof(const char *)); @@ -53,10 +55,11 @@ static const char *add_slash(const char *path) } static struct lock_file lock_file; +#define SUBMODULE_WITH_GITDIR ((const char *)1) int cmd_mv(int argc, const char **argv, const char *prefix) { - int i, newfd; + int i, newfd, gitmodules_modified = 0; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), @@ -65,11 +68,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), OPT_END(), }; - const char **source, **destination, **dest_path; + const char **source, **destination, **dest_path, **submodule_gitfile; enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; + gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_mv_options, @@ -81,17 +85,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - source = copy_pathspec(prefix, argv, argc, 0); + source = internal_copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); - dest_path = copy_pathspec(prefix, argv + argc, 1, 0); + dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0); + submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = copy_pathspec(dest_path[0], argv, argc, 1); + destination = internal_copy_pathspec(dest_path[0], argv, argc, 1); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = copy_pathspec(dest_path[0], argv, argc, 1); + destination = internal_copy_pathspec(dest_path[0], argv, argc, 1); } else { if (argc != 1) die("destination '%s' is not a directory", dest_path[0]); @@ -117,55 +122,70 @@ int cmd_mv(int argc, const char **argv, const char *prefix) && lstat(dst, &st) == 0) bad = _("cannot move directory over file"); else if (src_is_dir) { - const char *src_w_slash = add_slash(src); - int len_w_slash = length + 1; - int first, last; - - modes[i] = WORKING_DIRECTORY; - - first = cache_name_pos(src_w_slash, len_w_slash); - if (first >= 0) - die (_("Huh? %.*s is in index?"), - len_w_slash, src_w_slash); - - first = -1 - first; - for (last = first; last < active_nr; last++) { - const char *path = active_cache[last]->name; - if (strncmp(path, src_w_slash, len_w_slash)) - break; - } - free((char *)src_w_slash); - - if (last - first < 1) - bad = _("source directory is empty"); - else { - int j, dst_len; - - if (last - first > 0) { - source = xrealloc(source, - (argc + last - first) - * sizeof(char *)); - destination = xrealloc(destination, - (argc + last - first) - * sizeof(char *)); - modes = xrealloc(modes, - (argc + last - first) - * sizeof(enum update_mode)); + int first = cache_name_pos(src, length); + if (first >= 0) { + struct strbuf submodule_dotgit = STRBUF_INIT; + if (!S_ISGITLINK(active_cache[first]->ce_mode)) + die (_("Huh? Directory %s is in index and no submodule?"), src); + if (!is_staging_gitmodules_ok()) + die (_("Please, stage your changes to .gitmodules or stash them to proceed")); + strbuf_addf(&submodule_dotgit, "%s/.git", src); + submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf); + if (submodule_gitfile[i]) + submodule_gitfile[i] = xstrdup(submodule_gitfile[i]); + else + submodule_gitfile[i] = SUBMODULE_WITH_GITDIR; + strbuf_release(&submodule_dotgit); + } else { + const char *src_w_slash = add_slash(src); + int last, len_w_slash = length + 1; + + modes[i] = WORKING_DIRECTORY; + + first = cache_name_pos(src_w_slash, len_w_slash); + if (first >= 0) + die (_("Huh? %.*s is in index?"), + len_w_slash, src_w_slash); + + first = -1 - first; + for (last = first; last < active_nr; last++) { + const char *path = active_cache[last]->name; + if (strncmp(path, src_w_slash, len_w_slash)) + break; } + free((char *)src_w_slash); + + if (last - first < 1) + bad = _("source directory is empty"); + else { + int j, dst_len; - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const char *path = - active_cache[first + j]->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, - path + length + 1); - modes[argc + j] = INDEX; + if (last - first > 0) { + source = xrealloc(source, + (argc + last - first) + * sizeof(char *)); + destination = xrealloc(destination, + (argc + last - first) + * sizeof(char *)); + modes = xrealloc(modes, + (argc + last - first) + * sizeof(enum update_mode)); + } + + dst = add_slash(dst); + dst_len = strlen(dst); + + for (j = 0; j < last - first; j++) { + const char *path = + active_cache[first + j]->name; + source[argc + j] = path; + destination[argc + j] = + prefix_path(dst, dst_len, + path + length + 1); + modes[argc + j] = INDEX; + } + argc += last - first; } - argc += last - first; } } else if (cache_name_pos(src, length) < 0) bad = _("not under version control"); @@ -210,9 +230,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int pos; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); - if (!show_only && mode != INDEX && - rename(src, dst) < 0 && !ignore_errors) - die_errno (_("renaming '%s' failed"), src); + if (!show_only && mode != INDEX) { + if (rename(src, dst) < 0 && !ignore_errors) + die_errno (_("renaming '%s' failed"), src); + if (submodule_gitfile[i]) { + if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); + if (!update_path_in_gitmodules(src, dst)) + gitmodules_modified = 1; + } + } if (mode == WORKING_DIRECTORY) continue; @@ -223,6 +250,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) rename_cache_entry_at(pos, dst); } + if (gitmodules_modified) + stage_updated_gitmodules(); + if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index a908a340c6..23daaa7d99 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -27,8 +27,7 @@ static void name_rev(struct commit *commit, struct commit_list *parents; int parent_number = 1; - if (!commit->object.parsed) - parse_commit(commit); + parse_commit(commit); if (commit->date < cutoff) return; @@ -331,7 +330,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (!!all + !!transform_stdin + !!argc > 1) { + if (all + transform_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f069462cb0..36273dd6f0 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -38,17 +38,18 @@ struct object_entry { void *delta_data; /* cached delta (uncompressed) */ unsigned long delta_size; /* delta data size (uncompressed) */ unsigned long z_delta_size; /* delta data size (compressed) */ - unsigned int hash; /* name hint hash */ enum object_type type; enum object_type in_pack_type; /* could be delta */ + uint32_t hash; /* name hint hash */ unsigned char in_pack_header_size; - unsigned char preferred_base; /* we do not pack this, but is available - * to be used as the base object to delta - * objects against. - */ - unsigned char no_try_delta; - unsigned char tagged; /* near the very tip of refs */ - unsigned char filled; /* assigned write-order */ + unsigned preferred_base:1; /* + * we do not pack this, but is available + * to be used as the base object to delta + * objects against. + */ + unsigned no_try_delta:1; + unsigned tagged:1; /* near the very tip of refs */ + unsigned filled:1; /* assigned write-order */ }; /* @@ -859,9 +860,9 @@ static void rehash_objects(void) } } -static unsigned name_hash(const char *name) +static uint32_t name_hash(const char *name) { - unsigned c, hash = 0; + uint32_t c, hash = 0; if (!name) return 0; @@ -908,7 +909,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, struct packed_git *p, *found_pack = NULL; off_t found_offset = 0; int ix; - unsigned hash = name_hash(name); + uint32_t hash = name_hash(name); ix = nr_objects ? locate_object_entry_hash(sha1) : -1; if (ix >= 0) { @@ -1809,7 +1810,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, static void try_to_free_from_threads(size_t size) { read_lock(); - release_pack_memory(size, -1); + release_pack_memory(size); read_unlock(); } @@ -2378,7 +2379,7 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); - mark_edges_uninteresting(revs.commits, &revs, show_edge); + mark_edges_uninteresting(&revs, show_edge); traverse_commit_list(&revs, show_commit, show_object, NULL); if (keep_unreachable) diff --git a/builtin/push.c b/builtin/push.c index bebaf3df1d..7b1b66c36a 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -15,12 +15,14 @@ static const char * const push_usage[] = { NULL, }; -static int thin; +static int thin = 1; static int deleterefs; static const char *receivepack; static int verbosity; static int progress = -1; +static struct push_cas_option cas; + static const char **refspec; static int refspec_nr; static int refspec_alloc; @@ -92,7 +94,7 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote if (!short_upstream) short_upstream = branch->merge[0]->src; /* - * Don't show advice for people who explicitely set + * Don't show advice for people who explicitly set * push.default. */ if (push_default == PUSH_DEFAULT_UNSPECIFIED) @@ -313,8 +315,14 @@ static int push_with_options(struct transport *transport, int flags) if (receivepack) transport_set_option(transport, TRANS_OPT_RECEIVEPACK, receivepack); - if (thin) - transport_set_option(transport, TRANS_OPT_THIN, "yes"); + transport_set_option(transport, TRANS_OPT_THIN, thin ? "yes" : NULL); + + if (!is_empty_cas(&cas)) { + if (!transport->smart_options) + die("underlying transport does not support --%s option", + CAS_OPT_NAME); + transport->smart_options->cas = &cas; + } if (verbosity > 0) fprintf(stderr, _("Pushing to %s\n"), transport->url); @@ -451,6 +459,10 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, + 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG, parseopt_push_cas_option }, { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"), N_("control recursive pushing of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 0f5d7fe23f..0d7ef847a7 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -178,7 +178,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (1 < opts.index_only + opts.update) die("-u and -i at the same time makes no sense"); - if ((opts.update||opts.index_only) && !opts.merge) + if ((opts.update || opts.index_only) && !opts.merge) die("%s is meaningless without -m, --reset, or --prefix", opts.update ? "-u" : "-i"); if ((opts.dir && !opts.update)) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e3eb5fc058..67ce1ef105 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -8,6 +8,7 @@ #include "commit.h" #include "object.h" #include "remote.h" +#include "connect.h" #include "transport.h" #include "string-list.h" #include "sha1-array.h" @@ -38,6 +39,7 @@ static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; +static int fix_thin = 1; static const char *head_name; static void *head_name_to_free; static int sent_capabilities; @@ -524,7 +526,8 @@ static const char *update(struct command *cmd) return NULL; /* good */ } else { - lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0); + lock = lock_any_ref_for_update(namespaced_name, old_sha1, + 0, NULL); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock"; @@ -869,7 +872,8 @@ static const char *unpack(int err_fd) keeper[i++] = "--stdin"; if (fsck_objects) keeper[i++] = "--strict"; - keeper[i++] = "--fix-thin"; + if (fix_thin) + keeper[i++] = "--fix-thin"; keeper[i++] = hdr_arg; keeper[i++] = keep_arg; keeper[i++] = NULL; @@ -975,6 +979,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) stateless_rpc = 1; continue; } + if (!strcmp(arg, "--reject-thin-pack-for-testing")) { + fix_thin = 0; + continue; + } usage(receive_pack_usage); } diff --git a/builtin/reflog.c b/builtin/reflog.c index 54184b3d13..6eb24c8da2 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -94,8 +94,7 @@ static int tree_is_complete(const unsigned char *sha1) complete = 0; } } - free(tree->buffer); - tree->buffer = NULL; + free_tree_buffer(tree); if (complete) tree->object.flags |= SEEN; @@ -366,7 +365,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, * we take the lock for the ref itself to prevent it from * getting updated. */ - lock = lock_any_ref_for_update(ref, sha1, 0); + lock = lock_any_ref_for_update(ref, sha1, 0, NULL); if (!lock) return error("cannot lock ref '%s'", ref); log_file = git_pathdup("logs/%s", ref); diff --git a/builtin/remote.c b/builtin/remote.c index eaac3e2012..4e14891095 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -12,7 +12,7 @@ static const char * const builtin_remote_usage[] = { N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"), N_("git remote rename <old> <new>"), N_("git remote remove <name>"), - N_("git remote set-head <name> (-a | -d | <branch>)"), + N_("git remote set-head <name> (-a | --auto | -d | --delete |<branch>)"), N_("git remote [-v | --verbose] show [-n] <name>"), N_("git remote prune [-n | --dry-run] <name>"), N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), @@ -39,7 +39,7 @@ static const char * const builtin_remote_rm_usage[] = { }; static const char * const builtin_remote_sethead_usage[] = { - N_("git remote set-head <name> (-a | -d | <branch>)"), + N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"), NULL }; diff --git a/builtin/repack.c b/builtin/repack.c new file mode 100644 index 0000000000..a0ff5c704f --- /dev/null +++ b/builtin/repack.c @@ -0,0 +1,388 @@ +#include "builtin.h" +#include "cache.h" +#include "dir.h" +#include "parse-options.h" +#include "run-command.h" +#include "sigchain.h" +#include "strbuf.h" +#include "string-list.h" +#include "argv-array.h" + +static int delta_base_offset = 1; +static char *packdir, *packtmp; + +static const char *const git_repack_usage[] = { + N_("git repack [options]"), + NULL +}; + +static int repack_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "repack.usedeltabaseoffset")) { + delta_base_offset = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +/* + * Remove temporary $GIT_OBJECT_DIRECTORY/pack/.tmp-$$-pack-* files. + */ +static void remove_temporary_files(void) +{ + struct strbuf buf = STRBUF_INIT; + size_t dirlen, prefixlen; + DIR *dir; + struct dirent *e; + + dir = opendir(packdir); + if (!dir) + return; + + /* Point at the slash at the end of ".../objects/pack/" */ + dirlen = strlen(packdir) + 1; + strbuf_addstr(&buf, packtmp); + /* Hold the length of ".tmp-%d-pack-" */ + prefixlen = buf.len - dirlen; + + while ((e = readdir(dir))) { + if (strncmp(e->d_name, buf.buf + dirlen, prefixlen)) + continue; + strbuf_setlen(&buf, dirlen); + strbuf_addstr(&buf, e->d_name); + unlink(buf.buf); + } + closedir(dir); + strbuf_release(&buf); +} + +static void remove_pack_on_signal(int signo) +{ + remove_temporary_files(); + sigchain_pop(signo); + raise(signo); +} + +/* + * Adds all packs hex strings to the fname list, which do not + * have a corresponding .keep file. + */ +static void get_non_kept_pack_filenames(struct string_list *fname_list) +{ + DIR *dir; + struct dirent *e; + char *fname; + size_t len; + + if (!(dir = opendir(packdir))) + return; + + while ((e = readdir(dir)) != NULL) { + if (suffixcmp(e->d_name, ".pack")) + continue; + + len = strlen(e->d_name) - strlen(".pack"); + fname = xmemdupz(e->d_name, len); + + if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) + string_list_append_nodup(fname_list, fname); + else + free(fname); + } + closedir(dir); +} + +static void remove_redundant_pack(const char *dir_name, const char *base_name) +{ + const char *exts[] = {".pack", ".idx", ".keep"}; + int i; + struct strbuf buf = STRBUF_INIT; + size_t plen; + + strbuf_addf(&buf, "%s/%s", dir_name, base_name); + plen = buf.len; + + for (i = 0; i < ARRAY_SIZE(exts); i++) { + strbuf_setlen(&buf, plen); + strbuf_addstr(&buf, exts[i]); + unlink(buf.buf); + } + strbuf_release(&buf); +} + +#define ALL_INTO_ONE 1 +#define LOOSEN_UNREACHABLE 2 + +int cmd_repack(int argc, const char **argv, const char *prefix) +{ + const char *exts[2] = {".pack", ".idx"}; + struct child_process cmd; + struct string_list_item *item; + struct argv_array cmd_args = ARGV_ARRAY_INIT; + struct string_list names = STRING_LIST_INIT_DUP; + struct string_list rollback = STRING_LIST_INIT_NODUP; + struct string_list existing_packs = STRING_LIST_INIT_DUP; + struct strbuf line = STRBUF_INIT; + int nr_packs, ext, ret, failed; + FILE *out; + + /* variables to be filled by option parsing */ + int pack_everything = 0; + int delete_redundant = 0; + char *unpack_unreachable = NULL; + int window = 0, window_memory = 0; + int depth = 0; + int max_pack_size = 0; + int no_reuse_delta = 0, no_reuse_object = 0; + int no_update_server_info = 0; + int quiet = 0; + int local = 0; + + struct option builtin_repack_options[] = { + OPT_BIT('a', NULL, &pack_everything, + N_("pack everything in a single pack"), ALL_INTO_ONE), + OPT_BIT('A', NULL, &pack_everything, + N_("same as -a, and turn unreachable objects loose"), + LOOSEN_UNREACHABLE | ALL_INTO_ONE), + OPT_BOOL('d', NULL, &delete_redundant, + N_("remove redundant packs, and run git-prune-packed")), + OPT_BOOL('f', NULL, &no_reuse_delta, + N_("pass --no-reuse-delta to git-pack-objects")), + OPT_BOOL('F', NULL, &no_reuse_object, + N_("pass --no-reuse-object to git-pack-objects")), + OPT_BOOL('n', NULL, &no_update_server_info, + N_("do not run git-update-server-info")), + OPT__QUIET(&quiet, N_("be quiet")), + OPT_BOOL('l', "local", &local, + N_("pass --local to git-pack-objects")), + OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"), + N_("with -A, do not loosen objects older than this")), + OPT_INTEGER(0, "window", &window, + N_("size of the window used for delta compression")), + OPT_INTEGER(0, "window-memory", &window_memory, + N_("same as the above, but limit memory size instead of entries count")), + OPT_INTEGER(0, "depth", &depth, + N_("limits the maximum delta depth")), + OPT_INTEGER(0, "max-pack-size", &max_pack_size, + N_("maximum size of each packfile")), + OPT_END() + }; + + git_config(repack_config, NULL); + + argc = parse_options(argc, argv, prefix, builtin_repack_options, + git_repack_usage, 0); + + packdir = mkpathdup("%s/pack", get_object_directory()); + packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); + + sigchain_push_common(remove_pack_on_signal); + + argv_array_push(&cmd_args, "pack-objects"); + argv_array_push(&cmd_args, "--keep-true-parents"); + argv_array_push(&cmd_args, "--honor-pack-keep"); + argv_array_push(&cmd_args, "--non-empty"); + argv_array_push(&cmd_args, "--all"); + argv_array_push(&cmd_args, "--reflog"); + if (window) + argv_array_pushf(&cmd_args, "--window=%u", window); + if (window_memory) + argv_array_pushf(&cmd_args, "--window-memory=%u", window_memory); + if (depth) + argv_array_pushf(&cmd_args, "--depth=%u", depth); + if (max_pack_size) + argv_array_pushf(&cmd_args, "--max_pack_size=%u", max_pack_size); + if (no_reuse_delta) + argv_array_pushf(&cmd_args, "--no-reuse-delta"); + if (no_reuse_object) + argv_array_pushf(&cmd_args, "--no-reuse-object"); + + if (pack_everything & ALL_INTO_ONE) { + get_non_kept_pack_filenames(&existing_packs); + + if (existing_packs.nr && delete_redundant) { + if (unpack_unreachable) + argv_array_pushf(&cmd_args, + "--unpack-unreachable=%s", + unpack_unreachable); + else if (pack_everything & LOOSEN_UNREACHABLE) + argv_array_push(&cmd_args, + "--unpack-unreachable"); + } + } else { + argv_array_push(&cmd_args, "--unpacked"); + argv_array_push(&cmd_args, "--incremental"); + } + + if (local) + argv_array_push(&cmd_args, "--local"); + if (quiet) + argv_array_push(&cmd_args, "--quiet"); + if (delta_base_offset) + argv_array_push(&cmd_args, "--delta-base-offset"); + + argv_array_push(&cmd_args, packtmp); + + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = cmd_args.argv; + cmd.git_cmd = 1; + cmd.out = -1; + cmd.no_stdin = 1; + + ret = start_command(&cmd); + if (ret) + return ret; + + nr_packs = 0; + out = xfdopen(cmd.out, "r"); + while (strbuf_getline(&line, out, '\n') != EOF) { + if (line.len != 40) + die("repack: Expecting 40 character sha1 lines only from pack-objects."); + string_list_append(&names, line.buf); + nr_packs++; + } + fclose(out); + ret = finish_command(&cmd); + if (ret) + return ret; + argv_array_clear(&cmd_args); + + if (!nr_packs && !quiet) + printf("Nothing new to pack.\n"); + + /* + * Ok we have prepared all new packfiles. + * First see if there are packs of the same name and if so + * if we can move them out of the way (this can happen if we + * repacked immediately after packing fully. + */ + failed = 0; + for_each_string_list_item(item, &names) { + for (ext = 0; ext < 2; ext++) { + char *fname, *fname_old; + fname = mkpathdup("%s/%s%s", packdir, + item->string, exts[ext]); + if (!file_exists(fname)) { + free(fname); + continue; + } + + fname_old = mkpath("%s/old-%s%s", packdir, + item->string, exts[ext]); + if (file_exists(fname_old)) + if (unlink(fname_old)) + failed = 1; + + if (!failed && rename(fname, fname_old)) { + free(fname); + failed = 1; + break; + } else { + string_list_append(&rollback, fname); + } + } + if (failed) + break; + } + if (failed) { + struct string_list rollback_failure = STRING_LIST_INIT_DUP; + for_each_string_list_item(item, &rollback) { + char *fname, *fname_old; + fname = mkpathdup("%s/%s", packdir, item->string); + fname_old = mkpath("%s/old-%s", packdir, item->string); + if (rename(fname_old, fname)) + string_list_append(&rollback_failure, fname); + free(fname); + } + + if (rollback_failure.nr) { + int i; + fprintf(stderr, + "WARNING: Some packs in use have been renamed by\n" + "WARNING: prefixing old- to their name, in order to\n" + "WARNING: replace them with the new version of the\n" + "WARNING: file. But the operation failed, and the\n" + "WARNING: attempt to rename them back to their\n" + "WARNING: original names also failed.\n" + "WARNING: Please rename them in %s manually:\n", packdir); + for (i = 0; i < rollback_failure.nr; i++) + fprintf(stderr, "WARNING: old-%s -> %s\n", + rollback_failure.items[i].string, + rollback_failure.items[i].string); + } + exit(1); + } + + /* Now the ones with the same name are out of the way... */ + for_each_string_list_item(item, &names) { + for (ext = 0; ext < 2; ext++) { + char *fname, *fname_old; + struct stat statbuffer; + fname = mkpathdup("%s/pack-%s%s", + packdir, item->string, exts[ext]); + fname_old = mkpathdup("%s-%s%s", + packtmp, item->string, exts[ext]); + if (!stat(fname_old, &statbuffer)) { + statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + chmod(fname_old, statbuffer.st_mode); + } + if (rename(fname_old, fname)) + die_errno(_("renaming '%s' failed"), fname_old); + free(fname); + free(fname_old); + } + } + + /* Remove the "old-" files */ + for_each_string_list_item(item, &names) { + for (ext = 0; ext < 2; ext++) { + char *fname; + fname = mkpath("%s/old-pack-%s%s", + packdir, + item->string, + exts[ext]); + if (remove_path(fname)) + warning(_("removing '%s' failed"), fname); + } + } + + /* End of pack replacement. */ + + if (delete_redundant) { + sort_string_list(&names); + for_each_string_list_item(item, &existing_packs) { + char *sha1; + size_t len = strlen(item->string); + if (len < 40) + continue; + sha1 = item->string + len - 40; + if (!string_list_has_string(&names, sha1)) + remove_redundant_pack(packdir, item->string); + } + argv_array_push(&cmd_args, "prune-packed"); + if (quiet) + argv_array_push(&cmd_args, "--quiet"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = cmd_args.argv; + cmd.git_cmd = 1; + run_command(&cmd); + argv_array_clear(&cmd_args); + } + + if (!no_update_server_info) { + argv_array_push(&cmd_args, "update-server-info"); + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = cmd_args.argv; + cmd.git_cmd = 1; + run_command(&cmd); + argv_array_clear(&cmd_args); + } + remove_temporary_files(); + string_list_clear(&names, 0); + string_list_clear(&rollback, 0); + string_list_clear(&existing_packs, 0); + strbuf_release(&line); + + return 0; +} diff --git a/builtin/replace.c b/builtin/replace.c index 11b0a55ae7..b1bd3ef994 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -85,6 +85,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int force) { unsigned char object[20], prev[20], repl[20]; + enum object_type obj_type, repl_type; char ref[PATH_MAX]; struct ref_lock *lock; @@ -100,12 +101,21 @@ static int replace_object(const char *object_ref, const char *replace_ref, if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); + obj_type = sha1_object_info(object, NULL); + repl_type = sha1_object_info(repl, NULL); + if (!force && obj_type != repl_type) + die("Objects must be of the same type.\n" + "'%s' points to a replaced object of type '%s'\n" + "while '%s' points to a replacement object of type '%s'.", + object_ref, typename(obj_type), + replace_ref, typename(repl_type)); + if (read_ref(ref, prev)) hashclr(prev); else if (!force) die("replace ref '%s' already exists", ref); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref, prev, 0, NULL); if (!lock) die("%s: cannot lock the ref", ref); if (write_ref_sha1(lock, repl, NULL) < 0) @@ -118,9 +128,9 @@ int cmd_replace(int argc, const char **argv, const char *prefix) { int list = 0, delete = 0, force = 0; struct option options[] = { - OPT_BOOL('l', NULL, &list, N_("list replace refs")), - OPT_BOOL('d', NULL, &delete, N_("delete replace refs")), - OPT_BOOL('f', NULL, &force, N_("replace the ref if it exists")), + OPT_BOOL('l', "list", &list, N_("list replace refs")), + OPT_BOOL('d', "delete", &delete, N_("delete replace refs")), + OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")), OPT_END() }; diff --git a/builtin/rerere.c b/builtin/rerere.c index dc1708e6d6..4e51addb3e 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -6,6 +6,7 @@ #include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" +#include "pathspec.h" static const char * const rerere_usage[] = { N_("git rerere [clear | forget path... | status | remaining | diff | gc]"), @@ -68,11 +69,12 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return rerere(flags); if (!strcmp(argv[0], "forget")) { - const char **pathspec; + struct pathspec pathspec; if (argc < 2) warning("'git rerere forget' without paths is deprecated"); - pathspec = get_pathspec(prefix, argv + 1); - return rerere_forget(pathspec); + parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, + prefix, argv + 1); + return rerere_forget(&pathspec); } fd = setup_rerere(&merge_rr, flags); diff --git a/builtin/reset.c b/builtin/reset.c index 090581564b..60048030dd 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -133,21 +133,21 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } -static int read_from_tree(const char **pathspec, unsigned char *tree_sha1) +static int read_from_tree(const struct pathspec *pathspec, + unsigned char *tree_sha1) { struct diff_options opt; memset(&opt, 0, sizeof(opt)); - diff_tree_setup_paths(pathspec, &opt); + copy_pathspec(&opt.pathspec, pathspec); opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; - read_cache(); if (do_diff_cache(tree_sha1, &opt)) return 1; diffcore_std(&opt); diff_flush(&opt); - diff_tree_release_paths(&opt); + free_pathspec(&opt.pathspec); return 0; } @@ -168,13 +168,16 @@ static void set_reflog_message(struct strbuf *sb, const char *action, static void die_if_unmerged_cache(int reset_type) { - if (is_merge() || read_cache() < 0 || unmerged_cache()) + if (is_merge() || unmerged_cache()) die(_("Cannot do a %s reset in the middle of a merge."), _(reset_type_names[reset_type])); } -static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret) +static void parse_args(struct pathspec *pathspec, + const char **argv, const char *prefix, + int patch_mode, + const char **rev_ret) { const char *rev = "HEAD"; unsigned char unused[20]; @@ -216,10 +219,18 @@ static const char **parse_args(const char **argv, const char *prefix, const char } } *rev_ret = rev; - return argv[0] ? get_pathspec(prefix, argv) : NULL; + + if (read_cache() < 0) + die(_("index file corrupt")); + + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP | + (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), + prefix, argv); } -static int update_refs(const char *rev, const unsigned char *sha1) +static int reset_refs(const char *rev, const unsigned char *sha1) { int update_ref_status; struct strbuf msg = STRBUF_INIT; @@ -246,7 +257,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int patch_mode = 0, unborn; const char *rev; unsigned char sha1[20]; - const char **pathspec = NULL; + struct pathspec pathspec; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_SET_INT(0, "mixed", &reset_type, @@ -266,13 +277,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - pathspec = parse_args(argv, prefix, &rev); + parse_args(&pathspec, argv, prefix, patch_mode, &rev); unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ hashcpy(sha1, EMPTY_TREE_SHA1_BIN); - } else if (!pathspec) { + } else if (!pathspec.nr) { struct commit *commit; if (get_sha1_committish(rev, sha1)) die(_("Failed to resolve '%s' as a valid revision."), rev); @@ -293,13 +304,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (patch_mode) { if (reset_type != NONE) die(_("--patch is incompatible with --{hard,mixed,soft}")); - return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec); + return run_add_interactive(rev, "--patch=reset", &pathspec); } /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ - if (pathspec) { + if (pathspec.nr) { if (reset_type == MIXED) warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.")); else if (reset_type != NONE) @@ -323,11 +334,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die_if_unmerged_cache(reset_type); if (reset_type != SOFT) { - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + struct lock_file *lock = xcalloc(1, sizeof(*lock)); int newfd = hold_locked_index(lock, 1); if (reset_type == MIXED) { - if (read_from_tree(pathspec, sha1)) + int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; + if (read_from_tree(&pathspec, sha1)) return 1; + refresh_index(&the_index, flags, NULL, NULL, + _("Unstaged changes after reset:")); } else { int err = reset_index(sha1, reset_type, quiet); if (reset_type == KEEP && !err) @@ -336,26 +350,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die(_("Could not reset index file to revision '%s'."), rev); } - if (reset_type == MIXED) { /* Report what has not been updated. */ - int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; - refresh_index(&the_index, flags, NULL, NULL, - _("Unstaged changes after reset:")); - } - if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) die(_("Could not write new index file.")); } - if (!pathspec && !unborn) { + if (!pathspec.nr && !unborn) { /* Any resets without paths update HEAD to the head being * switched to, saving the previous head in ORIG_HEAD before. */ - update_ref_status = update_refs(rev, sha1); + update_ref_status = reset_refs(rev, sha1); if (reset_type == HARD && !update_ref_status && !quiet) print_new_head_line(lookup_commit_reference(sha1)); } - if (!pathspec) + if (!pathspec.nr) remove_branch_state(); return update_ref_status; diff --git a/builtin/rev-list.c b/builtin/rev-list.c index a5ec30d74e..0745e2d053 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -322,7 +322,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commit_format = CMIT_FMT_RAW; if ((!revs.commits && - (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && + (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) && !revs.pending.nr)) || revs.diff) usage(rev_list_usage); @@ -336,7 +336,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); if (revs.tree_objects) - mark_edges_uninteresting(revs.commits, &revs, show_edge); + mark_edges_uninteresting(&revs, show_edge); if (bisect_list) { int reaches = reaches, all = all; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 67e8ccfd9f..c76b89dc5b 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -486,21 +486,6 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (argc > 1 && !strcmp("--sq-quote", argv[1])) return cmd_sq_quote(argc - 2, argv + 2); - if (argc == 2 && !strcmp("--local-env-vars", argv[1])) { - int i; - for (i = 0; local_repo_env[i]; i++) - printf("%s\n", local_repo_env[i]); - return 0; - } - - if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) { - const char *gitdir = resolve_gitdir(argv[2]); - if (!gitdir) - die("not a gitdir '%s'", argv[2]); - puts(gitdir); - return 0; - } - if (argc > 1 && !strcmp("-h", argv[1])) usage(builtin_rev_parse_usage); @@ -661,6 +646,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_remote_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--local-env-vars")) { + int i; + for (i = 0; local_repo_env[i]; i++) + printf("%s\n", local_repo_env[i]); + continue; + } if (!strcmp(arg, "--show-toplevel")) { const char *work_tree = get_git_work_tree(); if (work_tree) @@ -711,6 +702,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); continue; } + if (!strcmp(arg, "--resolve-git-dir")) { + const char *gitdir = resolve_gitdir(argv[i+1]); + if (!gitdir) + die("not a gitdir '%s'", argv[i+1]); + puts(gitdir); + continue; + } if (!strcmp(arg, "--is-inside-git-dir")) { printf("%s\n", is_inside_git_dir() ? "true" : "false"); diff --git a/builtin/revert.c b/builtin/revert.c index 1d2648b756..87659c9fdb 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -71,44 +71,19 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -LAST_ARG_MUST_BE_NULL -static void verify_opt_mutually_compatible(const char *me, ...) -{ - const char *opt1, *opt2 = NULL; - va_list ap; - - va_start(ap, me); - while ((opt1 = va_arg(ap, const char *))) { - if (va_arg(ap, int)) - break; - } - if (opt1) { - while ((opt2 = va_arg(ap, const char *))) { - if (va_arg(ap, int)) - break; - } - } - va_end(ap); - - if (opt1 && opt2) - die(_("%s: %s cannot be used with %s"), me, opt1, opt2); -} - static void parse_args(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); - int remove_state = 0; - int contin = 0; - int rollback = 0; + int cmd = 0; struct option options[] = { - OPT_BOOLEAN(0, "quit", &remove_state, N_("end revert or cherry-pick sequence")), - OPT_BOOLEAN(0, "continue", &contin, N_("resume revert or cherry-pick sequence")), - OPT_BOOLEAN(0, "abort", &rollback, N_("cancel revert or cherry-pick sequence")), - OPT_BOOLEAN('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), - OPT_BOOLEAN('e', "edit", &opts->edit, N_("edit the commit message")), + OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'), + OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'), + OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'), + OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), + OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")), OPT_NOOP_NOARG('r', NULL), - OPT_BOOLEAN('s', "signoff", &opts->signoff, N_("add Signed-off-by:")), + OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")), OPT_INTEGER('m', "mainline", &opts->mainline, N_("parent number")), OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), OPT_STRING(0, "strategy", &opts->strategy, N_("strategy"), N_("merge strategy")), @@ -124,11 +99,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { - OPT_BOOLEAN('x', NULL, &opts->record_origin, N_("append commit name")), - OPT_BOOLEAN(0, "ff", &opts->allow_ff, N_("allow fast-forward")), - OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")), - OPT_BOOLEAN(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")), - OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")), + OPT_BOOL('x', NULL, &opts->record_origin, N_("append commit name")), + OPT_BOOL(0, "ff", &opts->allow_ff, N_("allow fast-forward")), + OPT_BOOL(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")), + OPT_BOOL(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")), + OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")), OPT_END(), }; if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) @@ -139,23 +114,16 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); - /* Check for incompatible subcommands */ - verify_opt_mutually_compatible(me, - "--quit", remove_state, - "--continue", contin, - "--abort", rollback, - NULL); - /* implies allow_empty */ if (opts->keep_redundant_commits) opts->allow_empty = 1; /* Set the subcommand */ - if (remove_state) + if (cmd == 'q') opts->subcommand = REPLAY_REMOVE_STATE; - else if (contin) + else if (cmd == 'c') opts->subcommand = REPLAY_CONTINUE; - else if (rollback) + else if (cmd == 'a') opts->subcommand = REPLAY_ROLLBACK; else opts->subcommand = REPLAY_NONE; @@ -200,6 +168,8 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; if (argc < 2) usage_with_options(usage_str, options); + if (!strcmp(argv[1], "-")) + argv[1] = "@{-1}"; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.assume_dashdash = 1; argc = setup_revisions(argc, argv, opts->revs, &s_r_opt); diff --git a/builtin/rm.c b/builtin/rm.c index ce28fa9ac9..3a0e0eaab7 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "string-list.h" #include "submodule.h" +#include "pathspec.h" static const char * const builtin_rm_usage[] = { N_("git rm [options] [--] <file>..."), @@ -58,6 +59,21 @@ static void print_error_files(struct string_list *files_list, } } +static void error_removing_concrete_submodules(struct string_list *files, int *errs) +{ + print_error_files(files, + Q_("the following submodule (or one of its nested " + "submodules)\n" + "uses a .git directory:", + "the following submodules (or one of its nested " + "submodules)\n" + "use a .git directory:", files->nr), + _("\n(use 'rm -rf' if you really want to remove " + "it including all of its history)"), + errs); + string_list_clear(files, 0); +} + static int check_submodules_use_gitfiles(void) { int i; @@ -86,16 +102,8 @@ static int check_submodules_use_gitfiles(void) if (!submodule_uses_gitfile(name)) string_list_append(&files, name); } - print_error_files(&files, - Q_("the following submodule (or one of its nested " - "submodules)\n uses a .git directory:", - "the following submodules (or one of its nested " - "submodules)\n use a .git directory:", - files.nr), - _("\n(use 'rm -rf' if you really want to remove " - "it including all of its history)"), - &errs); - string_list_clear(&files, 0); + + error_removing_concrete_submodules(&files, &errs); return errs; } @@ -237,17 +245,9 @@ static int check_local_mod(unsigned char *head, int index_only) " or -f to force removal)"), &errs); string_list_clear(&files_cached, 0); - print_error_files(&files_submodule, - Q_("the following submodule (or one of its nested " - "submodule)\nuses a .git directory:", - "the following submodules (or one of its nested " - "submodule)\nuse a .git directory:", - files_submodule.nr), - _("\n(use 'rm -rf' if you really " - "want to remove it including all " - "of its history)"), - &errs); - string_list_clear(&files_submodule, 0); + + error_removing_concrete_submodules(&files_submodule, &errs); + print_error_files(&files_local, Q_("the following file has local modifications:", "the following files have local modifications:", @@ -279,9 +279,10 @@ static struct option builtin_rm_options[] = { int cmd_rm(int argc, const char **argv, const char *prefix) { int i, newfd; - const char **pathspec; + struct pathspec pathspec; char *seen; + gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_rm_options, @@ -297,46 +298,35 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - /* - * Drop trailing directory separators from directories so we'll find - * submodules in the index. - */ - for (i = 0; i < argc; i++) { - size_t pathlen = strlen(argv[i]); - if (pathlen && is_dir_sep(argv[i][pathlen - 1]) && - is_directory(argv[i])) { - do { - pathlen--; - } while (pathlen && is_dir_sep(argv[i][pathlen - 1])); - argv[i] = xmemdupz(argv[i], pathlen); - } - } - - pathspec = get_pathspec(prefix, argv); - refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); + refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL); - seen = NULL; - for (i = 0; pathspec[i] ; i++) - /* nothing */; - seen = xcalloc(i, 1); + seen = xcalloc(pathspec.nr, 1); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) + if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; ALLOC_GROW(list.entry, list.nr + 1, list.alloc); list.entry[list.nr].name = ce->name; - list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode); + list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); + if (list.entry[list.nr++].is_submodule && + !is_staging_gitmodules_ok()) + die (_("Please, stage your changes to .gitmodules or stash them to proceed")); } - if (pathspec) { - const char *match; + if (pathspec.nr) { + const char *original; int seen_any = 0; - for (i = 0; (match = pathspec[i]) != NULL ; i++) { + for (i = 0; i < pathspec.nr; i++) { + original = pathspec.items[i].original; if (!seen[i]) { if (!ignore_unmatch) { die(_("pathspec '%s' did not match any files"), - match); + original); } } else { @@ -344,10 +334,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } if (!recursive && seen[i] == MATCHED_RECURSIVELY) die(_("not removing '%s' recursively without -r"), - *match ? match : "."); + *original ? original : "."); } - if (! seen_any) + if (!seen_any) exit(0); } @@ -397,13 +387,15 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * in the middle) */ if (!index_only) { - int removed = 0; + int removed = 0, gitmodules_modified = 0; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { if (is_empty_dir(path)) { if (!rmdir(path)) { removed = 1; + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; continue; } } else { @@ -411,9 +403,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix) strbuf_addstr(&buf, path); if (!remove_dir_recursively(&buf, 0)) { removed = 1; + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; strbuf_release(&buf); continue; - } + } else if (!file_exists(path)) + /* Submodule was removed by user */ + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; strbuf_release(&buf); /* Fallthrough and let remove_path() fail. */ } @@ -425,6 +422,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!removed) die_errno("git rm: '%s'", path); } + if (gitmodules_modified) + stage_updated_gitmodules(); } if (active_cache_changed) { diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 152c4ea092..4482f16efb 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -5,6 +5,7 @@ #include "sideband.h" #include "run-command.h" #include "remote.h" +#include "connect.h" #include "send-pack.h" #include "quote.h" #include "transport.h" @@ -54,6 +55,11 @@ static void print_helper_status(struct ref *ref) msg = "needs force"; break; + case REF_STATUS_REJECT_STALE: + res = "error"; + msg = "stale info"; + break; + case REF_STATUS_REJECT_ALREADY_EXISTS: res = "error"; msg = "already exists"; @@ -102,6 +108,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int flags; unsigned int reject_reasons; int progress = -1; + struct push_cas_option cas = {0}; argv++; for (i = 1; i < argc; i++, argv++) { @@ -164,6 +171,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) helper_status = 1; continue; } + if (!strcmp(arg, "--" CAS_OPT_NAME)) { + if (parse_push_cas_option(&cas, NULL, 0) < 0) + exit(1); + continue; + } + if (!strcmp(arg, "--no-" CAS_OPT_NAME)) { + if (parse_push_cas_option(&cas, NULL, 1) < 0) + exit(1); + continue; + } + if (!prefixcmp(arg, "--" CAS_OPT_NAME "=")) { + if (parse_push_cas_option(&cas, + strchr(arg, '=') + 1, 0) < 0) + exit(1); + continue; + } usage(send_pack_usage); } if (!dest) { @@ -224,6 +247,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; + if (!is_empty_cas(&cas)) + apply_push_cas(&cas, remote, remote_refs); + set_ref_status_for_push(remote_refs, args.send_mirror, args.force_update); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index ae73d17b6c..c226f767aa 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -127,9 +127,11 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) author = buffer + 7; buffer = eol; } - if (!author) - die(_("Missing author: %s"), + if (!author) { + warning(_("Missing author: %s"), sha1_to_hex(commit->object.sha1)); + return; + } if (log->user_format) { struct pretty_print_context ctx = {0}; ctx.fmt = CMIT_FMT_USERFORMAT; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 001f29ca1b..46902c3de4 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -227,8 +227,7 @@ static void join_revs(struct commit_list **list_p, parents = parents->next; if ((this_flag & flags) == flags) continue; - if (!p->object.parsed) - parse_commit(p); + parse_commit(p); if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index e981dfb9f0..1259ed708b 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -89,11 +89,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) if (argc == 2) { if (!strcmp(argv[1], "-s") || - !strcmp(argv[1], "--strip-comments")) { - strip_comments = 1; + !strcmp(argv[1], "--strip-comments")) { + strip_comments = 1; } else if (!strcmp(argv[1], "-c") || - !strcmp(argv[1], "--comment-lines")) { - mode = COMMENT_LINES; + !strcmp(argv[1], "--comment-lines")) { + mode = COMMENT_LINES; } else { mode = INVAL; } diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index f481959421..71286b4fae 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -47,7 +47,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_symbolic_ref_usage, 0); - if (msg &&!*msg) + if (msg && !*msg) die("Refusing to perform update with empty message"); if (delete) { diff --git a/builtin/tag.c b/builtin/tag.c index b577af562a..ea55f1d1bd 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -574,7 +574,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (annotate) create_tag(object, tag, &buf, &opt, prev, object); - lock = lock_any_ref_for_update(ref.buf, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL); if (!lock) die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) diff --git a/builtin/tar-tree.c b/builtin/tar-tree.c index 3f1e7012db..ba3ffe69a9 100644 --- a/builtin/tar-tree.c +++ b/builtin/tar-tree.c @@ -26,8 +26,8 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) * $0 tree-ish basedir ==> * git archive --format-tar --prefix=basedir tree-ish */ - int i; const char **nargv = xcalloc(sizeof(*nargv), argc + 3); + struct strbuf sb = STRBUF_INIT; char *basedir_arg; int nargc = 0; @@ -65,11 +65,10 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) fprintf(stderr, "*** \"git tar-tree\" is now deprecated.\n" "*** Running \"git archive\" instead.\n***"); - for (i = 0; i < nargc; i++) { - fputc(' ', stderr); - sq_quote_print(stderr, nargv[i]); - } - fputc('\n', stderr); + sq_quote_argv(&sb, nargv, 0); + strbuf_addch(&sb, '\n'); + fputs(sb.buf, stderr); + strbuf_release(&sb); return cmd_archive(nargc, nargv, prefix); } diff --git a/builtin/update-index.c b/builtin/update-index.c index c317981516..e3a10d706d 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -11,6 +11,7 @@ #include "refs.h" #include "resolve-undo.h" #include "parse-options.h" +#include "pathspec.h" /* * Default to not allowing changes to the list of files. The @@ -546,10 +547,11 @@ static int do_reupdate(int ac, const char **av, */ int pos; int has_head = 1; - const char **paths = get_pathspec(prefix, av + 1); struct pathspec pathspec; - init_pathspec(&pathspec, paths); + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, av + 1); if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7484d36a65..702e90db2a 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -2,23 +2,261 @@ #include "refs.h" #include "builtin.h" #include "parse-options.h" +#include "quote.h" +#include "argv-array.h" static const char * const git_update_ref_usage[] = { N_("git update-ref [options] -d <refname> [<oldval>]"), N_("git update-ref [options] <refname> <newval> [<oldval>]"), + N_("git update-ref [options] --stdin [-z]"), NULL }; +static int updates_alloc; +static int updates_count; +static const struct ref_update **updates; + +static char line_termination = '\n'; +static int update_flags; + +static struct ref_update *update_alloc(void) +{ + struct ref_update *update; + + /* Allocate and zero-init a struct ref_update */ + update = xcalloc(1, sizeof(*update)); + ALLOC_GROW(updates, updates_count + 1, updates_alloc); + updates[updates_count++] = update; + + /* Store and reset accumulated options */ + update->flags = update_flags; + update_flags = 0; + + return update; +} + +static void update_store_ref_name(struct ref_update *update, + const char *ref_name) +{ + if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", ref_name); + update->ref_name = xstrdup(ref_name); +} + +static void update_store_new_sha1(struct ref_update *update, + const char *newvalue) +{ + if (*newvalue && get_sha1(newvalue, update->new_sha1)) + die("invalid new value for ref %s: %s", + update->ref_name, newvalue); +} + +static void update_store_old_sha1(struct ref_update *update, + const char *oldvalue) +{ + if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) + die("invalid old value for ref %s: %s", + update->ref_name, oldvalue); + + /* We have an old value if non-empty, or if empty without -z */ + update->have_old = *oldvalue || line_termination; +} + +static const char *parse_arg(const char *next, struct strbuf *arg) +{ + /* Parse SP-terminated, possibly C-quoted argument */ + if (*next != '"') + while (*next && !isspace(*next)) + strbuf_addch(arg, *next++); + else if (unquote_c_style(arg, next, &next)) + die("badly quoted argument: %s", next); + + /* Return position after the argument */ + return next; +} + +static const char *parse_first_arg(const char *next, struct strbuf *arg) +{ + /* Parse argument immediately after "command SP" */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, use the next argument */ + next = parse_arg(next, arg); + } else { + /* With -z, use rest of first NUL-terminated line */ + strbuf_addstr(arg, next); + next = next + arg->len; + } + return next; +} + +static const char *parse_next_arg(const char *next, struct strbuf *arg) +{ + /* Parse next SP-terminated or NUL-terminated argument, if any */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, consume SP and use next argument */ + if (!*next) + return NULL; + if (*next != ' ') + die("expected SP but got: %s", next); + next = parse_arg(next + 1, arg); + } else { + /* With -z, read the next NUL-terminated line */ + if (*next) + die("expected NUL but got: %s", next); + if (strbuf_getline(arg, stdin, '\0') == EOF) + return NULL; + next = arg->buf + arg->len; + } + return next; +} + +static void parse_cmd_update(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("update line missing <ref>"); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("update %s missing <newvalue>", ref.buf); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("update %s missing [<oldvalue>] NUL", ref.buf); + + if (next && *next) + die("update %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_create(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("create line missing <ref>"); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("create %s missing <newvalue>", ref.buf); + if (is_null_sha1(update->new_sha1)) + die("create %s given zero new value", ref.buf); + + if (next && *next) + die("create %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_delete(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("delete line missing <ref>"); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("delete %s missing [<oldvalue>] NUL", ref.buf); + if (update->have_old && is_null_sha1(update->old_sha1)) + die("delete %s given zero old value", ref.buf); + + if (next && *next) + die("delete %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_verify(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf value = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("verify line missing <ref>"); + + if ((next = parse_next_arg(next, &value)) != NULL) { + update_store_old_sha1(update, value.buf); + update_store_new_sha1(update, value.buf); + } else if(!line_termination) + die("verify %s missing [<oldvalue>] NUL", ref.buf); + + if (next && *next) + die("verify %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_option(const char *next) +{ + if (!strcmp(next, "no-deref")) + update_flags |= REF_NODEREF; + else + die("option unknown: %s", next); +} + +static void update_refs_stdin(void) +{ + struct strbuf cmd = STRBUF_INIT; + + /* Read each line dispatch its command */ + while (strbuf_getline(&cmd, stdin, line_termination) != EOF) + if (!cmd.buf[0]) + die("empty command in input"); + else if (isspace(*cmd.buf)) + die("whitespace before command: %s", cmd.buf); + else if (!prefixcmp(cmd.buf, "update ")) + parse_cmd_update(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "create ")) + parse_cmd_create(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "delete ")) + parse_cmd_delete(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "verify ")) + parse_cmd_verify(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "option ")) + parse_cmd_option(cmd.buf + 7); + else + die("unknown command: %s", cmd.buf); + + strbuf_release(&cmd); +} + int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval, *msg = NULL; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), OPT_BOOL( 0 , "no-deref", &no_deref, N_("update <refname> not the one it points to")), + OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), + OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), OPT_END(), }; @@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); + if (read_stdin) { + if (delete || no_deref || argc > 0) + usage_with_options(git_update_ref_usage, options); + if (end_null) + line_termination = '\0'; + update_refs_stdin(); + return update_refs(msg, updates, updates_count, DIE_ON_ERR); + } + + if (end_null) + usage_with_options(git_update_ref_usage, options); + if (delete) { if (argc < 1 || argc > 2) usage_with_options(git_update_ref_usage, options); |