From: Junio C Hamano Date: Mon, 9 Sep 2013 21:36:15 +0000 (-0700) Subject: Merge branch 'jl/submodule-mv' X-Git-Tag: v1.8.5-rc0~165 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/b02f5aeda6f66ac3be9b2e35f9b237d4f1f80d73?hp=-c Merge branch 'jl/submodule-mv' "git mv A B" when moving a submodule A does "the right thing", inclusing relocating its working tree and adjusting the paths in the .gitmodules file. * jl/submodule-mv: (53 commits) rm: delete .gitmodules entry of submodules removed from the work tree mv: update the path entry in .gitmodules for moved submodules submodule.c: add .gitmodules staging helper functions mv: move submodules using a gitfile mv: move submodules together with their work trees rm: do not set a variable twice without intermediate reading. t6131 - skip tests if on case-insensitive file system parse_pathspec: accept :(icase)path syntax pathspec: support :(glob) syntax pathspec: make --literal-pathspecs disable pathspec magic pathspec: support :(literal) syntax for noglob pathspec kill limit_pathspec_to_literal() as it's only used by parse_pathspec() parse_pathspec: preserve prefix length via PATHSPEC_PREFIX_ORIGIN parse_pathspec: make sure the prefix part is wildcard-free rename field "raw" to "_raw" in struct pathspec tree-diff: remove the use of pathspec's raw[] in follow-rename codepath remove match_pathspec() in favor of match_pathspec_depth() remove init_pathspec() in favor of parse_pathspec() remove diff_tree_{setup,release}_paths convert common_prefix() to use struct pathspec ... --- b02f5aeda6f66ac3be9b2e35f9b237d4f1f80d73 diff --combined Documentation/git.txt index 83edf308b1,2c1f6f5f53..c4f0ed5957 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@@ -43,16 -43,9 +43,16 @@@ unreleased) version of Git, that is ava branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.8.3.2/git.html[documentation for release 1.8.3.2] +* link:v1.8.4/git.html[documentation for release 1.8.4] * release notes for + link:RelNotes/1.8.4.txt[1.8.4]. + +* link:v1.8.3.4/git.html[documentation for release 1.8.3.4] + +* release notes for + link:RelNotes/1.8.3.4.txt[1.8.3.4], + link:RelNotes/1.8.3.3.txt[1.8.3.3], link:RelNotes/1.8.3.2.txt[1.8.3.2], link:RelNotes/1.8.3.1.txt[1.8.3.1], link:RelNotes/1.8.3.txt[1.8.3]. @@@ -457,10 -450,25 +457,25 @@@ help ...` linkgit:git-replace[1] for more information. --literal-pathspecs:: - Treat pathspecs literally, rather than as glob patterns. This is - equivalent to setting the `GIT_LITERAL_PATHSPECS` environment + Treat pathspecs literally (i.e. no globbing, no pathspec magic). + This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment variable to `1`. + --glob-pathspecs: + Add "glob" magic to all pathspec. This is equivalent to setting + the `GIT_GLOB_PATHSPECS` environment variable to `1`. Disabling + globbing on individual pathspecs can be done using pathspec + magic ":(literal)" + + --noglob-pathspecs: + Add "literal" magic to all pathspec. This is equivalent to setting + the `GIT_NOGLOB_PATHSPECS` environment variable to `1`. Enabling + globbing on individual pathspecs can be done using pathspec + magic ":(glob)" + + --icase-pathspecs: + Add "icase" magic to all pathspec. This is equivalent to setting + the `GIT_ICASE_PATHSPECS` environment variable to `1`. GIT COMMANDS ------------ @@@ -823,7 -831,7 +838,7 @@@ for further details 'GIT_FLUSH':: If this environment variable is set to "1", then commands such as 'git blame' (in incremental mode), 'git rev-list', 'git log', - 'git check-attr', 'git check-ignore', and 'git whatchanged' will + 'git check-attr' and 'git check-ignore' will force a flush of the output stream after each record have been flushed. If this variable is set to "0", the output of these commands will be done @@@ -867,6 -875,18 +882,18 @@@ GIT_LITERAL_PATHSPECS: literal paths to Git (e.g., paths previously given to you by `git ls-tree`, `--raw` diff output, etc). + GIT_GLOB_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as glob patterns (aka "glob" magic). + + GIT_NOGLOB_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as literal (aka "literal" magic). + + GIT_ICASE_PATHSPECS:: + Setting this variable to `1` will cause Git to treat all + pathspecs as case-insensitive. + Discussion[[Discussion]] ------------------------ diff --combined builtin/add.c index 8266a9cb70,9d52fc7915..ae0bdc78bb --- a/builtin/add.c +++ b/builtin/add.c @@@ -166,14 -166,16 +166,16 @@@ static void update_callback(struct diff } } - 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 +183,8 @@@ 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 +195,21 @@@ } #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 +223,33 @@@ 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 +257,9 @@@ 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,23 +268,23 @@@ 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; - } + /* + * git-add--interactive itself does not parse pathspec. It + * simply passes the pathspec to other builtin commands. Let's + * hope all of them support all magic, or we'll need to limit + * the magic here. + */ + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, + 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) @@@ -343,7 -309,6 +309,7 @@@ argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; + rev.diffopt.use_color = 0; DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) @@@ -446,7 -411,7 +412,7 @@@ int cmd_add(int argc, const char **argv { int exit_status = 0; int newfd; - const char **pathspec; + struct pathspec pathspec; struct dir_struct dir; int flags; int add_new_files; @@@ -527,14 -492,23 +493,23 @@@ 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 -517,49 +518,49 @@@ 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 -575,11 +576,11 @@@ */ 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) diff --combined builtin/blame.c index 00927e0347,56e3d6b7b4..6da7233968 --- a/builtin/blame.c +++ b/builtin/blame.c @@@ -22,7 -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"); @@@ -409,7 -408,7 +409,7 @@@ static struct origin *find_origin(struc paths[0] = origin->path; paths[1] = NULL; - diff_tree_setup_paths(paths, &diff_opts); + parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC, 0, "", paths); diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) @@@ -459,7 -458,7 +459,7 @@@ } } 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 @@@ -487,15 -486,12 +487,12 @@@ static struct origin *find_rename(struc 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)) @@@ -517,7 -513,7 +514,7 @@@ } } diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); + free_pathspec(&diff_opts.pathspec); return porigin; } @@@ -1065,7 -1061,6 +1062,6 @@@ static int find_copy_in_parent(struct s int opt) { struct diff_options diff_opts; - const char *paths[1]; int i, j; int retval; struct blame_list *blame_list; @@@ -1079,8 -1074,6 +1075,6 @@@ 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; @@@ -1163,7 -1156,7 +1157,7 @@@ } reset_scanned_flag(sb); diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); + free_pathspec(&diff_opts.pathspec); return retval; } @@@ -1938,6 -1931,18 +1932,6 @@@ static const char *add_prefix(const cha 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")) { @@@ -2234,27 -2239,38 +2228,27 @@@ static int blame_move_callback(const st 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; static const char *contents_from = NULL; static const struct option options[] = { - OPT_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), - OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), - OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), - OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")), + OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), + OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), + OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), + OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), @@@ -2271,16 -2287,13 +2265,16 @@@ OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use '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); @@@ -2473,48 -2486,22 +2467,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 --combined builtin/check-ignore.c index 25aa2a5f4c,70537c8d30..e2a1cef684 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@@ -12,18 -12,18 +12,18 @@@ static const char * const check_ignore_ 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")), OPT__VERBOSE(&verbose, N_("be verbose")), OPT_GROUP(""), - OPT_BOOLEAN(0, "stdin", &stdin_paths, - N_("read file names from stdin")), - OPT_BOOLEAN('z', NULL, &null_term_line, - N_("input paths are terminated by a null character")), - OPT_BOOLEAN('n', "non-matching", &show_non_matching, - N_("show non-matching input paths")), + OPT_BOOL(0, "stdin", &stdin_paths, + N_("read file names from stdin")), + 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_END() }; @@@ -31,7 -31,7 +31,7 @@@ static void output_exclude(const char * { 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 -64,45 +64,45 @@@ } 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 -115,7 +115,7 @@@ static int check_ignore_stdin_paths(str { 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 -128,8 +128,8 @@@ 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 -151,7 +151,7 @@@ int cmd_check_ignore(int argc, const ch 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")); @@@ -166,7 -175,7 +175,7 @@@ 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 --combined builtin/checkout.c index ed39cecf9a,7ea1100338..62a96a7e2f --- a/builtin/checkout.c +++ b/builtin/checkout.c @@@ -46,7 -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 +83,9 @@@ static int update_some(const unsigned c 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 @@@ -97,7 -94,7 +94,7 @@@ return 0; } -static int skip_same_name(struct cache_entry *ce, int pos) +static int skip_same_name(const struct cache_entry *ce, int pos) { while (++pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) @@@ -105,7 -102,7 +102,7 @@@ return pos; } -static int check_stage(int stage, struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@@ -119,7 -116,7 +116,7 @@@ return error(_("path '%s' does not have their version"), ce->name); } -static int check_stages(unsigned stages, struct cache_entry *ce, int pos) +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) { unsigned seen = 0; const char *name = ce->name; @@@ -228,6 -225,8 +225,6 @@@ static int checkout_paths(const struct int flag; struct commit *head; int errs = 0; - int stage = opts->writeout_stage; - int merge = opts->merge; int newfd; struct lock_file *lock_file; @@@ -255,20 -254,18 +252,18 @@@ 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 @@@ -302,12 -299,12 +297,12 @@@ * 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; } @@@ -319,14 -316,14 +314,14 @@@ /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; if (opts->force) { warning(_("path '%s' is unmerged"), ce->name); - } else if (stage) { - errs |= check_stage(stage, ce, pos); + } else if (opts->writeout_stage) { + errs |= check_stage(opts->writeout_stage, ce, pos); } else if (opts->merge) { errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { @@@ -350,9 -347,9 +345,9 @@@ 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; } @@@ -1000,7 -997,7 +995,7 @@@ static int switch_unborn_to_new_branch( 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) @@@ -1054,8 -1051,8 +1049,8 @@@ int cmd_checkout(int argc, const char * N_("create and checkout a new branch")), OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"), N_("create/reset and checkout a branch")), - OPT_BOOLEAN('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), - OPT_BOOLEAN(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")), + OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), + OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")), OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new branch"), N_("new unparented branch")), @@@ -1064,15 -1061,16 +1059,15 @@@ OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"), 3), OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")), - OPT_BOOLEAN('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")), - OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")), + OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")), + OPT_BOOL(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")), OPT_STRING(0, "conflict", &conflict_style, N_("style"), N_("conflict style (merge or diff3)")), - OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")), + OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), - { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, - N_("second guess 'git checkout no-such-branch'"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, + N_("second guess 'git checkout no-such-branch'")), OPT_END(), }; @@@ -1151,9 -1149,18 +1146,18 @@@ } if (argc) { - opts.pathspec = get_pathspec(prefix, argv); + /* + * In patch mode (opts.patch_mode != 0), we pass the + * pathspec to an external program, git-add--interactive. + * Do not accept any kind of magic that that program + * cannot handle. Magic mask is pretty safe to be + * lifted for new magic when opts.patch_mode == 0. + */ + 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")); /* @@@ -1185,7 -1192,7 +1189,7 @@@ 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 --combined builtin/clean.c index 4b6fd42be7,d540ca4a0a..615cd57caf --- a/builtin/clean.c +++ b/builtin/clean.c @@@ -13,16 -13,12 +13,17 @@@ #include "refs.h" #include "string-list.h" #include "quote.h" +#include "column.h" +#include "color.h" + #include "pathspec.h" static int force = -1; /* unset */ +static int interactive; +static struct string_list del_list = STRING_LIST_INIT_DUP; +static unsigned int colopts; static const char *const builtin_clean_usage[] = { - N_("git clean [-d] [-f] [-n] [-q] [-e ] [-x | -X] [--] ..."), + N_("git clean [-d] [-f] [-i] [-n] [-q] [-e ] [-x | -X] [--] ..."), NULL }; @@@ -32,112 -28,11 +33,112 @@@ static const char *msg_skip_git_dir = N static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); +static int clean_use_color = -1; +static char clean_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_BOLD_BLUE, /* PROMPT */ + GIT_COLOR_BOLD, /* HEADER */ + GIT_COLOR_BOLD_RED, /* HELP */ + GIT_COLOR_BOLD_RED, /* ERROR */ +}; +enum color_clean { + CLEAN_COLOR_RESET = 0, + CLEAN_COLOR_PLAIN = 1, + CLEAN_COLOR_PROMPT = 2, + CLEAN_COLOR_HEADER = 3, + CLEAN_COLOR_HELP = 4, + CLEAN_COLOR_ERROR = 5, +}; + +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { + const char *header; + const char *prompt; + int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { + char hotkey; + const char *title; + int selected; + int (*fn)(); +}; + +enum menu_stuff_type { + MENU_STUFF_TYPE_STRING_LIST = 1, + MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { + enum menu_stuff_type type; + int nr; + void *stuff; +}; + +static int parse_clean_color_slot(const char *var) +{ + if (!strcasecmp(var, "reset")) + return CLEAN_COLOR_RESET; + if (!strcasecmp(var, "plain")) + return CLEAN_COLOR_PLAIN; + if (!strcasecmp(var, "prompt")) + return CLEAN_COLOR_PROMPT; + if (!strcasecmp(var, "header")) + return CLEAN_COLOR_HEADER; + if (!strcasecmp(var, "help")) + return CLEAN_COLOR_HELP; + if (!strcasecmp(var, "error")) + return CLEAN_COLOR_ERROR; + return -1; +} + static int git_clean_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "clean.requireforce")) + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "clean", &colopts); + + /* honors the color.interactive* config variables which also + applied in git-add--interactive and git-stash */ + if (!strcmp(var, "color.interactive")) { + clean_use_color = git_config_colorbool(var, value); + return 0; + } + if (!prefixcmp(var, "color.interactive.")) { + int slot = parse_clean_color_slot(var + + strlen("color.interactive.")); + if (slot < 0) + return 0; + if (!value) + return config_error_nonbool(var); + color_parse(value, var, clean_colors[slot]); + return 0; + } + + if (!strcmp(var, "clean.requireforce")) { force = !git_config_bool(var, value); - return git_default_config(var, value, cb); + return 0; + } + + /* inspect the color.ui config variable and others */ + return git_color_default_config(var, value, cb); +} + +static const char *clean_get_color(enum color_clean ix) +{ + if (want_color(clean_use_color)) + return clean_colors[ix]; + return ""; +} + +static void clean_print_color(enum color_clean ix) +{ + printf("%s", clean_get_color(ix)); } static int exclude_cb(const struct option *opt, const char *arg, int unset) @@@ -162,7 -57,7 +163,7 @@@ static int remove_dirs(struct strbuf *p if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { if (!quiet) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), quoted.buf); } @@@ -176,7 -71,7 +177,7 @@@ /* an empty dir could be removed even if it is unreadble */ res = dry_run ? 0 : rmdir(path->buf); if (res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } @@@ -200,7 -95,7 +201,7 @@@ if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; if (gone) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else *dir_gone = 0; @@@ -208,10 -103,10 +209,10 @@@ } else { res = dry_run ? 0 : unlink(path->buf); if (!res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@@ -233,7 -128,7 +234,7 @@@ if (!res) *dir_gone = 1; else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@@ -248,639 -143,29 +249,638 @@@ return ret; } +static void pretty_print_dels(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + const char *qname; + struct column_options copts; + + for_each_string_list_item(item, &del_list) { + qname = quote_path_relative(item->string, NULL, &buf); + string_list_append(&list, qname); + } + + /* + * always enable column display, we only consult column.* + * about layout strategy and stuff + */ + colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(&list, colopts, &copts); + strbuf_release(&buf); + string_list_clear(&list, 0); +} + +static void pretty_print_menus(struct string_list *menu_list) +{ + unsigned int local_colopts = 0; + struct column_options copts; + + local_colopts = COL_ENABLED | COL_ROW; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd(int singleton) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(singleton ? + _("Prompt help:\n" + "1 - select a numbered item\n" + "foo - select item based on unique prefix\n" + " - (empty) select nothing") : + _("Prompt help:\n" + "1 - select a single item\n" + "3-5 - select a range of items\n" + "2-3,6-9 - select multiple ranges\n" + "foo - select item based on unique prefix\n" + "-... - unselect specified items\n" + "* - choose all items\n" + " - (empty) finish selecting")); + clean_print_color(CLEAN_COLOR_RESET); +} + +/* + * display menu stuff with number prefix and hotkey highlight + */ +static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) +{ + struct string_list menu_list = STRING_LIST_INIT_DUP; + struct strbuf menu = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int i; + + switch (stuff->type) { + default: + die("Bad type of menu_staff when print menu"); + case MENU_STUFF_TYPE_MENU_ITEM: + menu_item = (struct menu_item *)stuff->stuff; + for (i = 0; i < stuff->nr; i++, menu_item++) { + const char *p; + int highlighted = 0; + + p = menu_item->title; + if ((*chosen)[i] < 0) + (*chosen)[i] = menu_item->selected ? 1 : 0; + strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1); + for (; *p; p++) { + if (!highlighted && *p == menu_item->hotkey) { + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT)); + strbuf_addch(&menu, *p); + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET)); + highlighted = 1; + } else { + strbuf_addch(&menu, *p); + } + } + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + i = 0; + for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) { + if ((*chosen)[i] < 0) + (*chosen)[i] = 0; + strbuf_addf(&menu, "%s%2d: %s", + (*chosen)[i] ? "*" : " ", i+1, string_list_item->string); + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + i++; + } + break; + } + + pretty_print_menus(&menu_list); + + strbuf_release(&menu); + strbuf_release(&buf); + 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). + * + * Input + * (for single choice) + * 1 - select a numbered item + * foo - select item based on menu title + * - (empty) select nothing + * + * (for multiple choice) + * 1 - select a single item + * 3-5 - select a range of items + * 2-3,6-9 - select multiple ranges + * foo - select item based on menu title + * -... - unselect specified items + * * - choose all items + * - (empty) finish selecting + * + * The parse result will be saved in array **chosen, and + * return number of total selections. + */ +static int parse_choice(struct menu_stuff *menu_stuff, + int is_single, + struct strbuf input, + int **chosen) +{ + struct strbuf **choice_list, **ptr; + int nr = 0; + int i; + + if (is_single) { + choice_list = strbuf_split_max(&input, '\n', 0); + } else { + char *p = input.buf; + do { + if (*p == ',') + *p = ' '; + } while (*p++); + choice_list = strbuf_split_max(&input, ' ', 0); + } + + for (ptr = choice_list; *ptr; ptr++) { + char *p; + int choose = 1; + int bottom = 0, top = 0; + int is_range, is_number; + + strbuf_trim(*ptr); + if (!(*ptr)->len) + continue; + + /* Input that begins with '-'; unchoose */ + if (*(*ptr)->buf == '-') { + choose = 0; + strbuf_remove((*ptr), 0, 1); + } + + is_range = 0; + is_number = 1; + for (p = (*ptr)->buf; *p; p++) { + if ('-' == *p) { + if (!is_range) { + is_range = 1; + is_number = 0; + } else { + is_number = 0; + is_range = 0; + break; + } + } else if (!isdigit(*p)) { + is_number = 0; + is_range = 0; + break; + } + } + + if (is_number) { + bottom = atoi((*ptr)->buf); + top = bottom; + } else if (is_range) { + bottom = atoi((*ptr)->buf); + /* a range can be specified like 5-7 or 5- */ + if (!*(strchr((*ptr)->buf, '-') + 1)) + top = menu_stuff->nr; + else + top = atoi(strchr((*ptr)->buf, '-') + 1); + } else if (!strcmp((*ptr)->buf, "*")) { + bottom = 1; + top = menu_stuff->nr; + } else { + bottom = find_unique((*ptr)->buf, menu_stuff); + top = bottom; + } + + if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("Huh (%s)?"), (*ptr)->buf); + clean_print_color(CLEAN_COLOR_RESET); + continue; + } + + for (i = bottom; i <= top; i++) + (*chosen)[i-1] = choose; + } + + strbuf_list_free(choice_list); + + for (i = 0; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; + return nr; +} + +/* + * Implement a git-add-interactive compatible UI, which is borrowed + * from git-add--interactive.perl. + * + * Return value: + * + * - Return an array of integers + * - , and it is up to you to free the allocated memory. + * - The array ends with EOF. + * - If user pressed CTRL-D (i.e. EOF), no selection returned. + */ +static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) +{ + struct strbuf choice = STRBUF_INIT; + int *chosen, *result; + int nr = 0; + int eof = 0; + int i; + + chosen = xmalloc(sizeof(int) * stuff->nr); + /* set chosen as uninitialized */ + for (i = 0; i < stuff->nr; i++) + chosen[i] = -1; + + for (;;) { + if (opts->header) { + printf_ln("%s%s%s", + clean_get_color(CLEAN_COLOR_HEADER), + _(opts->header), + clean_get_color(CLEAN_COLOR_RESET)); + } + + /* chosen will be initialized by print_highlight_menu_stuff */ + print_highlight_menu_stuff(stuff, &chosen); + + if (opts->flags & MENU_OPTS_LIST_ONLY) + break; + + if (opts->prompt) { + printf("%s%s%s%s", + clean_get_color(CLEAN_COLOR_PROMPT), + _(opts->prompt), + opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ", + clean_get_color(CLEAN_COLOR_RESET)); + } + + if (strbuf_getline(&choice, stdin, '\n') != EOF) { + strbuf_trim(&choice); + } else { + eof = 1; + break; + } + + /* help for prompt */ + if (!strcmp(choice.buf, "?")) { + prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON); + continue; + } + + /* for a multiple-choice menu, press ENTER (empty) will return back */ + if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len) + break; + + nr = parse_choice(stuff, + opts->flags & MENU_OPTS_SINGLETON, + choice, + &chosen); + + if (opts->flags & MENU_OPTS_SINGLETON) { + if (nr) + break; + } else if (opts->flags & MENU_OPTS_IMMEDIATE) { + break; + } + } + + if (eof) { + result = xmalloc(sizeof(int)); + *result = EOF; + } else { + int j = 0; + + /* + * recalculate nr, if return back from menu directly with + * default selections. + */ + if (!nr) { + for (i = 0; i < stuff->nr; i++) + nr += chosen[i]; + } + + result = xmalloc(sizeof(int) * (nr + 1)); + memset(result, 0, sizeof(int) * (nr + 1)); + for (i = 0; i < stuff->nr && j < nr; i++) { + if (chosen[i]) + result[j++] = i; + } + result[j] = EOF; + } + + free(chosen); + strbuf_release(&choice); + return result; +} + +static int clean_cmd(void) +{ + return MENU_RETURN_NO_LOOP; +} + +static int filter_by_patterns_cmd(void) +{ + struct dir_struct dir; + struct strbuf confirm = STRBUF_INIT; + struct strbuf **ignore_list; + struct string_list_item *item; + struct exclude_list *el; + int changed = -1, i; + + for (;;) { + if (!del_list.nr) + break; + + if (changed) + pretty_print_dels(); + + clean_print_color(CLEAN_COLOR_PROMPT); + printf(_("Input ignore patterns>> ")); + clean_print_color(CLEAN_COLOR_RESET); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) + strbuf_trim(&confirm); + else + putchar('\n'); + + /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ + if (!confirm.len) + break; + + memset(&dir, 0, sizeof(dir)); + el = add_exclude_list(&dir, EXC_CMDL, "manual exclude"); + ignore_list = strbuf_split_max(&confirm, ' ', 0); + + for (i = 0; ignore_list[i]; i++) { + strbuf_trim(ignore_list[i]); + if (!ignore_list[i]->len) + continue; + + add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1)); + } + + changed = 0; + for_each_string_list_item(item, &del_list) { + int dtype = DT_UNKNOWN; + + if (is_excluded(&dir, item->string, &dtype)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) { + string_list_remove_empty_items(&del_list, 0); + } else { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf); + clean_print_color(CLEAN_COLOR_RESET); + } + + strbuf_list_free(ignore_list); + clear_directory(&dir); + } + + strbuf_release(&confirm); + return 0; +} + +static int select_by_numbers_cmd(void) +{ + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct string_list_item *items; + int *chosen; + int i, j; + + menu_opts.header = NULL; + menu_opts.prompt = N_("Select items to delete"); + menu_opts.flags = 0; + + menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen = list_and_choose(&menu_opts, &menu_stuff); + items = del_list.items; + for (i = 0, j = 0; i < del_list.nr; i++) { + if (i < chosen[j]) { + *(items[i].string) = '\0'; + } else if (i == chosen[j]) { + /* delete selected item */ + j++; + continue; + } else { + /* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) = '\0'; + } + } + + string_list_remove_empty_items(&del_list, 0); + + free(chosen); + return 0; +} + +static int ask_each_cmd(void) +{ + struct strbuf confirm = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct string_list_item *item; + const char *qname; + int changed = 0, eof = 0; + + for_each_string_list_item(item, &del_list) { + /* Ctrl-D should stop removing files */ + if (!eof) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(_("remove %s? "), qname); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) { + strbuf_trim(&confirm); + } else { + putchar('\n'); + eof = 1; + } + } + if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) + string_list_remove_empty_items(&del_list, 0); + + strbuf_release(&buf); + strbuf_release(&confirm); + return MENU_RETURN_NO_LOOP; +} + +static int quit_cmd(void) +{ + string_list_clear(&del_list, 0); + printf_ln(_("Bye.")); + return MENU_RETURN_NO_LOOP; +} + +static int help_cmd(void) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(_( + "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" + "ask each - confirm each deletion (like \"rm -i\")\n" + "quit - stop cleaning\n" + "help - this screen\n" + "? - help for prompt selection" + )); + clean_print_color(CLEAN_COLOR_RESET); + return 0; +} + +static void interactive_main_loop(void) +{ + while (del_list.nr) { + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct menu_item menus[] = { + {'c', "clean", 0, clean_cmd}, + {'f', "filter by pattern", 0, filter_by_patterns_cmd}, + {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, + {'q', "quit", 0, quit_cmd}, + {'h', "help", 0, help_cmd}, + }; + int *chosen; + + menu_opts.header = N_("*** Commands ***"); + menu_opts.prompt = N_("What now"); + menu_opts.flags = MENU_OPTS_SINGLETON; + + menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM; + menu_stuff.stuff = menus; + menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item); + + clean_print_color(CLEAN_COLOR_HEADER); + printf_ln(Q_("Would remove the following item:", + "Would remove the following items:", + del_list.nr)); + clean_print_color(CLEAN_COLOR_RESET); + + pretty_print_dels(); + + chosen = list_and_choose(&menu_opts, &menu_stuff); + + if (*chosen != EOF) { + int ret; + ret = menus[*chosen].fn(); + if (ret != MENU_RETURN_NO_LOOP) { + free(chosen); + chosen = NULL; + if (!del_list.nr) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("No more files to clean, exiting.")); + clean_print_color(CLEAN_COLOR_RESET); + break; + } + continue; + } + } else { + quit_cmd(); + } + + free(chosen); + chosen = NULL; + break; + } +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i, res; int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; - struct strbuf directory = STRBUF_INIT; + 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")), OPT__FORCE(&force, N_("force")), - OPT_BOOLEAN('d', NULL, &remove_directories, + OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), + OPT_BOOL('d', NULL, &remove_directories, N_("remove whole directories")), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), N_("add to ignore rules"), PARSE_OPT_NONEG, exclude_cb }, - OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")), - OPT_BOOLEAN('X', NULL, &ignored_only, + OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")), + OPT_BOOL('X', NULL, &ignored_only, N_("remove only ignored files")), OPT_END() }; @@@ -901,12 -186,12 +901,12 @@@ if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!dry_run && !force) { + if (!interactive && !dry_run && !force) { if (config_set) - die(_("clean.requireForce set to true and neither -n nor -f given; " + die(_("clean.requireForce set to true and neither -i, -n nor -f given; " "refusing to clean")); else - die(_("clean.requireForce defaults to true and neither -n nor -f given; " + die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; " "refusing to clean")); } @@@ -925,20 -210,18 +925,19 @@@ 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]; int len, pos; int matches = 0; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; + const char *rel; /* * Remove the '/' at the end that directory @@@ -958,72 -241,45 +957,69 @@@ continue; /* Yup, this one exists unmerged */ } - /* - * we might have removed this as part of earlier - * recursive directory removal, so lstat() here could - * fail with ENOENT. - */ if (lstat(ent->name, &st)) - continue; + 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)) { - strbuf_addstr(&directory, ent->name); if (remove_directories || (matches == MATCHED_EXACTLY)) { - if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; - if (gone && !quiet) { - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); - } + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); } - strbuf_reset(&directory); } else { - if (pathspec && !matches) + if (pathspec.nr && !matches) continue; - res = dry_run ? 0 : unlink(ent->name); + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); + } + } + + if (interactive && del_list.nr > 0) + interactive_main_loop(); + + for_each_string_list_item(item, &del_list) { + struct stat st; + + if (prefix) + strbuf_addstr(&abs_path, prefix); + + strbuf_addstr(&abs_path, item->string); + + /* + * we might have removed this as part of earlier + * recursive directory removal, so lstat() here could + * fail with ENOENT. + */ + if (lstat(abs_path.buf, &st)) + continue; + + if (S_ISDIR(st.st_mode)) { + if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; + if (gone && !quiet) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); + } + } else { + res = dry_run ? 0 : unlink(abs_path.buf); if (res) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); warning(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } + strbuf_reset(&abs_path); } - free(seen); - strbuf_release(&directory); + strbuf_release(&abs_path); + strbuf_release(&buf); + string_list_clear(&del_list, 0); string_list_clear(&exclude_list, 0); return (errors != 0); } diff --combined builtin/commit.c index 60812b5b4b,4ee9ba6c63..80d886ab3a --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -63,18 -63,8 +63,18 @@@ N_("The previous cherry-pick is now emp "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" -"Otherwise, please use 'git reset'\n"); +" git reset\n" +"\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"; @@@ -117,7 -107,6 +117,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; @@@ -152,11 -141,8 +152,11 @@@ static void determine_whence(struct wt_ { 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) @@@ -202,17 -188,15 +202,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); @@@ -221,12 -205,12 +219,12 @@@ } for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) continue; - if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) + 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)) @@@ -298,17 -282,17 +296,17 @@@ static char *prepare_index(int argc, co { 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) { @@@ -350,9 -334,9 +348,9 @@@ * (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) || @@@ -371,7 -355,7 +369,7 @@@ * 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) { @@@ -416,7 -400,7 +414,7 @@@ 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(); @@@ -548,6 -532,7 +546,6 @@@ static void determine_author_info(struc (lb - strlen(" ") - (a + strlen("\nauthor ")))); email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); - date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); len = eol - (rb + strlen("> ")); date = xmalloc(len + 2); *date = '@'; @@@ -824,13 -809,8 +822,13 @@@ static int prepare_to_commit(const cha 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; } @@@ -1091,7 -1071,7 +1089,7 @@@ static int parse_and_validate_options(i 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.")); @@@ -1228,14 -1208,14 +1226,14 @@@ int cmd_status(int argc, const char **a OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), - OPT_BOOLEAN('z', "null", &s.null_termination, - N_("terminate entries with NUL")), + OPT_BOOL('z', "null", &s.null_termination, + N_("terminate entries with NUL")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "ignored", &show_ignored_in_status, - N_("show ignored files")), + OPT_BOOL(0, "ignored", &show_ignored_in_status, + N_("show ignored files")), { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"), N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, @@@ -1259,11 -1239,12 +1257,12 @@@ 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) @@@ -1434,24 -1415,24 +1433,24 @@@ int cmd_commit(int argc, const char **a OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), + OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), - OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")), + OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, /* end commit message options */ OPT_GROUP(N_("Commit contents options")), - OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")), - OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")), - OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")), - OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")), - OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")), - OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), - OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")), + OPT_BOOL('a', "all", &all, N_("commit all changed files")), + OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")), + OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), + OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), + OPT_BOOL('o', "only", &only, N_("commit only specified files")), + OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), + OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), @@@ -1460,17 -1441,19 +1459,17 @@@ OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), - OPT_BOOLEAN('z', "null", &s.null_termination, - N_("terminate entries with NUL")), - OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")), - OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), + OPT_BOOL('z', "null", &s.null_termination, + N_("terminate entries with NUL")), + OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), + OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */ - { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, - N_("ok to record an empty change"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, - N_("ok to record a change with an empty message"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, + N_("ok to record an empty change")), + OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message, + N_("ok to record a change with an empty message")), OPT_END() }; diff --combined builtin/grep.c index 7877e7755c,76a6a60906..03bc442e3f --- a/builtin/grep.c +++ b/builtin/grep.c @@@ -17,6 -17,7 +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] [...] [[--] ...]"), @@@ -286,7 -287,8 +287,7 @@@ static int grep_sha1(struct grep_opt *o struct strbuf pathbuf = STRBUF_INIT; if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, -1, &pathbuf, - opt->prefix); + quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); } else { strbuf_addstr(&pathbuf, filename); @@@ -317,7 -319,7 +318,7 @@@ static int grep_file(struct grep_opt *o struct strbuf buf = STRBUF_INIT; if (opt->relative && opt->prefix_length) - quote_path_relative(filename, -1, &buf, opt->prefix); + quote_path_relative(filename, opt->prefix, &buf); else strbuf_addstr(&buf, filename); @@@ -375,7 -377,7 +376,7 @@@ static int grep_cache(struct grep_opt * read_cache(); for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; + const struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) @@@ -521,7 -523,7 +522,7 @@@ static int grep_directory(struct grep_o 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 -631,6 +630,6 @@@ int cmd_grep(int argc, const char **arg 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; @@@ -638,20 -639,20 +638,20 @@@ int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED; struct option options[] = { - OPT_BOOLEAN(0, "cached", &cached, + OPT_BOOL(0, "cached", &cached, N_("search in index instead of in the work tree")), OPT_NEGBIT(0, "no-index", &use_index, N_("find in contents not managed by git"), 1), - OPT_BOOLEAN(0, "untracked", &untracked, + OPT_BOOL(0, "untracked", &untracked, N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, N_("search also in ignored files"), 1), OPT_GROUP(""), - OPT_BOOLEAN('v', "invert-match", &opt.invert, + OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), - OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case, + OPT_BOOL('i', "ignore-case", &opt.ignore_case, N_("case insensitive matching")), - OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp, + OPT_BOOL('w', "word-regexp", &opt.word_regexp, N_("match patterns only at word boundaries")), OPT_SET_INT('a', "text", &opt.binary, N_("process binary files as text"), GREP_BINARY_TEXT), @@@ -675,26 -676,26 +675,26 @@@ N_("use Perl-compatible regular expressions"), GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), - OPT_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")), + OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")), OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1), OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1), OPT_NEGBIT(0, "full-name", &opt.relative, N_("show filenames relative to top directory"), 1), - OPT_BOOLEAN('l', "files-with-matches", &opt.name_only, + OPT_BOOL('l', "files-with-matches", &opt.name_only, N_("show only filenames instead of matching lines")), - OPT_BOOLEAN(0, "name-only", &opt.name_only, + OPT_BOOL(0, "name-only", &opt.name_only, N_("synonym for --files-with-matches")), - OPT_BOOLEAN('L', "files-without-match", + OPT_BOOL('L', "files-without-match", &opt.unmatch_name_only, N_("show only the names of files without match")), - OPT_BOOLEAN('z', "null", &opt.null_following_name, + OPT_BOOL('z', "null", &opt.null_following_name, N_("print NUL after filenames")), - OPT_BOOLEAN('c', "count", &opt.count, + OPT_BOOL('c', "count", &opt.count, N_("show the number of matches instead of matching lines")), OPT__COLOR(&opt.color, N_("highlight matches")), - OPT_BOOLEAN(0, "break", &opt.file_break, + OPT_BOOL(0, "break", &opt.file_break, N_("print empty line between matches from different files")), - OPT_BOOLEAN(0, "heading", &opt.heading, + OPT_BOOL(0, "heading", &opt.heading, N_("show filename only once above matches from same file")), OPT_GROUP(""), OPT_CALLBACK('C', "context", &opt, N_("n"), @@@ -706,9 -707,9 +706,9 @@@ N_("show context lines after matches")), OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"), context_callback), - OPT_BOOLEAN('p', "show-function", &opt.funcname, + OPT_BOOL('p', "show-function", &opt.funcname, N_("show a line with the function name before matches")), - OPT_BOOLEAN('W', "function-context", &opt.funcbody, + OPT_BOOL('W', "function-context", &opt.funcbody, N_("show the surrounding function")), OPT_GROUP(""), OPT_CALLBACK('f', NULL, &opt, N_("file"), @@@ -718,7 -719,7 +718,7 @@@ { OPTION_CALLBACK, 0, "and", &opt, NULL, N_("combine patterns specified with -e"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, - OPT_BOOLEAN(0, "or", &dummy, ""), + OPT_BOOL(0, "or", &dummy, ""), { OPTION_CALLBACK, 0, "not", &opt, NULL, "", PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", @@@ -729,7 -730,7 +729,7 @@@ close_callback }, OPT__QUIET(&opt.status_only, N_("indicate hit with exit status without output")), - OPT_BOOLEAN(0, "all-match", &opt.all_match, + OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), { OPTION_SET_INT, 0, "debug", &opt.debug, NULL, N_("show parse tree for grep expression"), @@@ -738,8 -739,8 +738,8 @@@ { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, - N_("allow calling of grep(1) (ignored by this build)")), + OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored, + N_("allow calling of grep(1) (ignored by this build)")), { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() @@@ -856,8 -857,10 +856,10 @@@ 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 --combined builtin/log.c index ed4dec406e,873af69476..77d0f5f3fd --- a/builtin/log.c +++ b/builtin/log.c @@@ -121,7 -121,7 +121,7 @@@ static void cmd_log_init_finish(int arg 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"), @@@ -503,7 -503,7 +503,7 @@@ int cmd_show(int argc, const char **arg 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; @@@ -1112,21 -1112,6 +1112,21 @@@ static int cc_callback(const struct opt return 0; } +static int from_callback(const struct option *opt, const char *arg, int unset) +{ + char **from = opt->value; + + free(*from); + + if (unset) + *from = NULL; + else if (arg) + *from = xstrdup(arg); + else + *from = xstrdup(git_committer_info(IDENT_NO_DATE)); + return 0; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@@ -1149,7 -1134,6 +1149,7 @@@ int quiet = 0; int reroll_count = -1; char *branch_name = NULL; + char *from = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), @@@ -1179,13 -1163,13 +1179,13 @@@ { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, N_("don't strip/add [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, - OPT_BOOLEAN(0, "no-binary", &no_binary_diff, - N_("don't output binary diffs")), - OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, - N_("don't include a patch matching a commit upstream")), - { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL, + OPT_BOOL(0, "no-binary", &no_binary_diff, + N_("don't output binary diffs")), + OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream, + N_("don't include a patch matching a commit upstream")), + { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL, N_("show patch format instead of default (patch + stat)"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG }, + PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1}, OPT_GROUP(N_("Messaging")), { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"), N_("add email header"), 0, header_callback }, @@@ -1193,9 -1177,6 +1193,9 @@@ 0, to_callback }, { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), 0, cc_callback }, + { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + N_("set From address to (or committer ident if absent)"), + PARSE_OPT_OPTARG, from_callback }, OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), N_("make first mail a reply to ")), { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), @@@ -1210,7 -1191,8 +1210,7 @@@ 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() }; @@@ -1282,11 -1264,6 +1282,11 @@@ rev.extra_headers = strbuf_detach(&buf, NULL); + if (from) { + if (split_ident_line(&rev.from_ident, from, strlen(from))) + die(_("invalid ident line: %s"), from); + } + if (start_number < 0) start_number = 1; diff --combined builtin/ls-files.c index 963ccc9742,d3a0495f7d..d4823c9d38 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@@ -13,6 -13,7 +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 +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; @@@ -46,14 -47,10 +47,14 @@@ static const char *tag_modified = "" static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_name(const char* name, size_t len) +static void write_name(const char *name) { - write_name_quoted_relative(name, len, prefix, prefix_len, stdout, - line_terminator); + /* + * With "--full-name", prefix_len=0; this caller needs to pass + * an empty string in that case (a NULL is good for ""). + */ + write_name_quoted_relative(name, prefix_len ? prefix : NULL, + stdout, line_terminator); } static void show_dir_entry(const char *tag, struct dir_entry *ent) @@@ -63,11 -60,11 +64,11 @@@ 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); - write_name(ent->name, ent->len); + write_name(ent->name); } static void show_other_files(struct dir_struct *dir) @@@ -131,14 -128,14 +132,14 @@@ static void show_killed_files(struct di } } -static void show_ce_entry(const char *tag, struct cache_entry *ce) +static void show_ce_entry(const char *tag, const struct cache_entry *ce) { int len = max_prefix_len; 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 && @@@ -167,9 -164,9 +168,9 @@@ find_unique_abbrev(ce->sha1,abbrev), ce_stage(ce)); } - write_name(ce->name, ce_namelen(ce)); + write_name(ce->name); if (debug_mode) { - struct stat_data *sd = &ce->ce_stat_data; + const struct stat_data *sd = &ce->ce_stat_data; printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); @@@ -194,7 -191,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]) @@@ -202,12 -199,12 +203,12 @@@ printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name(path, len); + write_name(path); } } } -static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); return is_excluded(dir, ce->name, &dtype); @@@ -219,7 -216,7 +220,7 @@@ static void show_files(struct dir_struc /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { - fill_directory(dir, pathspec); + fill_directory(dir, &pathspec); if (show_others) show_other_files(dir); if (show_killed) @@@ -227,7 -224,7 +228,7 @@@ } if (show_cached || show_stage) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if ((dir->flags & DIR_SHOW_IGNORED) && !ce_excluded(dir, ce)) continue; @@@ -241,7 -238,7 +242,7 @@@ } if (show_deleted || show_modified) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct stat st; int err; if ((dir->flags & DIR_SHOW_IGNORED) && @@@ -277,7 -274,7 +278,7 @@@ static void prune_cache(const char *pre last = active_nr; while (last > first) { int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = active_cache[next]; if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; @@@ -287,21 -284,6 +288,6 @@@ 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 -315,12 +319,12 @@@ void overlay_tree_on_cache(const char * } 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 -345,16 +349,16 @@@ } } - 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 -362,16 +366,16 @@@ /* * 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 -380,8 +384,8 @@@ 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); @@@ -461,24 -445,24 +449,24 @@@ int cmd_ls_files(int argc, const char * { OPTION_CALLBACK, 'z', NULL, NULL, NULL, N_("paths are separated with NUL character"), PARSE_OPT_NOARG, option_parse_z }, - OPT_BOOLEAN('t', NULL, &show_tag, + OPT_BOOL('t', NULL, &show_tag, N_("identify the file status with tags")), - OPT_BOOLEAN('v', NULL, &show_valid_bit, + OPT_BOOL('v', NULL, &show_valid_bit, N_("use lowercase letters for 'assume unchanged' files")), - OPT_BOOLEAN('c', "cached", &show_cached, + OPT_BOOL('c', "cached", &show_cached, N_("show cached files in the output (default)")), - OPT_BOOLEAN('d', "deleted", &show_deleted, + OPT_BOOL('d', "deleted", &show_deleted, N_("show deleted files in the output")), - OPT_BOOLEAN('m', "modified", &show_modified, + OPT_BOOL('m', "modified", &show_modified, N_("show modified files in the output")), - OPT_BOOLEAN('o', "others", &show_others, + OPT_BOOL('o', "others", &show_others, N_("show other files in the output")), OPT_BIT('i', "ignored", &dir.flags, N_("show ignored files in the output"), DIR_SHOW_IGNORED), - OPT_BOOLEAN('s', "stage", &show_stage, + OPT_BOOL('s', "stage", &show_stage, N_("show staged contents' object name in the output")), - OPT_BOOLEAN('k', "killed", &show_killed, + OPT_BOOL('k', "killed", &show_killed, N_("show files on the filesystem that need to be removed")), OPT_BIT(0, "directory", &dir.flags, N_("show 'other' directories' name only"), @@@ -486,9 -470,9 +474,9 @@@ OPT_NEGBIT(0, "empty-directory", &dir.flags, N_("don't show empty directories"), DIR_HIDE_EMPTY_DIRECTORIES), - OPT_BOOLEAN('u', "unmerged", &show_unmerged, + OPT_BOOL('u', "unmerged", &show_unmerged, N_("show unmerged files in the output")), - OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, + OPT_BOOL(0, "resolve-undo", &show_resolve_undo, N_("show resolve-undo information")), { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"), N_("skip files matching pattern"), @@@ -504,12 -488,12 +492,12 @@@ { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, N_("make the output relative to the project top directory"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, - OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, + OPT_BOOL(0, "error-unmatch", &error_unmatch, N_("if any is not in the index, treat this as an error")), OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"), N_("pretend that paths removed since are still present")), OPT__ABBREV(&abbrev), - OPT_BOOLEAN(0, "debug", &debug_mode, N_("show debugging data")), + OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), OPT_END() }; @@@ -555,23 -539,18 +543,18 @@@ 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 -577,7 +581,7 @@@ 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 --combined builtin/ls-tree.c index de88563edf,f6d8215181..65ec931846 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@@ -10,6 -10,7 +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 +36,7 @@@ static int show_recursive(const char *b if (ls_options & LS_RECURSIVE) return 1; - s = pathspec.raw; + s = pathspec._raw; if (!s) return 0; @@@ -138,9 -139,9 +139,9 @@@ int cmd_ls_tree(int argc, const char ** LS_NAME_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, N_("use full path names"), 0), - OPT_BOOLEAN(0, "full-tree", &full_tree, - N_("list entire tree; not just current directory " - "(implies --full-name)")), + OPT_BOOL(0, "full-tree", &full_tree, + N_("list entire tree; not just current directory " + "(implies --full-name)")), OPT__ABBREV(&abbrev), OPT_END() }; @@@ -166,7 -167,15 +167,15 @@@ 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 --combined builtin/mv.c index be6fa77d04,7dd6bb491c..aec79d1838 --- a/builtin/mv.c +++ b/builtin/mv.c @@@ -9,14 -9,16 +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] ... "), 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 *)); @@@ -56,20 -58,21 +58,21 @@@ static struct lock_file lock_file 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")), OPT__DRY_RUN(&show_only, N_("dry run")), OPT__FORCE(&force, N_("force move/rename even if target exists")), - OPT_BOOLEAN('k', NULL, &ignore_errors, N_("skip move/rename errors")), + 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 -84,18 +84,18 @@@ 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 -121,68 +121,68 @@@ && 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]); + 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 -227,14 +227,14 @@@ 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]) + 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 -245,9 +245,9 @@@ 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 --combined builtin/reset.c index 090581564b,86150d14f1..5e4c551531 --- a/builtin/reset.c +++ b/builtin/reset.c @@@ -133,12 -133,13 +133,13 @@@ static void update_index_from_diff(stru } } - 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; @@@ -147,7 -148,7 +148,7 @@@ return 1; diffcore_std(&opt); diff_flush(&opt); - diff_tree_release_paths(&opt); + free_pathspec(&opt.pathspec); return 0; } @@@ -174,7 -175,10 +175,10 @@@ static void die_if_unmerged_cache(int r } - 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,7 -220,10 +220,10 @@@ } } *rev_ret = rev; - return argv[0] ? get_pathspec(prefix, argv) : NULL; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), + prefix, argv); } static int update_refs(const char *rev, const unsigned char *sha1) @@@ -246,7 -253,7 +253,7 @@@ int cmd_reset(int argc, const char **ar 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, @@@ -258,7 -265,7 +265,7 @@@ N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, N_("reset HEAD but keep local changes"), KEEP), - OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")), + OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_END() }; @@@ -266,13 -273,13 +273,13 @@@ 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 -300,13 +300,13 @@@ 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(sha1_to_hex(sha1), "--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 -- ' instead.")); else if (reset_type != NONE) @@@ -326,7 -333,7 +333,7 @@@ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int newfd = hold_locked_index(lock, 1); if (reset_type == MIXED) { - if (read_from_tree(pathspec, sha1)) + if (read_from_tree(&pathspec, sha1)) return 1; } else { int err = reset_index(sha1, reset_type, quiet); @@@ -347,7 -354,7 +354,7 @@@ 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); @@@ -355,7 -362,7 +362,7 @@@ 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 --combined builtin/rm.c index 18bf218999,c848dad1d0..9b59ab3a64 --- a/builtin/rm.c +++ b/builtin/rm.c @@@ -11,6 -11,7 +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] [--] ..."), @@@ -58,21 -59,6 +59,21 @@@ static void print_error_files(struct st } } +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; @@@ -82,7 -68,7 +83,7 @@@ for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; pos = cache_name_pos(name, strlen(name)); @@@ -101,8 -87,16 +102,8 @@@ 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; } @@@ -127,7 -121,7 +128,7 @@@ static int check_local_mod(unsigned cha for (i = 0; i < list.nr; i++) { struct stat st; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; const char *name = list.entry[i].name; unsigned char sha1[20]; unsigned mode; @@@ -244,9 -238,17 +245,9 @@@ " 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:", @@@ -267,20 -269,21 +268,21 @@@ static int ignore_unmatch = 0 static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), OPT__QUIET(&quiet, N_("do not list removed files")), - OPT_BOOLEAN( 0 , "cached", &index_only, N_("only remove from the index")), + OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")), OPT__FORCE(&force, N_("override the up-to-date check")), - OPT_BOOLEAN('r', NULL, &recursive, N_("allow recursive removal")), - OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, + OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), + OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), OPT_END(), }; int cmd_rm(int argc, const char **argv, const char *prefix) { - int i, newfd, seen_any; - const char **pathspec, *match; + int i, newfd; + struct pathspec pathspec; char *seen; + gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_rm_options, @@@ -311,40 -314,45 +313,45 @@@ } } - pathspec = get_pathspec(prefix, argv); - refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); + parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv); + refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL); - for (i = 0; pathspec[i] ; i++) - /* nothing */; - seen = xcalloc(i, 1); + seen = xcalloc(pathspec.nr, 1); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[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")); } - - seen_any = 0; - for (i = 0; (match = pathspec[i]) != NULL ; i++) { - if (!seen[i]) { - if (!ignore_unmatch) { - die(_("pathspec '%s' did not match any files"), - match); + if (pathspec.nr) { + const char *original; + int seen_any = 0; + 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"), + original); + } } + else { + seen_any = 1; + } + if (!recursive && seen[i] == MATCHED_RECURSIVELY) + die(_("not removing '%s' recursively without -r"), + *original ? original : "."); } - else { - seen_any = 1; - } - if (!recursive && seen[i] == MATCHED_RECURSIVELY) - die(_("not removing '%s' recursively without -r"), - *match ? match : "."); + - if (! seen_any) ++ if (!seen_any) + exit(0); } - if (!seen_any) - exit(0); /* * If not forced, the file, the index and the HEAD (if exists) @@@ -392,13 -400,15 +399,15 @@@ * 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 { @@@ -406,9 -416,14 +415,14 @@@ 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. */ } @@@ -420,6 -435,8 +434,8 @@@ if (!removed) die_errno("git rm: '%s'", path); } + if (gitmodules_modified) + stage_updated_gitmodules(); } if (active_cache_changed) { diff --combined builtin/update-index.c index c317981516,e795818799..e3a10d706d --- a/builtin/update-index.c +++ b/builtin/update-index.c @@@ -11,6 -11,7 +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 @@@ -83,7 -84,7 +84,7 @@@ static int process_lstat_error(const ch return error("lstat(\"%s\"): %s", path, strerror(errno)); } -static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st) +static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) { int option, size; struct cache_entry *ce; @@@ -142,7 -143,7 +143,7 @@@ static int process_directory(const cha /* Exact match: file or existing gitlink */ if (pos >= 0) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ @@@ -158,7 -159,7 +159,7 @@@ /* Inexact match: is there perhaps a subdirectory match? */ pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; if (strncmp(ce->name, path, len)) break; @@@ -183,7 -184,7 +184,7 @@@ static int process_path(const char *pat { int pos, len; struct stat st; - struct cache_entry *ce; + const struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) @@@ -448,7 -449,7 +449,7 @@@ static int unresolve_one(const char *pa /* already merged */ pos = unmerge_cache_entry_at(pos); if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce) && ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) @@@ -462,7 -463,7 +463,7 @@@ */ pos = -pos-1; if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) { fprintf(stderr, @@@ -546,10 -547,11 +547,11 @@@ static int do_reupdate(int ac, const ch */ 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 @@@ -558,7 -560,7 +560,7 @@@ has_head = 0; redo: for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; int save_nr; diff --combined cache.h index 558ccb91f6,3cff825d5c..bd6fb9f664 --- a/cache.h +++ b/cache.h @@@ -189,6 -189,8 +189,8 @@@ struct cache_entry #error "CE_EXTENDED_FLAGS out of range" #endif + struct pathspec; + /* * Copy the sha1 and stat state of a cache entry from one to * another. But we never change the name, or the hash state! @@@ -365,6 -367,9 +367,9 @@@ static inline enum object_type object_t #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF" #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE" #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS" + #define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS" + #define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS" + #define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS" /* * This environment variable is expected to contain a boolean indicating @@@ -412,6 -417,7 +417,7 @@@ extern void setup_work_tree(void) extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); + extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, @@@ -425,8 -431,6 +431,8 @@@ extern int path_inside_repo(const char extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int); extern int init_db(const char *template_dir, unsigned int flags); +extern void sanitize_stdfds(void); + #define alloc_nr(x) (((x)+16)*3/2) /* @@@ -449,7 -453,7 +455,7 @@@ /* Initialize and use the cache information */ extern int read_index(struct index_state *); - extern int read_index_preload(struct index_state *, const char **pathspec); + extern int read_index_preload(struct index_state *, const struct pathspec *pathspec); extern int read_index_from(struct index_state *, const char *path); extern int is_index_unborn(struct index_state *); extern int read_index_unmerged(struct index_state *); @@@ -478,7 -482,7 +484,7 @@@ extern int remove_file_from_index(struc extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); extern int index_name_is_other(const struct index_state *, const char *, int); extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *); @@@ -491,28 -495,8 +497,8 @@@ extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int); - #define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */ - - struct pathspec { - const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ - int nr; - unsigned int has_wildcard:1; - unsigned int recursive:1; - int max_depth; - struct pathspec_item { - const char *match; - int len; - int nowildcard_len; - int flags; - } *items; - }; - - extern int init_pathspec(struct pathspec *, const char **); - extern void free_pathspec(struct pathspec *); extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); - extern int limit_pathspec_to_literal(void); - #define HASH_WRITE_OBJECT 1 #define HASH_FORMAT_CHECK 2 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); @@@ -540,7 -524,7 +526,7 @@@ extern void fill_stat_cache_info(struc #define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ #define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ - extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg); + extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg); struct lock_file { struct lock_file *next; @@@ -577,7 -561,6 +563,7 @@@ extern int assume_unchanged extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; +extern int warn_on_object_refname_ambiguity; extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; @@@ -761,7 -744,8 +747,8 @@@ int is_directory(const char *) const char *real_path(const char *path); const char *real_path_if_valid(const char *path); const char *absolute_path(const char *path); -const char *relative_path(const char *abs, const char *base); +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); + int normalize_path_copy_len(char *dst, const char *src, int *prefix_len); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); @@@ -1038,6 -1022,68 +1025,6 @@@ struct pack_entry struct packed_git *p; }; -struct ref { - struct ref *next; - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - char *symref; - unsigned int - force:1, - forced_update:1, - deletion:1, - matched:1; - - /* - * Order is important here, as we write to FETCH_HEAD - * in numeric order. And the default NOT_FOR_MERGE - * should be 0, so that xcalloc'd structures get it - * by default. - */ - enum { - FETCH_HEAD_MERGE = -1, - FETCH_HEAD_NOT_FOR_MERGE = 0, - FETCH_HEAD_IGNORE = 1 - } fetch_head_status; - - enum { - REF_STATUS_NONE = 0, - REF_STATUS_OK, - REF_STATUS_REJECT_NONFASTFORWARD, - REF_STATUS_REJECT_ALREADY_EXISTS, - REF_STATUS_REJECT_NODELETE, - REF_STATUS_REJECT_FETCH_FIRST, - REF_STATUS_REJECT_NEEDS_FORCE, - REF_STATUS_UPTODATE, - REF_STATUS_REMOTE_REJECT, - REF_STATUS_EXPECTING_REPORT - } status; - char *remote_status; - struct ref *peer_ref; /* when renaming */ - char name[FLEX_ARRAY]; /* more */ -}; - -#define REF_NORMAL (1u << 0) -#define REF_HEADS (1u << 1) -#define REF_TAGS (1u << 2) - -extern struct ref *find_ref_by_name(const struct ref *list, const char *name); - -#define CONNECT_VERBOSE (1u << 0) -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); -extern int finish_connect(struct child_process *conn); -extern int git_connection_is_socket(struct child_process *conn); -struct extra_have_objects { - int nr, alloc; - unsigned char (*array)[20]; -}; -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, - struct ref **list, unsigned int flags, - struct extra_have_objects *); -extern int server_supports(const char *feature); -extern int parse_feature_request(const char *features, const char *feature); -extern const char *server_feature_value(const char *feature, int *len_ret); -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret); - extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); /* A hook for count-objects to report invalid files in pack directory */ @@@ -1070,9 -1116,7 +1057,9 @@@ extern int unpack_object_header(struct struct object_info { /* Request */ + enum object_type *typep; unsigned long *sizep; + unsigned long *disk_sizep; /* Response */ enum { @@@ -1116,15 -1160,11 +1103,15 @@@ extern int update_server_info(int) typedef int (*config_fn_t)(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config_from_buf(config_fn_t fn, const char *name, + const char *buf, size_t len, void *data); extern void git_config_push_parameter(const char *text); extern int git_config_from_parameters(config_fn_t fn, void *data); extern int git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, - const char *filename, int respect_includes); + const char *filename, + const char *blob_ref, + int respect_includes); extern int git_config_early(config_fn_t fn, void *, const char *repo_config); extern int git_parse_ulong(const char *, unsigned long *); extern int git_config_int(const char *, const char *); @@@ -1243,7 -1283,7 +1230,7 @@@ void packet_trace_identity(const char * * return 0 if success, 1 - if addition of a file failed and * ADD_FILES_IGNORE_ERRORS was specified in flags */ - 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); /* diff.c */ extern int diff_auto_refresh_index; @@@ -1277,7 -1317,7 +1264,7 @@@ extern int ws_blank_line(const char *li #define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK) /* ls-files */ - 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); void overlay_tree_on_cache(const char *tree_name, const char *prefix); char *alias_lookup(const char *alias); diff --combined combine-diff.c index 4fc16ad4f3,c973f5d07e..3b92c44880 --- a/combine-diff.c +++ b/combine-diff.c @@@ -10,7 -10,6 +10,7 @@@ #include "refs.h" #include "userdiff.h" #include "sha1-array.h" +#include "revision.h" static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { @@@ -166,7 -165,7 +166,7 @@@ static struct lline *coalesce_lines(str /* * Coalesce new lines into base by finding the LCS - * - Create the table to run dynamic programing + * - Create the table to run dynamic programming * - Compute the LCS * - Then reverse read the direction structure: * - If we have MATCH, assign parent to base flag, and consume @@@ -1306,7 -1305,7 +1306,7 @@@ void diff_tree_combined(const unsigned int i, num_paths, needsep, show_log_first, num_parent = parents->nr; diffopts = *opt; - diff_tree_setup_paths(diffopts.pathspec.raw, &diffopts); + copy_pathspec(&diffopts.pathspec, &opt->pathspec); diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(&diffopts, RECURSIVE); DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL); @@@ -1378,13 -1377,13 +1378,13 @@@ free(tmp); } - diff_tree_release_paths(&diffopts); + free_pathspec(&diffopts.pathspec); } void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev) { - struct commit_list *parent = commit->parents; + struct commit_list *parent = get_saved_parents(rev, commit); struct sha1_array parents = SHA1_ARRAY_INIT; while (parent) { diff --combined commit.h index f9504f70cc,1f1350c404..90a5a3c361 --- a/commit.h +++ b/commit.h @@@ -6,7 -6,6 +6,7 @@@ #include "strbuf.h" #include "decorate.h" #include "gpg-interface.h" +#include "string-list.h" struct commit_list { struct commit *item; @@@ -62,9 -61,6 +62,9 @@@ struct commit_list *commit_list_insert_ struct commit_list **list); void commit_list_sort_by_date(struct commit_list **list); +/* Shallow copy of the input list */ +struct commit_list *copy_commit_list(struct commit_list *list); + void free_commit_list(struct commit_list *list); /* Commit formats */ @@@ -83,9 -79,6 +83,9 @@@ enum cmit_fmt }; struct pretty_print_context { + /* + * Callers should tweak these to change the behavior of pp_* functions. + */ enum cmit_fmt fmt; int abbrev; const char *subject; @@@ -99,13 -92,6 +99,13 @@@ const char *output_encoding; struct string_list *mailmap; int color; + struct ident_split *from_ident; + + /* + * Fields below here are manipulated internally by pp_* functions and + * should not be counted on by callers. + */ + struct string_list in_body_headers; }; struct userformat_want { @@@ -125,20 -111,20 +125,20 @@@ extern void userformat_find_requirement extern void format_commit_message(const struct commit *commit, const char *format, struct strbuf *sb, const struct pretty_print_context *context); -extern void pretty_print_commit(const struct pretty_print_context *pp, +extern void pretty_print_commit(struct pretty_print_context *pp, const struct commit *commit, struct strbuf *sb); extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, struct strbuf *sb); -void pp_user_info(const struct pretty_print_context *pp, +void pp_user_info(struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding); -void pp_title_line(const struct pretty_print_context *pp, +void pp_title_line(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, const char *encoding, int need_8bit_cte); -void pp_remainder(const struct pretty_print_context *pp, +void pp_remainder(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, int indent); @@@ -208,7 -194,7 +208,7 @@@ int in_merge_bases_many(struct commit * extern int interactive_add(int argc, const char **argv, const char *prefix, int patch); extern int run_add_interactive(const char *revision, const char *patch_mode, - const char **pathspec); + const struct pathspec *pathspec); static inline int single_parent(struct commit *commit) { @@@ -268,6 -254,4 +268,6 @@@ extern void print_commit_list(struct co */ extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc); +int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused); + #endif /* COMMIT_H */ diff --combined diff-lib.c index f1a9053168,0a95763063..346cac651d --- a/diff-lib.c +++ b/diff-lib.c @@@ -87,12 -87,10 +87,12 @@@ int run_diff_files(struct rev_info *rev { int entries, i; int diff_unmerged_stage = revs->max_count; - int silent_on_removed = option & DIFF_SILENT_ON_REMOVED; unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED) ? CE_MATCH_RACY_IS_DIRTY : 0); + if (option & DIFF_SILENT_ON_REMOVED) + handle_deprecated_show_diff_q(&revs->diffopt); + diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/"); if (diff_unmerged_stage < 0) @@@ -139,6 -137,8 +139,6 @@@ perror(ce->name); continue; } - if (silent_on_removed) - continue; wt_mode = 0; } dpath->mode = wt_mode; @@@ -204,6 -204,8 +204,6 @@@ perror(ce->name); continue; } - if (silent_on_removed) - continue; diff_addremove(&revs->diffopt, '-', ce->ce_mode, ce->sha1, !is_null_sha1(ce->sha1), ce->name, 0); @@@ -474,7 -476,6 +474,6 @@@ static int diff_cache(struct rev_info * opts.dst_index = NULL; opts.pathspec = &revs->diffopt.pathspec; opts.pathspec->recursive = 1; - opts.pathspec->max_depth = -1; init_tree_desc(&t, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); @@@ -500,7 -501,7 +499,7 @@@ int do_diff_cache(const unsigned char * struct rev_info revs; init_revisions(&revs, NULL); - init_pathspec(&revs.prune_data, opt->pathspec.raw); + copy_pathspec(&revs.prune_data, &opt->pathspec); revs.diffopt = *opt; if (diff_cache(&revs, tree_sha1, NULL, 1)) diff --combined diff.h index 5237d63a71,b8df245ad9..44092c2176 --- a/diff.h +++ b/diff.h @@@ -5,6 -5,7 +5,7 @@@ #define DIFF_H #include "tree-walk.h" + #include "pathspec.h" struct rev_info; struct diff_options; @@@ -103,15 -104,12 +104,15 @@@ enum diff_words_type }; struct diff_options { - const char *filter; const char *orderfile; const char *pickaxe; const char *single_follow; const char *a_prefix, *b_prefix; unsigned flags; + + /* diff-filter bits */ + unsigned int filter; + int use_color; int context; int interhunkcontext; @@@ -182,8 -180,6 +183,6 @@@ const char *diff_line_prefix(struct dif extern const char mime_boundary_leader[]; - extern void diff_tree_setup_paths(const char **paths, struct diff_options *); - extern void diff_tree_release_paths(struct diff_options *); extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt); extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, @@@ -341,8 -337,6 +340,8 @@@ extern int parse_rename_score(const cha extern long parse_algorithm_value(const char *value); +extern void handle_deprecated_show_diff_q(struct diff_options *); + extern int print_stat_summary(FILE *fp, int files, int insertions, int deletions); extern void setup_diff_pager(struct diff_options *); diff --combined dir.c index 910bfcde4e,8543736deb..1128110a44 --- a/dir.c +++ b/dir.c @@@ -11,6 -11,7 +11,7 @@@ #include "dir.h" #include "refs.h" #include "wildmatch.h" + #include "pathspec.h" struct path_simplify { int len; @@@ -51,26 -52,32 +52,32 @@@ int fnmatch_icase(const char *pattern, return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0)); } - inline int git_fnmatch(const char *pattern, const char *string, - int flags, int prefix) + inline int git_fnmatch(const struct pathspec_item *item, + const char *pattern, const char *string, + int prefix) { - int fnm_flags = 0; - if (flags & GFNM_PATHNAME) - fnm_flags |= FNM_PATHNAME; if (prefix > 0) { - if (strncmp(pattern, string, prefix)) + if (ps_strncmp(item, pattern, string, prefix)) return FNM_NOMATCH; pattern += prefix; string += prefix; } - if (flags & GFNM_ONESTAR) { + if (item->flags & PATHSPEC_ONESTAR) { int pattern_len = strlen(++pattern); int string_len = strlen(string); return string_len < pattern_len || - strcmp(pattern, - string + string_len - pattern_len); + ps_strcmp(item, pattern, + string + string_len - pattern_len); } - return fnmatch(pattern, string, fnm_flags); + if (item->magic & PATHSPEC_GLOB) + return wildmatch(pattern, string, + WM_PATHNAME | + (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0), + NULL); + else + /* wildmatch has not learned no FNM_PATHNAME mode yet */ + return fnmatch(pattern, string, + item->magic & PATHSPEC_ICASE ? FNM_CASEFOLD : 0); } static int fnmatch_icase_mem(const char *pattern, int patternlen, @@@ -102,26 -109,40 +109,40 @@@ return match_status; } - static size_t common_prefix_len(const char **pathspec) + static size_t common_prefix_len(const struct pathspec *pathspec) { - const char *n, *first; + int n; size_t max = 0; - int literal = limit_pathspec_to_literal(); - if (!pathspec) - return max; - - first = *pathspec; - while ((n = *pathspec++)) { - size_t i, len = 0; - for (i = 0; first == n || i < max; i++) { - char c = n[i]; - if (!c || c != first[i] || (!literal && is_glob_special(c))) + /* + * ":(icase)path" is treated as a pathspec full of + * wildcard. In other words, only prefix is considered common + * prefix. If the pathspec is abc/foo abc/bar, running in + * subdir xyz, the common prefix is still xyz, not xuz/abc as + * in non-:(icase). + */ + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE); + + for (n = 0; n < pathspec->nr; n++) { + size_t i = 0, len = 0, item_len; + if (pathspec->items[n].magic & PATHSPEC_ICASE) + item_len = pathspec->items[n].prefix; + else + item_len = pathspec->items[n].nowildcard_len; + while (i < item_len && (n == 0 || i < max)) { + char c = pathspec->items[n].match[i]; + if (c != pathspec->items[0].match[i]) break; if (c == '/') len = i + 1; + i++; } - if (first == n || len < max) { + if (n == 0 || len < max) { max = len; if (!max) break; @@@ -134,14 -155,14 +155,14 @@@ * Returns a copy of the longest leading path common among all * pathspecs. */ - char *common_prefix(const char **pathspec) + char *common_prefix(const struct pathspec *pathspec) { unsigned long len = common_prefix_len(pathspec); - return len ? xmemdupz(*pathspec, len) : NULL; + return len ? xmemdupz(pathspec->items[0].match, len) : NULL; } - int fill_directory(struct dir_struct *dir, const char **pathspec) + int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) { size_t len; @@@ -152,7 -173,7 +173,7 @@@ len = common_prefix_len(pathspec); /* Read the directory and prune it */ - read_directory(dir, pathspec ? *pathspec : "", len, pathspec); + read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec); return len; } @@@ -171,113 -192,6 +192,6 @@@ int within_depth(const char *name, int return 1; } - /* - * Does 'match' match the given name? - * A match is found if - * - * (1) the 'match' string is leading directory of 'name', or - * (2) the 'match' string is a wildcard and matches 'name', or - * (3) the 'match' string is exactly the same as 'name'. - * - * and the return value tells which case it was. - * - * It returns 0 when there is no match. - */ - static int match_one(const char *match, const char *name, int namelen) - { - int matchlen; - int literal = limit_pathspec_to_literal(); - - /* If the match was just the prefix, we matched */ - if (!*match) - return MATCHED_RECURSIVELY; - - if (ignore_case) { - for (;;) { - unsigned char c1 = tolower(*match); - unsigned char c2 = tolower(*name); - if (c1 == '\0' || (!literal && is_glob_special(c1))) - break; - if (c1 != c2) - return 0; - match++; - name++; - namelen--; - } - } else { - for (;;) { - unsigned char c1 = *match; - unsigned char c2 = *name; - if (c1 == '\0' || (!literal && is_glob_special(c1))) - break; - if (c1 != c2) - return 0; - match++; - name++; - namelen--; - } - } - - /* - * If we don't match the matchstring exactly, - * we need to match by fnmatch - */ - matchlen = strlen(match); - if (strncmp_icase(match, name, matchlen)) { - if (literal) - return 0; - return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0; - } - - if (namelen == matchlen) - return MATCHED_EXACTLY; - if (match[matchlen-1] == '/' || name[matchlen] == '/') - return MATCHED_RECURSIVELY; - return 0; - } - - /* - * Given a name and a list of pathspecs, returns the nature of the - * closest (i.e. most specific) match of the name to any of the - * pathspecs. - * - * The caller typically calls this multiple times with the same - * pathspec and seen[] array but with different name/namelen - * (e.g. entries from the index) and is interested in seeing if and - * how each pathspec matches all the names it calls this function - * with. A mark is left in the seen[] array for each pathspec element - * indicating the closest type of match that element achieved, so if - * seen[n] remains zero after multiple invocations, that means the nth - * pathspec did not match any names, which could indicate that the - * user mistyped the nth pathspec. - */ - int match_pathspec(const char **pathspec, const char *name, int namelen, - int prefix, char *seen) - { - int i, retval = 0; - - if (!pathspec) - return 1; - - name += prefix; - namelen -= prefix; - - for (i = 0; pathspec[i] != NULL; i++) { - int how; - const char *match = pathspec[i] + prefix; - if (seen && seen[i] == MATCHED_EXACTLY) - continue; - how = match_one(match, name, namelen); - if (how) { - if (retval < how) - retval = how; - if (seen && seen[i] < how) - seen[i] = how; - } - } - return retval; - } - /* * Does 'match' match the given name? * A match is found if @@@ -297,11 -211,44 +211,44 @@@ static int match_pathspec_item(const st const char *match = item->match + prefix; int matchlen = item->len - prefix; + /* + * The normal call pattern is: + * 1. prefix = common_prefix_len(ps); + * 2. prune something, or fill_directory + * 3. match_pathspec_depth() + * + * 'prefix' at #1 may be shorter than the command's prefix and + * it's ok for #2 to match extra files. Those extras will be + * trimmed at #3. + * + * Suppose the pathspec is 'foo' and '../bar' running from + * subdir 'xyz'. The common prefix at #1 will be empty, thanks + * to "../". We may have xyz/foo _and_ XYZ/foo after #2. The + * user does not want XYZ/foo, only the "foo" part should be + * case-insensitive. We need to filter out XYZ/foo here. In + * other words, we do not trust the caller on comparing the + * prefix part when :(icase) is involved. We do exact + * comparison ourselves. + * + * Normally the caller (common_prefix_len() in fact) does + * _exact_ matching on name[-prefix+1..-1] and we do not need + * to check that part. Be defensive and check it anyway, in + * case common_prefix_len is changed, or a new caller is + * introduced that does not use common_prefix_len. + * + * If the penalty turns out too high when prefix is really + * long, maybe change it to + * strncmp(match, name, item->prefix - prefix) + */ + if (item->prefix && (item->magic & PATHSPEC_ICASE) && + strncmp(item->match, name - prefix, item->prefix)) + return 0; + /* If the match was just the prefix, we matched */ if (!*match) return MATCHED_RECURSIVELY; - if (matchlen <= namelen && !strncmp(match, name, matchlen)) { + if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) { if (matchlen == namelen) return MATCHED_EXACTLY; @@@ -310,8 -257,7 +257,7 @@@ } if (item->nowildcard_len < item->len && - !git_fnmatch(match, name, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + !git_fnmatch(item, match, name, item->nowildcard_len - prefix)) return MATCHED_FNMATCH; @@@ -339,8 -285,17 +285,17 @@@ int match_pathspec_depth(const struct p { int i, retval = 0; + GUARD_PATHSPEC(ps, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE); + if (!ps->nr) { - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return MATCHED_RECURSIVELY; if (within_depth(name, namelen, 0, ps->max_depth)) @@@ -357,7 -312,9 +312,9 @@@ if (seen && seen[i] == MATCHED_EXACTLY) continue; how = match_pathspec_item(ps->items+i, prefix, name, namelen); - if (ps->recursive && ps->max_depth != -1 && + if (ps->recursive && + (ps->magic & PATHSPEC_MAXDEPTH) && + ps->max_depth != -1 && how && how != MATCHED_FNMATCH) { int len = ps->items[i].len; if (name[len] == '/') @@@ -380,7 -337,7 +337,7 @@@ /* * Return the length of the "simple" part of a path match limiter. */ - static int simple_length(const char *match) + int simple_length(const char *match) { int len = -1; @@@ -392,7 -349,7 +349,7 @@@ } } - static int no_wildcard(const char *string) + int no_wildcard(const char *string) { return string[simple_length(string)] == '\0'; } @@@ -933,7 -890,7 +890,7 @@@ enum exist_status */ static enum exist_status directory_exists_in_index_icase(const char *dirname, int len) { - struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case); + const struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case); unsigned char endchar; if (!ce) @@@ -977,7 -934,7 +934,7 @@@ static enum exist_status directory_exis if (pos < 0) pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; unsigned char endchar; if (strncmp(ce->name, dirname, len)) @@@ -1113,7 -1070,7 +1070,7 @@@ static int exclude_matches_pathspec(con static int get_index_dtype(const char *path, int len) { int pos; - struct cache_entry *ce; + const struct cache_entry *ce; ce = cache_name_exists(path, len, 0); if (ce) { @@@ -1381,14 -1338,25 +1338,25 @@@ static int treat_leading_path(struct di return rc; } - int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) + int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec) { struct path_simplify *simplify; + /* + * Check out create_simplify() + */ + if (pathspec) + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE); + if (has_symlink_leading_path(path, len)) return dir->nr; - simplify = create_simplify(pathspec); + simplify = create_simplify(pathspec ? pathspec->_raw : NULL); if (!len || treat_leading_path(dir, path, len, simplify)) read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); @@@ -1568,71 -1536,6 +1536,6 @@@ int remove_path(const char *name return 0; } - static int pathspec_item_cmp(const void *a_, const void *b_) - { - struct pathspec_item *a, *b; - - a = (struct pathspec_item *)a_; - b = (struct pathspec_item *)b_; - return strcmp(a->match, b->match); - } - - int init_pathspec(struct pathspec *pathspec, const char **paths) - { - const char **p = paths; - int i; - - memset(pathspec, 0, sizeof(*pathspec)); - if (!p) - return 0; - while (*p) - p++; - pathspec->raw = paths; - pathspec->nr = p - paths; - if (!pathspec->nr) - return 0; - - pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); - for (i = 0; i < pathspec->nr; i++) { - struct pathspec_item *item = pathspec->items+i; - const char *path = paths[i]; - - item->match = path; - item->len = strlen(path); - item->flags = 0; - if (limit_pathspec_to_literal()) { - item->nowildcard_len = item->len; - } else { - item->nowildcard_len = simple_length(path); - if (item->nowildcard_len < item->len) { - pathspec->has_wildcard = 1; - if (path[item->nowildcard_len] == '*' && - no_wildcard(path + item->nowildcard_len + 1)) - item->flags |= PATHSPEC_ONESTAR; - } - } - } - - qsort(pathspec->items, pathspec->nr, - sizeof(struct pathspec_item), pathspec_item_cmp); - - return 0; - } - - void free_pathspec(struct pathspec *pathspec) - { - free(pathspec->items); - pathspec->items = NULL; - } - - int limit_pathspec_to_literal(void) - { - static int flag = -1; - if (flag < 0) - flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); - return flag; - } - /* * Frees memory within dir which was allocated for exclude lists and * the exclude_stack. Does not free dir itself. diff --combined git.c index 2025f77d01,cebf8827da..b3893e73c9 --- a/git.c +++ b/git.c @@@ -147,6 -147,18 +147,18 @@@ static int handle_options(const char ** setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--glob-pathspecs")) { + setenv(GIT_GLOB_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--noglob-pathspecs")) { + setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--icase-pathspecs")) { + setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--shallow-file")) { (*argv)++; (*argc)--; @@@ -324,7 -336,6 +336,7 @@@ static void handle_internal_command(in { "cat-file", cmd_cat_file, RUN_SETUP }, { "check-attr", cmd_check_attr, RUN_SETUP }, { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE }, + { "check-mailmap", cmd_check_mailmap, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, { "checkout-index", cmd_checkout_index, @@@ -526,13 -537,6 +538,13 @@@ int main(int argc, char **av if (!cmd) cmd = "git-help"; + /* + * Always open file descriptors 0/1/2 to avoid clobbering files + * in die(). It also avoids messing up when the pipes are dup'ed + * onto stdin/stdout/stderr in the child processes we spawn. + */ + sanitize_stdfds(); + git_setup_gettext(); /* diff --combined line-log.c index d40c79dc2b,843a334bf0..8b6e497b3f --- a/line-log.c +++ b/line-log.c @@@ -23,7 -23,7 +23,7 @@@ static void range_set_grow(struct range /* Either initialization would be fine */ #define RANGE_SET_INIT {0} -static void range_set_init(struct range_set *rs, size_t prealloc) +void range_set_init(struct range_set *rs, size_t prealloc) { rs->alloc = rs->nr = 0; rs->ranges = NULL; @@@ -31,7 -31,7 +31,7 @@@ range_set_grow(rs, prealloc); } -static void range_set_release(struct range_set *rs) +void range_set_release(struct range_set *rs) { free(rs->ranges); rs->alloc = rs->nr = 0; @@@ -56,7 -56,7 +56,7 @@@ static void range_set_move(struct range } /* tack on a _new_ range _at the end_ */ -static void range_set_append_unsafe(struct range_set *rs, long a, long b) +void range_set_append_unsafe(struct range_set *rs, long a, long b) { assert(a <= b); range_set_grow(rs, 1); @@@ -65,7 -65,7 +65,7 @@@ rs->nr++; } -static void range_set_append(struct range_set *rs, long a, long b) +void range_set_append(struct range_set *rs, long a, long b) { assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a); range_set_append_unsafe(rs, a, b); @@@ -107,19 -107,16 +107,19 @@@ static void range_set_check_invariants( * In-place pass of sorting and merging the ranges in the range set, * to establish the invariants when we get the ranges from the user */ -static void sort_and_merge_range_set(struct range_set *rs) +void sort_and_merge_range_set(struct range_set *rs) { int i; - int o = 1; /* output cursor */ + int o = 0; /* output cursor */ qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp); - for (i = 1; i < rs->nr; i++) { - if (rs->ranges[i].start <= rs->ranges[o-1].end) { - rs->ranges[o-1].end = rs->ranges[i].end; + for (i = 0; i < rs->nr; i++) { + if (rs->ranges[i].start == rs->ranges[i].end) + continue; + if (o > 0 && rs->ranges[i].start <= rs->ranges[o-1].end) { + if (rs->ranges[o-1].end < rs->ranges[i].end) + rs->ranges[o-1].end = rs->ranges[i].end; } else { rs->ranges[o].start = rs->ranges[i].start; rs->ranges[o].end = rs->ranges[i].end; @@@ -291,6 -288,7 +291,6 @@@ static void line_log_data_insert(struc if (p) { range_set_append_unsafe(&p->ranges, begin, end); - sort_and_merge_range_set(&p->ranges); free(path); return; } @@@ -564,14 -562,12 +564,14 @@@ parse_lines(struct commit *commit, cons struct nth_line_cb cb_data; struct string_list_item *item; struct line_log_data *ranges = NULL; + struct line_log_data *p; for_each_string_list_item(item, args) { const char *name_part, *range_part; char *full_name; struct diff_filespec *spec; long begin = 0, end = 0; + long anchor; name_part = skip_range_arg(item->string); if (!name_part || *name_part != ':' || !name_part[1]) @@@ -590,23 -586,17 +590,23 @@@ cb_data.lines = lines; cb_data.line_ends = ends; + p = search_line_log_data(ranges, full_name, NULL); + if (p && p->ranges.nr) + anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1; + else + anchor = 1; + if (parse_range_arg(range_part, nth_line, &cb_data, - lines, &begin, &end, + lines, anchor, &begin, &end, full_name)) die("malformed -L argument '%s'", range_part); + if (lines < end || ((lines || begin) && lines < begin)) + die("file %s has only %lu lines", name_part, lines); if (begin < 1) begin = 1; if (end < 1) end = lines; begin--; - if (lines < end || lines < begin) - die("file %s has only %ld lines", name_part, lines); line_log_data_insert(&ranges, full_name, begin, end); free_filespec(spec); @@@ -614,9 -604,6 +614,9 @@@ ends = NULL; } + for (p = ranges; p; p = p->next) + sort_and_merge_range_set(&p->ranges); + return ranges; } @@@ -760,7 -747,7 +760,7 @@@ void line_log_init(struct rev_info *rev r = r->next; } paths[count] = NULL; - init_pathspec(&rev->diffopt.pathspec, paths); + parse_pathspec(&rev->diffopt.pathspec, 0, 0, "", paths); free(paths); } } diff --combined merge-recursive.c index f95933b0aa,8395b9b08a..40eb840a52 --- a/merge-recursive.c +++ b/merge-recursive.c @@@ -251,7 -251,7 +251,7 @@@ struct tree *write_tree_from_memory(str int i; fprintf(stderr, "BUG: There are unmerged index entries:\n"); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce), (int)ce_namelen(ce), ce->name); @@@ -264,8 -264,7 +264,8 @@@ if (!cache_tree_fully_valid(active_cache_tree) && cache_tree_update(active_cache_tree, - active_cache, active_nr, 0) < 0) + (const struct cache_entry * const *)active_cache, + active_nr, 0) < 0) die(_("error building trees")); result = lookup_tree(active_cache_tree->sha1); @@@ -298,7 -297,7 +298,7 @@@ static int get_files_dirs(struct merge_ { int n; struct pathspec match_all; - init_pathspec(&match_all, NULL); + memset(&match_all, 0, sizeof(match_all)); if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o)) return 0; n = o->current_file_set.nr + o->current_directory_set.nr; @@@ -340,7 -339,7 +340,7 @@@ static struct string_list *get_unmerged for (i = 0; i < active_nr; i++) { struct string_list_item *item; struct stage_data *e; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; diff --combined path.c index 3d244d3e03,f4b49d6924..9fd28bcd08 --- a/path.c +++ b/path.c @@@ -5,7 -5,13 +5,7 @@@ #include "strbuf.h" #include "string-list.h" -#ifndef get_st_mode_bits -/* - * The replacement lstat(2) we use on Cygwin is incomplete and - * may return wrong permission bits. Most of the time we do not care, - * but the callsites of this wrapper do care. - */ -int get_st_mode_bits(const char *path, int *mode) +static int get_st_mode_bits(const char *path, int *mode) { struct stat st; if (lstat(path, &st) < 0) @@@ -13,6 -19,7 +13,6 @@@ *mode = st.st_mode; return 0; } -#endif static char bad_path[] = "/bad-path/"; @@@ -434,100 -441,42 +434,100 @@@ int adjust_shared_perm(const char *path return 0; } -const char *relative_path(const char *abs, const char *base) +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +const char *relative_path(const char *in, const char *prefix, + struct strbuf *sb) { - static char buf[PATH_MAX + 1]; + int in_len = in ? strlen(in) : 0; + int prefix_len = prefix ? strlen(prefix) : 0; + int in_off = 0; + int prefix_off = 0; int i = 0, j = 0; - if (!base || !base[0]) - return abs; - while (base[i]) { - if (is_dir_sep(base[i])) { - if (!is_dir_sep(abs[j])) - return abs; - while (is_dir_sep(base[i])) + if (!in_len) + return "./"; + else if (!prefix_len) + return in; + + while (i < prefix_len && j < in_len && prefix[i] == in[j]) { + if (is_dir_sep(prefix[i])) { + while (is_dir_sep(prefix[i])) i++; - while (is_dir_sep(abs[j])) + while (is_dir_sep(in[j])) j++; + prefix_off = i; + in_off = j; + } else { + i++; + j++; + } + } + + if ( + /* "prefix" seems like prefix of "in" */ + i >= prefix_len && + /* + * but "/foo" is not a prefix of "/foobar" + * (i.e. prefix not end with '/') + */ + prefix_off < prefix_len) { + if (j >= in_len) { + /* in="/a/b", prefix="/a/b" */ + in_off = in_len; + } else if (is_dir_sep(in[j])) { + /* in="/a/b/c", prefix="/a/b" */ + while (is_dir_sep(in[j])) + j++; + in_off = j; + } else { + /* in="/a/bbb/c", prefix="/a/b" */ + i = prefix_off; + } + } else if ( + /* "in" is short than "prefix" */ + j >= in_len && + /* "in" not end with '/' */ + in_off < in_len) { + if (is_dir_sep(prefix[i])) { + /* in="/a/b", prefix="/a/b/c/" */ + while (is_dir_sep(prefix[i])) + i++; + in_off = in_len; + } + } + in += in_off; + in_len -= in_off; + + if (i >= prefix_len) { + if (!in_len) + return "./"; + else + return in; + } + + strbuf_reset(sb); + strbuf_grow(sb, in_len); + + while (i < prefix_len) { + if (is_dir_sep(prefix[i])) { + strbuf_addstr(sb, "../"); + while (is_dir_sep(prefix[i])) + i++; continue; - } else if (abs[j] != base[i]) { - return abs; } i++; - j++; } - if ( - /* "/foo" is a prefix of "/foo" */ - abs[j] && - /* "/foo" is not a prefix of "/foobar" */ - !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) - ) - return abs; - while (is_dir_sep(abs[j])) - j++; - if (!abs[j]) - strcpy(buf, "."); - else - strcpy(buf, abs + j); - return buf; + if (!is_dir_sep(prefix[prefix_len - 1])) + strbuf_addstr(sb, "../"); + + strbuf_addstr(sb, in); + + return sb->buf; } /* @@@ -543,8 -492,14 +543,14 @@@ * * Note that this function is purely textual. It does not follow symlinks, * verify the existence of the path, or make any system calls. + * + * prefix_len != NULL is for a specific case of prefix_pathspec(): + * assume that src == dst and src[0..prefix_len-1] is already + * normalized, any time "../" eats up to the prefix_len part, + * prefix_len is reduced. In the end prefix_len is the remaining + * prefix that has not been overridden by user pathspec. */ - int normalize_path_copy(char *dst, const char *src) + int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; @@@ -619,11 -574,18 +625,18 @@@ /* Windows: dst[-1] cannot be backslash anymore */ while (dst0 < dst && dst[-1] != '/') dst--; + if (prefix_len && *prefix_len > dst - dst0) + *prefix_len = dst - dst0; } *dst = '\0'; return 0; } + int normalize_path_copy(char *dst, const char *src) + { + return normalize_path_copy_len(dst, src, NULL); + } + /* * path = Canonical absolute path * prefixes = string_list containing normalized, absolute paths without diff --combined pathspec.c index 6ea0867493,d9f4143222..4b32cc32cb --- a/pathspec.c +++ b/pathspec.c @@@ -15,8 -15,8 +15,8 @@@ * If seen[] has not already been written to, it may make sense * to use find_pathspecs_matching_against_index() instead. */ - void add_pathspec_matches_against_index(const char **pathspec, - char *seen, int specs) + void add_pathspec_matches_against_index(const struct pathspec *pathspec, + char *seen) { int num_unmatched = 0, i; @@@ -26,14 -26,14 +26,14 @@@ * mistakenly think that the user gave a pathspec that did not match * anything. */ - for (i = 0; i < specs; i++) + for (i = 0; i < pathspec->nr; i++) if (!seen[i]) num_unmatched++; if (!num_unmatched) return; for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); + match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen); } } @@@ -45,57 -45,428 +45,428 @@@ * nature of the "closest" (i.e. most specific) matches which each of the * given pathspecs achieves against all items in the index. */ - char *find_pathspecs_matching_against_index(const char **pathspec) + char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) { - char *seen; - int i; - - for (i = 0; pathspec[i]; i++) - ; /* just counting */ - seen = xcalloc(i, 1); - add_pathspec_matches_against_index(pathspec, seen, i); + char *seen = xcalloc(pathspec->nr, 1); + add_pathspec_matches_against_index(pathspec, seen); return seen; } /* - * Check the index to see whether path refers to a submodule, or - * something inside a submodule. If the former, returns the path with - * any trailing slash stripped. If the latter, dies with an error - * message. + * Magic pathspec + * + * Possible future magic semantics include stuff like: + * + * { PATHSPEC_RECURSIVE, '*', "recursive" }, + * { PATHSPEC_REGEXP, '\0', "regexp" }, + * + */ + + static struct pathspec_magic { + unsigned bit; + char mnemonic; /* this cannot be ':'! */ + const char *name; + } pathspec_magic[] = { + { PATHSPEC_FROMTOP, '/', "top" }, + { PATHSPEC_LITERAL, 0, "literal" }, + { PATHSPEC_GLOB, '\0', "glob" }, + { PATHSPEC_ICASE, '\0', "icase" }, + }; + + /* + * Take an element of a pathspec and check for magic signatures. + * Append the result to the prefix. Return the magic bitmap. + * + * For now, we only parse the syntax and throw out anything other than + * "top" magic. + * + * NEEDSWORK: This needs to be rewritten when we start migrating + * get_pathspec() users to use the "struct pathspec" interface. For + * example, a pathspec element may be marked as case-insensitive, but + * the prefix part must always match literally, and a single stupid + * string cannot express such a case. */ - const char *check_path_for_gitlink(const char *path) + static unsigned prefix_pathspec(struct pathspec_item *item, + unsigned *p_short_magic, + const char **raw, unsigned flags, + const char *prefix, int prefixlen, + const char *elt) { - int i, path_len = strlen(path); - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; - if (S_ISGITLINK(ce->ce_mode)) { - int ce_len = ce_namelen(ce); - if (path_len <= ce_len || path[ce_len] != '/' || - memcmp(ce->name, path, ce_len)) - /* path does not refer to this - * submodule or anything inside it */ + static int literal_global = -1; + static int glob_global = -1; + static int noglob_global = -1; + static int icase_global = -1; + unsigned magic = 0, short_magic = 0, global_magic = 0; + const char *copyfrom = elt, *long_magic_end = NULL; + char *match; + int i, pathspec_prefix = -1; + + if (literal_global < 0) + literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + if (literal_global) + global_magic |= PATHSPEC_LITERAL; + + if (glob_global < 0) + glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); + if (glob_global) + global_magic |= PATHSPEC_GLOB; + + if (noglob_global < 0) + noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); + + if (glob_global && noglob_global) + die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); + + + if (icase_global < 0) + icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); + if (icase_global) + global_magic |= PATHSPEC_ICASE; + + if ((global_magic & PATHSPEC_LITERAL) && + (global_magic & ~PATHSPEC_LITERAL)) + die(_("global 'literal' pathspec setting is incompatible " + "with all other global pathspec settings")); + + if (elt[0] != ':' || literal_global) { + ; /* nothing to do */ + } else if (elt[1] == '(') { + /* longhand */ + const char *nextat; + for (copyfrom = elt + 2; + *copyfrom && *copyfrom != ')'; + copyfrom = nextat) { + size_t len = strcspn(copyfrom, ",)"); + if (copyfrom[len] == ',') + nextat = copyfrom + len + 1; + else + /* handle ')' and '\0' */ + nextat = copyfrom + len; + if (!len) continue; - if (path_len == ce_len + 1) { - /* path refers to submodule; - * strip trailing slash */ - return xstrndup(ce->name, ce_len); + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + if (strlen(pathspec_magic[i].name) == len && + !strncmp(pathspec_magic[i].name, copyfrom, len)) { + magic |= pathspec_magic[i].bit; + break; + } + if (!prefixcmp(copyfrom, "prefix:")) { + char *endptr; + pathspec_prefix = strtol(copyfrom + 7, + &endptr, 10); + if (endptr - copyfrom != len) + die(_("invalid parameter for pathspec magic 'prefix'")); + /* "i" would be wrong, but it does not matter */ + break; + } + } + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Invalid pathspec magic '%.*s' in '%s'"), + (int) len, copyfrom, elt); + } + if (*copyfrom != ')') + die(_("Missing ')' at the end of pathspec magic in '%s'"), elt); + long_magic_end = copyfrom; + copyfrom++; + } else { + /* shorthand */ + for (copyfrom = elt + 1; + *copyfrom && *copyfrom != ':'; + copyfrom++) { + char ch = *copyfrom; + + if (!is_pathspec_magic(ch)) + break; + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) + if (pathspec_magic[i].mnemonic == ch) { + short_magic |= pathspec_magic[i].bit; + break; + } + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Unimplemented pathspec magic '%c' in '%s'"), + ch, elt); + } + if (*copyfrom == ':') + copyfrom++; + } + + magic |= short_magic; + *p_short_magic = short_magic; + + /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specifed */ + if (noglob_global && !(magic & PATHSPEC_GLOB)) + global_magic |= PATHSPEC_LITERAL; + + /* --glob-pathspec is overriden by :(literal) */ + if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL)) + global_magic &= ~PATHSPEC_GLOB; + + magic |= global_magic; + + if (pathspec_prefix >= 0 && + (prefixlen || (prefix && *prefix))) + die("BUG: 'prefix' magic is supposed to be used at worktree's root"); + + if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB)) + die(_("%s: 'literal' and 'glob' are incompatible"), elt); + + if (pathspec_prefix >= 0) { + match = xstrdup(copyfrom); + prefixlen = pathspec_prefix; + } else if (magic & PATHSPEC_FROMTOP) { + match = xstrdup(copyfrom); + prefixlen = 0; + } else { + match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); + if (!match) + die(_("%s: '%s' is outside repository"), elt, copyfrom); + } + *raw = item->match = match; + /* + * Prefix the pathspec (keep all magic) and assign to + * original. Useful for passing to another command. + */ + if (flags & PATHSPEC_PREFIX_ORIGIN) { + struct strbuf sb = STRBUF_INIT; + const char *start = elt; + if (prefixlen && !literal_global) { + /* Preserve the actual prefix length of each pattern */ + if (long_magic_end) { + strbuf_add(&sb, start, long_magic_end - start); + strbuf_addf(&sb, ",prefix:%d", prefixlen); + start = long_magic_end; } else { - die (_("Path '%s' is in submodule '%.*s'"), - path, ce_len, ce->name); + if (*start == ':') + start++; + strbuf_addf(&sb, ":(prefix:%d)", prefixlen); } } + strbuf_add(&sb, start, copyfrom - start); + strbuf_addstr(&sb, match); + item->original = strbuf_detach(&sb, NULL); + } else + item->original = elt; + item->len = strlen(item->match); + item->prefix = prefixlen; + + if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && + (item->len >= 1 && item->match[item->len - 1] == '/') && + (i = cache_name_pos(item->match, item->len - 1)) >= 0 && + S_ISGITLINK(active_cache[i]->ce_mode)) { + item->len--; + match[item->len] = '\0'; + } + + if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len <= ce_len || match[ce_len] != '/' || + memcmp(ce->name, match, ce_len)) + continue; + if (item->len == ce_len + 1) { + /* strip trailing slash */ + item->len--; + match[item->len] = '\0'; + } else + die (_("Pathspec '%s' is in submodule '%.*s'"), + elt, ce_len, ce->name); + } + + if (magic & PATHSPEC_LITERAL) + item->nowildcard_len = item->len; + else { + item->nowildcard_len = simple_length(item->match); + if (item->nowildcard_len < prefixlen) + item->nowildcard_len = prefixlen; + } + item->flags = 0; + if (magic & PATHSPEC_GLOB) { + /* + * FIXME: should we enable ONESTAR in _GLOB for + * pattern "* * / * . c"? + */ + } else { + if (item->nowildcard_len < item->len && + item->match[item->nowildcard_len] == '*' && + no_wildcard(item->match + item->nowildcard_len + 1)) + item->flags |= PATHSPEC_ONESTAR; } - return path; + + /* sanity checks, pathspec matchers assume these are sane */ + assert(item->nowildcard_len <= item->len && + item->prefix <= item->len); + return magic; + } + + static int pathspec_item_cmp(const void *a_, const void *b_) + { + struct pathspec_item *a, *b; + + a = (struct pathspec_item *)a_; + b = (struct pathspec_item *)b_; + return strcmp(a->match, b->match); + } + + static void NORETURN unsupported_magic(const char *pattern, + unsigned magic, + unsigned short_magic) + { + struct strbuf sb = STRBUF_INIT; + int i, n; + for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + const struct pathspec_magic *m = pathspec_magic + i; + if (!(magic & m->bit)) + continue; + if (sb.len) + strbuf_addstr(&sb, " "); + if (short_magic & m->bit) + strbuf_addf(&sb, "'%c'", m->mnemonic); + else + strbuf_addf(&sb, "'%s'", m->name); + n++; + } + /* + * We may want to substitute "this command" with a command + * name. E.g. when add--interactive dies when running + * "checkout -p" + */ + die(_("%s: pathspec magic not supported by this command: %s"), + pattern, sb.buf); } /* - * Dies if the given path refers to a file inside a symlinked - * directory in the index. + * Given command line arguments and a prefix, convert the input to + * pathspec. die() if any magic in magic_mask is used. */ - void die_if_path_beyond_symlink(const char *path, const char *prefix) + void parse_pathspec(struct pathspec *pathspec, + unsigned magic_mask, unsigned flags, + const char *prefix, const char **argv) { - if (has_symlink_leading_path(path, strlen(path))) { - int len = prefix ? strlen(prefix) : 0; - die(_("'%s' is beyond a symbolic link"), path + len); + struct pathspec_item *item; + const char *entry = argv ? *argv : NULL; + int i, n, prefixlen; + + memset(pathspec, 0, sizeof(*pathspec)); + + if (flags & PATHSPEC_MAXDEPTH_VALID) + pathspec->magic |= PATHSPEC_MAXDEPTH; + + /* No arguments, no prefix -> no pathspec */ + if (!entry && !prefix) + return; + + if ((flags & PATHSPEC_PREFER_CWD) && + (flags & PATHSPEC_PREFER_FULL)) + die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible"); + + /* No arguments with prefix -> prefix pathspec */ + if (!entry) { + static const char *raw[2]; + + if (flags & PATHSPEC_PREFER_FULL) + return; + + if (!(flags & PATHSPEC_PREFER_CWD)) + die("BUG: PATHSPEC_PREFER_CWD requires arguments"); + + pathspec->items = item = xmalloc(sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->match = prefix; + item->original = prefix; + item->nowildcard_len = item->len = strlen(prefix); + item->prefix = item->len; + raw[0] = prefix; + raw[1] = NULL; + pathspec->nr = 1; + pathspec->_raw = raw; + return; } + + n = 0; + while (argv[n]) + n++; + + pathspec->nr = n; + pathspec->items = item = xmalloc(sizeof(*item) * n); + pathspec->_raw = argv; + prefixlen = prefix ? strlen(prefix) : 0; + + for (i = 0; i < n; i++) { + unsigned short_magic; + entry = argv[i]; + + item[i].magic = prefix_pathspec(item + i, &short_magic, + argv + i, flags, + prefix, prefixlen, entry); + if (item[i].magic & magic_mask) + unsupported_magic(entry, + item[i].magic & magic_mask, + short_magic); + + if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) && + has_symlink_leading_path(item[i].match, item[i].len)) { + die(_("pathspec '%s' is beyond a symbolic link"), entry); + } + + if (item[i].nowildcard_len < item[i].len) + pathspec->has_wildcard = 1; + pathspec->magic |= item[i].magic; + } + + + if (pathspec->magic & PATHSPEC_MAXDEPTH) { + if (flags & PATHSPEC_KEEP_ORDER) + die("BUG: PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible"); + qsort(pathspec->items, pathspec->nr, + sizeof(struct pathspec_item), pathspec_item_cmp); + } + } + + /* + * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" + * based interface - see pathspec.c:parse_pathspec(). + * + * Arguments: + * - prefix - a path relative to the root of the working tree + * - pathspec - a list of paths underneath the prefix path + * + * Iterates over pathspec, prepending each path with prefix, + * and return the resulting list. + * + * If pathspec is empty, return a singleton list containing prefix. + * + * If pathspec and prefix are both empty, return an empty list. + * + * This is typically used by built-in commands such as add.c, in order + * to normalize argv arguments provided to the built-in into a list of + * paths to process, all relative to the root of the working tree. + */ + const char **get_pathspec(const char *prefix, const char **pathspec) + { + struct pathspec ps; + parse_pathspec(&ps, + PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), + PATHSPEC_PREFER_CWD, + prefix, pathspec); + return ps._raw; + } + + void copy_pathspec(struct pathspec *dst, const struct pathspec *src) + { + *dst = *src; + dst->items = xmalloc(sizeof(struct pathspec_item) * dst->nr); + memcpy(dst->items, src->items, + sizeof(struct pathspec_item) * dst->nr); + } + + void free_pathspec(struct pathspec *pathspec) + { + free(pathspec->items); + pathspec->items = NULL; } diff --combined read-cache.c index c3d5e3543f,6ad2ff6cdd..737a27f81b --- a/read-cache.c +++ b/read-cache.c @@@ -489,7 -489,7 +489,7 @@@ int remove_index_entry_at(struct index_ } /* - * Remove all cache ententries marked for removal, that is where + * Remove all cache entries marked for removal, that is where * CE_REMOVE is set in ce_flags. This is much more effective than * calling remove_index_entry_at() for each entry to be removed. */ @@@ -722,7 -722,7 +722,7 @@@ struct cache_entry *make_cache_entry(un return ce; } -int ce_same_name(struct cache_entry *a, struct cache_entry *b) +int ce_same_name(const struct cache_entry *a, const struct cache_entry *b) { int len = ce_namelen(a); return ce_namelen(b) == len && !memcmp(a->name, b->name, len); @@@ -1114,7 -1114,8 +1114,8 @@@ static void show_file(const char * fmt printf(fmt, name); } - int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, + int refresh_index(struct index_state *istate, unsigned int flags, + const struct pathspec *pathspec, char *seen, const char *header_msg) { int i; @@@ -1149,7 -1150,7 +1150,7 @@@ continue; if (pathspec && - !match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) + !match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen)) filtered = 1; if (ce_stage(ce)) { @@@ -1760,7 -1761,7 +1761,7 @@@ static int has_racy_timestamp(struct in } /* - * Opportunisticly update the index but do not complain if we can't + * Opportunistically update the index but do not complain if we can't */ void update_index_if_able(struct index_state *istate, struct lock_file *lockfile) { diff --combined rerere.c index 6fde8f94c2,4105bca86e..1f2d21a72f --- a/rerere.c +++ b/rerere.c @@@ -6,6 -6,7 +6,7 @@@ #include "resolve-undo.h" #include "ll-merge.h" #include "attr.h" + #include "pathspec.h" #define RESOLVED 0 #define PUNTED 1 @@@ -299,7 -300,7 +300,7 @@@ static int handle_cache(const char *pat { mmfile_t mmfile[3] = {{NULL}}; mmbuffer_t result = {NULL, 0}; - struct cache_entry *ce; + const struct cache_entry *ce; int pos, len, i, hunk_no; struct rerere_io_mem io; int marker_size = ll_merge_marker_size(path); @@@ -359,7 -360,7 +360,7 @@@ static int check_one_conflict(int i, int *type) { - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; if (!ce_stage(e)) { *type = RESOLVED; @@@ -374,8 -375,8 +375,8 @@@ /* Only handle regular files with both stages #2 and #3 */ if (i + 1 < active_nr) { - struct cache_entry *e2 = active_cache[i]; - struct cache_entry *e3 = active_cache[i + 1]; + const struct cache_entry *e2 = active_cache[i]; + const struct cache_entry *e3 = active_cache[i + 1]; if (ce_stage(e2) == 2 && ce_stage(e3) == 3 && ce_same_name(e, e3) && @@@ -398,7 -399,7 +399,7 @@@ static int find_conflict(struct string_ for (i = 0; i < active_nr;) { int conflict_type; - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; i = check_one_conflict(i, &conflict_type); if (conflict_type == THREE_STAGED) string_list_insert(conflict, (const char *)e->name); @@@ -414,7 -415,7 +415,7 @@@ int rerere_remaining(struct string_lis for (i = 0; i < active_nr;) { int conflict_type; - struct cache_entry *e = active_cache[i]; + const struct cache_entry *e = active_cache[i]; i = check_one_conflict(i, &conflict_type); if (conflict_type == PUNTED) string_list_insert(merge_rr, (const char *)e->name); @@@ -656,7 -657,7 +657,7 @@@ static int rerere_forget_one_path(cons return 0; } - int rerere_forget(const char **pathspec) + int rerere_forget(struct pathspec *pathspec) { int i, fd; struct string_list conflict = STRING_LIST_INIT_DUP; @@@ -671,8 -672,8 +672,8 @@@ find_conflict(&conflict); for (i = 0; i < conflict.nr; i++) { struct string_list_item *it = &conflict.items[i]; - if (!match_pathspec(pathspec, it->string, strlen(it->string), - 0, NULL)) + if (!match_pathspec_depth(pathspec, it->string, strlen(it->string), + 0, NULL)) continue; rerere_forget_one_path(it->string, &merge_rr); } diff --combined resolve-undo.c index 77101f51c1,4b78e6f8b8..c09b00664e --- a/resolve-undo.c +++ b/resolve-undo.c @@@ -115,7 -115,7 +115,7 @@@ void resolve_undo_clear_index(struct in int unmerge_index_entry_at(struct index_state *istate, int pos) { - struct cache_entry *ce; + const struct cache_entry *ce; struct string_list_item *item; struct resolve_undo_info *ru; int i, err = 0, matched; @@@ -167,13 -167,13 +167,13 @@@ void unmerge_marked_index(struct index_ return; for (i = 0; i < istate->cache_nr; i++) { - struct cache_entry *ce = istate->cache[i]; + const struct cache_entry *ce = istate->cache[i]; if (ce->ce_flags & CE_MATCHED) i = unmerge_index_entry_at(istate, i); } } - void unmerge_index(struct index_state *istate, const char **pathspec) + void unmerge_index(struct index_state *istate, const struct pathspec *pathspec) { int i; @@@ -181,8 -181,8 +181,8 @@@ return; for (i = 0; i < istate->cache_nr; i++) { - struct cache_entry *ce = istate->cache[i]; + const struct cache_entry *ce = istate->cache[i]; - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) + if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) continue; i = unmerge_index_entry_at(istate, i); } diff --combined revision.c index ac20d1aaed,001623a921..6230a80a77 --- a/revision.c +++ b/revision.c @@@ -15,7 -15,6 +15,7 @@@ #include "string-list.h" #include "line-log.h" #include "mailmap.h" +#include "commit-slab.h" volatile show_early_output_fn_t show_early_output; @@@ -1359,7 -1358,7 +1359,7 @@@ static void prepare_show_merge(struct r if (!active_nr) read_cache(); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; if (ce_path_match(ce, &revs->prune_data)) { @@@ -1373,7 -1372,7 +1373,7 @@@ i++; } free_pathspec(&revs->prune_data); - init_pathspec(&revs->prune_data, prune); + parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune); revs->limited = 1; } @@@ -2121,8 -2120,8 +2121,8 @@@ int setup_revisions(int argc, const cha */ ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc); prune_data.path[prune_data.nr++] = NULL; - init_pathspec(&revs->prune_data, - get_pathspec(revs->prefix, prune_data.path)); + parse_pathspec(&revs->prune_data, 0, 0, + revs->prefix, prune_data.path); } if (revs->def == NULL) @@@ -2155,12 -2154,13 +2155,13 @@@ revs->limited = 1; if (revs->prune_data.nr) { - diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning); + copy_pathspec(&revs->pruning.pathspec, &revs->prune_data); /* Can't prune commits with rename following: the paths change.. */ if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) revs->prune = 1; if (!revs->full_diff) - diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt); + copy_pathspec(&revs->diffopt.pathspec, + &revs->prune_data); } if (revs->combine_merges) revs->ignore_merges = 0; @@@ -2764,7 -2764,7 +2765,7 @@@ static int commit_match(struct commit * return retval; } -static inline int want_ancestry(struct rev_info *revs) +static inline int want_ancestry(const struct rev_info *revs) { return (revs->rewrite_parents || revs->children.name); } @@@ -2821,14 -2821,6 +2822,14 @@@ enum commit_action simplify_commit(stru if (action == commit_show && !revs->show_all && revs->prune && revs->dense && want_ancestry(revs)) { + /* + * --full-diff on simplified parents is no good: it + * will show spurious changes from the commits that + * were elided. So we save the parents on the side + * when --full-diff is in effect. + */ + if (revs->full_diff) + save_parents(revs, commit); if (rewrite_parents(revs, commit, rewrite_one) < 0) return commit_error; } @@@ -2848,7 -2840,6 +2849,7 @@@ static struct commit *get_revision_1(st free(entry); if (revs->reflog_info) { + save_parents(revs, commit); fake_reflog_parent(revs->reflog_info, commit); commit->object.flags &= ~(ADDED | SEEN | SHOWN); } @@@ -3048,8 -3039,6 +3049,8 @@@ struct commit *get_revision(struct rev_ c = get_revision_internal(revs); if (c && revs->graph) graph_update(revs->graph, c); + if (!c) + free_saved_parents(revs); return c; } @@@ -3081,54 -3070,3 +3082,54 @@@ void put_revision_mark(const struct rev fputs(mark, stdout); putchar(' '); } + +define_commit_slab(saved_parents, struct commit_list *); + +#define EMPTY_PARENT_LIST ((struct commit_list *)-1) + +void save_parents(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp; + + if (!revs->saved_parents_slab) { + revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents)); + init_saved_parents(revs->saved_parents_slab); + } + + pp = saved_parents_at(revs->saved_parents_slab, commit); + + /* + * When walking with reflogs, we may visit the same commit + * several times: once for each appearance in the reflog. + * + * In this case, save_parents() will be called multiple times. + * We want to keep only the first set of parents. We need to + * store a sentinel value for an empty (i.e., NULL) parent + * list to distinguish it from a not-yet-saved list, however. + */ + if (*pp) + return; + if (commit->parents) + *pp = copy_commit_list(commit->parents); + else + *pp = EMPTY_PARENT_LIST; +} + +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit) +{ + struct commit_list *parents; + + if (!revs->saved_parents_slab) + return commit->parents; + + parents = *saved_parents_at(revs->saved_parents_slab, commit); + if (parents == EMPTY_PARENT_LIST) + return NULL; + return parents; +} + +void free_saved_parents(struct rev_info *revs) +{ + if (revs->saved_parents_slab) + clear_saved_parents(revs->saved_parents_slab); +} diff --combined setup.c index 5262319be6,ff4499eaef..f08dd64976 --- a/setup.c +++ b/setup.c @@@ -5,7 -5,19 +5,19 @@@ static int inside_git_dir = -1; static int inside_work_tree = -1; - static char *prefix_path_gently(const char *prefix, int len, const char *path) + /* + * Normalize "path", prepending the "prefix" for relative paths. If + * remaining_prefix is not NULL, return the actual prefix still + * remains in the path. For example, prefix = sub1/sub2/ and path is + * + * foo -> sub1/sub2/foo (full prefix) + * ../foo -> sub1/foo (remaining prefix is sub1/) + * ../../bar -> bar (no remaining prefix) + * ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix) + * `pwd`/../bar -> sub1/bar (no remaining prefix) + */ + char *prefix_path_gently(const char *prefix, int len, + int *remaining_prefix, const char *path) { const char *orig = path; char *sanitized; @@@ -13,13 -25,17 +25,17 @@@ const char *temp = real_path(path); sanitized = xmalloc(len + strlen(temp) + 1); strcpy(sanitized, temp); + if (remaining_prefix) + *remaining_prefix = 0; } else { sanitized = xmalloc(len + strlen(path) + 1); if (len) memcpy(sanitized, prefix, len); strcpy(sanitized + len, path); + if (remaining_prefix) + *remaining_prefix = len; } - if (normalize_path_copy(sanitized, sanitized)) + if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) goto error_out; if (is_absolute_path(orig)) { size_t root_len, len, total; @@@ -44,7 -60,7 +60,7 @@@ char *prefix_path(const char *prefix, int len, const char *path) { - char *r = prefix_path_gently(prefix, len, path); + char *r = prefix_path_gently(prefix, len, NULL, path); if (!r) die("'%s' is outside repository", path); return r; @@@ -53,7 -69,7 +69,7 @@@ int path_inside_repo(const char *prefix, const char *path) { int len = prefix ? strlen(prefix) : 0; - char *r = prefix_path_gently(prefix, len, path); + char *r = prefix_path_gently(prefix, len, NULL, path); if (r) { free(r); return 1; @@@ -154,155 -170,6 +170,6 @@@ void verify_non_filename(const char *pr "'git [...] -- [...]'", arg); } - /* - * Magic pathspec - * - * NEEDSWORK: These need to be moved to dir.h or even to a new - * pathspec.h when we restructure get_pathspec() users to use the - * "struct pathspec" interface. - * - * Possible future magic semantics include stuff like: - * - * { PATHSPEC_NOGLOB, '!', "noglob" }, - * { PATHSPEC_ICASE, '\0', "icase" }, - * { PATHSPEC_RECURSIVE, '*', "recursive" }, - * { PATHSPEC_REGEXP, '\0', "regexp" }, - * - */ - #define PATHSPEC_FROMTOP (1<<0) - - static struct pathspec_magic { - unsigned bit; - char mnemonic; /* this cannot be ':'! */ - const char *name; - } pathspec_magic[] = { - { PATHSPEC_FROMTOP, '/', "top" }, - }; - - /* - * Take an element of a pathspec and check for magic signatures. - * Append the result to the prefix. - * - * For now, we only parse the syntax and throw out anything other than - * "top" magic. - * - * NEEDSWORK: This needs to be rewritten when we start migrating - * get_pathspec() users to use the "struct pathspec" interface. For - * example, a pathspec element may be marked as case-insensitive, but - * the prefix part must always match literally, and a single stupid - * string cannot express such a case. - */ - static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt) - { - unsigned magic = 0; - const char *copyfrom = elt; - int i; - - if (elt[0] != ':') { - ; /* nothing to do */ - } else if (elt[1] == '(') { - /* longhand */ - const char *nextat; - for (copyfrom = elt + 2; - *copyfrom && *copyfrom != ')'; - copyfrom = nextat) { - size_t len = strcspn(copyfrom, ",)"); - if (copyfrom[len] == ',') - nextat = copyfrom + len + 1; - else - /* handle ')' and '\0' */ - nextat = copyfrom + len; - if (!len) - continue; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (strlen(pathspec_magic[i].name) == len && - !strncmp(pathspec_magic[i].name, copyfrom, len)) { - magic |= pathspec_magic[i].bit; - break; - } - if (ARRAY_SIZE(pathspec_magic) <= i) - die("Invalid pathspec magic '%.*s' in '%s'", - (int) len, copyfrom, elt); - } - if (*copyfrom != ')') - die("Missing ')' at the end of pathspec magic in '%s'", elt); - copyfrom++; - } else { - /* shorthand */ - for (copyfrom = elt + 1; - *copyfrom && *copyfrom != ':'; - copyfrom++) { - char ch = *copyfrom; - - if (!is_pathspec_magic(ch)) - break; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (pathspec_magic[i].mnemonic == ch) { - magic |= pathspec_magic[i].bit; - break; - } - if (ARRAY_SIZE(pathspec_magic) <= i) - die("Unimplemented pathspec magic '%c' in '%s'", - ch, elt); - } - if (*copyfrom == ':') - copyfrom++; - } - - if (magic & PATHSPEC_FROMTOP) - return xstrdup(copyfrom); - else - return prefix_path(prefix, prefixlen, copyfrom); - } - - /* - * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" - * based interface - see pathspec_magic above. - * - * Arguments: - * - prefix - a path relative to the root of the working tree - * - pathspec - a list of paths underneath the prefix path - * - * Iterates over pathspec, prepending each path with prefix, - * and return the resulting list. - * - * If pathspec is empty, return a singleton list containing prefix. - * - * If pathspec and prefix are both empty, return an empty list. - * - * This is typically used by built-in commands such as add.c, in order - * to normalize argv arguments provided to the built-in into a list of - * paths to process, all relative to the root of the working tree. - */ - const char **get_pathspec(const char *prefix, const char **pathspec) - { - const char *entry = *pathspec; - const char **src, **dst; - int prefixlen; - - if (!prefix && !entry) - return NULL; - - if (!entry) { - static const char *spec[2]; - spec[0] = prefix; - spec[1] = NULL; - return spec; - } - - /* Otherwise we have to re-write the entries.. */ - src = pathspec; - dst = pathspec; - prefixlen = prefix ? strlen(prefix) : 0; - while (*src) { - *(dst++) = prefix_pathspec(prefix, prefixlen, *src); - src++; - } - *dst = NULL; - if (!*pathspec) - return NULL; - return pathspec; - } /* * Test if it looks like we're at a git directory. @@@ -360,7 -227,6 +227,7 @@@ int is_inside_work_tree(void void setup_work_tree(void) { + struct strbuf sb = STRBUF_INIT; const char *work_tree, *git_dir; static int initialized = 0; @@@ -380,10 -246,8 +247,10 @@@ if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - set_git_dir(relative_path(git_dir, work_tree)); + set_git_dir(relative_path(git_dir, work_tree, &sb)); initialized = 1; + + strbuf_release(&sb); } static int check_repository_format_gently(const char *gitdir, int *nongit_ok) @@@ -911,15 -775,3 +778,15 @@@ const char *resolve_gitdir(const char * return suspect; return read_gitfile(suspect); } + +/* if any standard file descriptor is missing open it to /dev/null */ +void sanitize_stdfds(void) +{ + int fd = open("/dev/null", O_RDWR, 0); + while (fd != -1 && fd < 2) + fd = dup(fd); + if (fd == -1) + die_errno("open /dev/null or dup failed"); + if (fd > 2) + close(fd); +} diff --combined submodule.c index c0f93c208c,0494492bd0..1905d75b2b --- a/submodule.c +++ b/submodule.c @@@ -10,6 -10,7 +10,7 @@@ #include "string-list.h" #include "sha1-array.h" #include "argv-array.h" + #include "blob.h" static struct string_list config_name_for_path; static struct string_list config_fetch_recurse_submodules_for_name; @@@ -30,6 -31,118 +31,118 @@@ static struct sha1_array ref_tips_after */ static int gitmodules_is_unmerged; + /* + * This flag is set if the .gitmodules file had unstaged modifications on + * startup. This must be checked before allowing modifications to the + * .gitmodules file with the intention to stage them later, because when + * continuing we would stage the modifications the user didn't stage herself + * too. That might change in a future version when we learn to stage the + * changes we do ourselves without staging any previous modifications. + */ + static int gitmodules_is_modified; + + + int is_staging_gitmodules_ok(void) + { + return !gitmodules_is_modified; + } + + /* + * Try to update the "path" entry in the "submodule." section of the + * .gitmodules file. Return 0 only if a .gitmodules file was found, a section + * with the correct path= setting was found and we could update it. + */ + int update_path_in_gitmodules(const char *oldpath, const char *newpath) + { + struct strbuf entry = STRBUF_INIT; + struct string_list_item *path_option; + + if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ + return -1; + + if (gitmodules_is_unmerged) + die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); + + path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath); + if (!path_option) { + warning(_("Could not find section in .gitmodules where path=%s"), oldpath); + return -1; + } + strbuf_addstr(&entry, "submodule."); + strbuf_addstr(&entry, path_option->util); + strbuf_addstr(&entry, ".path"); + if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) { + /* Maybe the user already did that, don't error out here */ + warning(_("Could not update .gitmodules entry %s"), entry.buf); + strbuf_release(&entry); + return -1; + } + strbuf_release(&entry); + return 0; + } + + /* + * Try to remove the "submodule." section from .gitmodules where the given + * path is configured. Return 0 only if a .gitmodules file was found, a section + * with the correct path= setting was found and we could remove it. + */ + int remove_path_from_gitmodules(const char *path) + { + struct strbuf sect = STRBUF_INIT; + struct string_list_item *path_option; + + if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ + return -1; + + if (gitmodules_is_unmerged) + die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); + + path_option = unsorted_string_list_lookup(&config_name_for_path, path); + if (!path_option) { + warning(_("Could not find section in .gitmodules where path=%s"), path); + return -1; + } + strbuf_addstr(§, "submodule."); + strbuf_addstr(§, path_option->util); + if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { + /* Maybe the user already did that, don't error out here */ + warning(_("Could not remove .gitmodules entry for %s"), path); + strbuf_release(§); + return -1; + } + strbuf_release(§); + return 0; + } + + void stage_updated_gitmodules(void) + { + struct strbuf buf = STRBUF_INIT; + struct stat st; + int pos; + struct cache_entry *ce; + int namelen = strlen(".gitmodules"); + + pos = cache_name_pos(".gitmodules", namelen); + if (pos < 0) { + warning(_("could not find .gitmodules in index")); + return; + } + ce = active_cache[pos]; + ce->ce_flags = namelen; + if (strbuf_read_file(&buf, ".gitmodules", 0) < 0) + die(_("reading updated .gitmodules failed")); + if (lstat(".gitmodules", &st) < 0) + die_errno(_("unable to stat updated .gitmodules")); + fill_stat_cache_info(ce, &st); + ce->ce_mode = ce_mode_from_stat(ce, st.st_mode); + if (remove_cache_entry_at(pos) < 0) + die(_("unable to remove .gitmodules from index")); + if (write_sha1_file(buf.buf, buf.len, blob_type, ce->sha1)) + die(_("adding updated .gitmodules failed")); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE)) + die(_("staging updated .gitmodules failed")); + } + static int add_submodule_odb(const char *path) { struct strbuf objects_directory = STRBUF_INIT; @@@ -116,6 -229,11 +229,11 @@@ void gitmodules_config(void !memcmp(ce->name, ".gitmodules", 11)) gitmodules_is_unmerged = 1; } + } else if (pos < active_nr) { + struct stat st; + if (lstat(".gitmodules", &st) == 0 && + ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED) + gitmodules_is_modified = 1; } if (!gitmodules_is_unmerged) @@@ -134,9 -252,6 +252,9 @@@ int parse_submodule_config_option(cons return 0; if (!strcmp(key, "path")) { + if (!value) + return config_error_nonbool(var); + config = unsorted_string_list_lookup(&config_name_for_path, value); if (config) free(config->util); @@@ -154,9 -269,6 +272,9 @@@ } else if (!strcmp(key, "ignore")) { char *name_cstr; + if (!value) + return config_error_nonbool(var); + if (strcmp(value, "untracked") && strcmp(value, "dirty") && strcmp(value, "all") && strcmp(value, "none")) { warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); @@@ -630,7 -742,7 +748,7 @@@ int fetch_populated_submodules(const st struct strbuf submodule_path = STRBUF_INIT; struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; const char *git_dir, *name, *default_argv; if (!S_ISGITLINK(ce->ce_mode)) @@@ -1010,3 -1122,34 +1128,34 @@@ int merge_submodule(unsigned char resul free(merges.objects); return 0; } + + /* Update gitfile and core.worktree setting to connect work tree and git dir */ + void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) + { + struct strbuf file_name = STRBUF_INIT; + struct strbuf rel_path = STRBUF_INIT; + const char *real_work_tree = xstrdup(real_path(work_tree)); + FILE *fp; + + /* Update gitfile */ + strbuf_addf(&file_name, "%s/.git", work_tree); + fp = fopen(file_name.buf, "w"); + if (!fp) + die(_("Could not create git link %s"), file_name.buf); + fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree, + &rel_path)); + fclose(fp); + + /* Update core.worktree setting */ + strbuf_reset(&file_name); + strbuf_addf(&file_name, "%s/config", git_dir); + if (git_config_set_in_file(file_name.buf, "core.worktree", + relative_path(real_work_tree, git_dir, + &rel_path))) + die(_("Could not set core.worktree in %s"), + file_name.buf); + + strbuf_release(&file_name); + strbuf_release(&rel_path); + free((void *)real_work_tree); + } diff --combined t/t0008-ignores.sh index c29342d6bc,2ced8e9729..96f40fedfb --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@@ -432,7 -432,7 +432,7 @@@ test_expect_success_multi SYMLINKS 'sym test_expect_success_multi SYMLINKS 'beyond a symlink' '' ' test_check_ignore "a/symlink/foo" 128 && - test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link" + test_stderr "fatal: pathspec '\''a/symlink/foo'\'' is beyond a symbolic link" ' test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' ' @@@ -440,7 -440,7 +440,7 @@@ cd a && test_check_ignore "symlink/foo" 128 ) && - test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link" + test_stderr "fatal: pathspec '\''symlink/foo'\'' is beyond a symbolic link" ' ############################################################################ @@@ -449,7 -449,7 +449,7 @@@ test_expect_success_multi 'submodule' '' ' test_check_ignore "a/submodule/one" 128 && - test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" + test_stderr "fatal: Pathspec '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" ' test_expect_success_multi 'submodule from subdirectory' '' ' @@@ -457,7 -457,7 +457,7 @@@ cd a && test_check_ignore "submodule/one" 128 ) && - test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''" + test_stderr "fatal: Pathspec '\''submodule/one'\'' is in submodule '\''a/submodule'\''" ' ############################################################################ @@@ -697,21 -697,13 +697,21 @@@ test_expect_success PIPE 'streaming sup # shell, and then echo to the fd. We make sure to close it at # the end, so that the subprocess does get EOF and dies # properly. + # + # Similarly, we must keep "out" open so that check-ignore does + # not ever get SIGPIPE trying to write to us. Not only would that + # produce incorrect results, but then there would be no writer on the + # other end of the pipe, and we would potentially block forever trying + # to open it. exec 9>in && + exec 8&-" && + test_when_finished "exec 8<&-" && echo >&9 one && - read response &9 two && - read response .gitmodules <<-\EOF && + [submodule "s"] + path + ignore + EOF + test_must_fail git status +' + test_expect_success 'setup - repository in init subdirectory' ' mkdir init && ( @@@ -783,13 -773,11 +783,11 @@@ test_expect_success 'submodule add --na test_cmp expect .git ) && echo "repo" >expect && - git config -f .gitmodules submodule.repo.path >actual && - test_cmp expect actual && + test_must_fail git config -f .gitmodules submodule.repo.path && git config -f .gitmodules submodule.repo_new.path >actual && test_cmp expect actual&& echo "$submodurl/repo" >expect && - git config -f .gitmodules submodule.repo.url >actual && - test_cmp expect actual && + test_must_fail git config -f .gitmodules submodule.repo.url && echo "$submodurl/bare.git" >expect && git config -f .gitmodules submodule.repo_new.url >actual && test_cmp expect actual && @@@ -809,12 -797,8 +807,8 @@@ test_expect_success 'submodule add wit git rm repo && test_must_fail git submodule add -q --name repo_new "$submodurl/repo.git" repo && test ! -d repo && - echo "repo" >expect && - git config -f .gitmodules submodule.repo_new.path >actual && - test_cmp expect actual&& - echo "$submodurl/bare.git" >expect && - git config -f .gitmodules submodule.repo_new.url >actual && - test_cmp expect actual && + test_must_fail git config -f .gitmodules submodule.repo_new.path && + test_must_fail git config -f .gitmodules submodule.repo_new.url && echo "$submodurl/bare.git" >expect && git config submodule.repo_new.url >actual && test_cmp expect actual && @@@ -973,20 -957,4 +967,20 @@@ test_expect_success 'submodule with UTF git submodule >&2 && test -n "$(git submodule | grep "$svname")" ' + +test_expect_success 'submodule add clone shallow submodule' ' + mkdir super && + pwd=$(pwd) + ( + cd super && + git init && + git submodule add --depth=1 file://"$pwd"/example2 submodule && + ( + cd submodule && + test 1 = $(git log --oneline | wc -l) + ) + ) +' + + test_done diff --combined tree-walk.c index c626135234,c366852553..5ece8c3477 --- a/tree-walk.c +++ b/tree-walk.c @@@ -3,6 -3,7 +3,7 @@@ #include "unpack-trees.h" #include "dir.h" #include "tree.h" + #include "pathspec.h" static const char *get_mode(const char *str, unsigned int *modep) { @@@ -323,6 -324,7 +324,6 @@@ static inline int prune_traversal(struc int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { - int ret = 0; int error = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); int i; @@@ -340,7 -342,6 +341,7 @@@ strbuf_setlen(&base, info->pathlen); } for (;;) { + int trees_used; unsigned long mask, dirmask; const char *first = NULL; int first_len = 0; @@@ -404,14 -405,15 +405,14 @@@ if (interesting < 0) break; if (interesting) { - ret = info->fn(n, mask, dirmask, entry, info); - if (ret < 0) { - error = ret; + trees_used = info->fn(n, mask, dirmask, entry, info); + if (trees_used < 0) { + error = trees_used; if (!info->show_all_errors) break; } - mask &= ret; + mask &= trees_used; } - ret = 0; for (i = 0; i < n; i++) if (mask & (1ul << i)) update_extended_entry(tx + i, entry + i); @@@ -487,13 -489,25 +488,25 @@@ int get_tree_entry(const unsigned char return retval; } - static int match_entry(const struct name_entry *entry, int pathlen, + static int match_entry(const struct pathspec_item *item, + const struct name_entry *entry, int pathlen, const char *match, int matchlen, enum interesting *never_interesting) { int m = -1; /* signals that we haven't called strncmp() */ - if (*never_interesting != entry_not_interesting) { + if (item->magic & PATHSPEC_ICASE) + /* + * "Never interesting" trick requires exact + * matching. We could do something clever with inexact + * matching, but it's trickier (and not to forget that + * strcasecmp is locale-dependent, at least in + * glibc). Just disable it for now. It can't be worse + * than the wildcard's codepath of '[Tt][Hi][Is][Ss]' + * pattern. + */ + *never_interesting = entry_not_interesting; + else if (*never_interesting != entry_not_interesting) { /* * We have not seen any match that sorts later * than the current path. @@@ -539,7 -553,7 +552,7 @@@ * we cheated and did not do strncmp(), so we do * that here. */ - m = strncmp(match, entry->path, pathlen); + m = ps_strncmp(item, match, entry->path, pathlen); /* * If common part matched earlier then it is a hit, @@@ -547,15 -561,39 +560,39 @@@ * leading directory and is shorter than match. */ if (!m) + /* + * match_entry does not check if the prefix part is + * matched case-sensitively. If the entry is a + * directory and part of prefix, it'll be rematched + * eventually by basecmp with special treatment for + * the prefix. + */ return 1; return 0; } - static int match_dir_prefix(const char *base, + /* :(icase)-aware string compare */ + static int basecmp(const struct pathspec_item *item, + const char *base, const char *match, int len) + { + if (item->magic & PATHSPEC_ICASE) { + int ret, n = len > item->prefix ? item->prefix : len; + ret = strncmp(base, match, n); + if (ret) + return ret; + base += n; + match += n; + len -= n; + } + return ps_strncmp(item, base, match, len); + } + + static int match_dir_prefix(const struct pathspec_item *item, + const char *base, const char *match, int matchlen) { - if (strncmp(base, match, matchlen)) + if (basecmp(item, base, match, matchlen)) return 0; /* @@@ -592,7 -630,7 +629,7 @@@ static int match_wildcard_base(const st */ if (baselen >= matchlen) { *matched = matchlen; - return !strncmp(base, match, matchlen); + return !basecmp(item, base, match, matchlen); } dirlen = matchlen; @@@ -605,7 -643,7 +642,7 @@@ * base ends with '/' so we are sure it really matches * directory */ - if (strncmp(base, match, baselen)) + if (basecmp(item, base, match, baselen)) return 0; *matched = baselen; } else @@@ -634,8 -672,17 +671,17 @@@ enum interesting tree_entry_interesting enum interesting never_interesting = ps->has_wildcard ? entry_not_interesting : all_entries_not_interesting; + GUARD_PATHSPEC(ps, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE); + if (!ps->nr) { - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return all_entries_interesting; return within_depth(base->buf + base_offset, baselen, !!S_ISDIR(entry->mode), @@@ -653,10 -700,12 +699,12 @@@ if (baselen >= matchlen) { /* If it doesn't match, move along... */ - if (!match_dir_prefix(base_str, match, matchlen)) + if (!match_dir_prefix(item, base_str, match, matchlen)) goto match_wildcards; - if (!ps->recursive || ps->max_depth == -1) + if (!ps->recursive || + !(ps->magic & PATHSPEC_MAXDEPTH) || + ps->max_depth == -1) return all_entries_interesting; return within_depth(base_str + matchlen + 1, @@@ -667,15 -716,14 +715,14 @@@ } /* Either there must be no base, or the base must match. */ - if (baselen == 0 || !strncmp(base_str, match, baselen)) { - if (match_entry(entry, pathlen, + if (baselen == 0 || !basecmp(item, base_str, match, baselen)) { + if (match_entry(item, entry, pathlen, match + baselen, matchlen - baselen, &never_interesting)) return entry_interesting; if (item->nowildcard_len < item->len) { - if (!git_fnmatch(match + baselen, entry->path, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + if (!git_fnmatch(item, match + baselen, entry->path, item->nowildcard_len - baselen)) return entry_interesting; @@@ -716,8 -764,7 +763,7 @@@ match_wildcards strbuf_add(base, entry->path, pathlen); - if (!git_fnmatch(match, base->buf + base_offset, - item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0, + if (!git_fnmatch(item, match, base->buf + base_offset, item->nowildcard_len)) { strbuf_setlen(base, base_offset + baselen); return entry_interesting; diff --combined tree.c index c0e568c08f,ff72f67dc1..549e5883a0 --- a/tree.c +++ b/tree.c @@@ -47,7 -47,7 +47,7 @@@ static int read_one_entry_quick(const u } static int read_tree_1(struct tree *tree, struct strbuf *base, - int stage, struct pathspec *pathspec, + int stage, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) { struct tree_desc desc; @@@ -116,7 -116,7 +116,7 @@@ int read_tree_recursive(struct tree *tree, const char *base, int baselen, - int stage, struct pathspec *pathspec, + int stage, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) { struct strbuf sb = STRBUF_INIT; @@@ -159,7 -159,7 +159,7 @@@ int read_tree(struct tree *tree, int st * sort at the end. */ for (i = 0; !fn && i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (ce_stage(ce) == stage) fn = read_one_entry; } diff --combined wt-status.c index cb24f1fa9b,33baf0a698..ff4b32426a --- a/wt-status.c +++ b/wt-status.c @@@ -1,4 -1,5 +1,5 @@@ #include "cache.h" + #include "pathspec.h" #include "wt-status.h" #include "object.h" #include "dir.h" @@@ -244,7 -245,7 +245,7 @@@ static void wt_status_print_unmerged_da struct strbuf onebuf = STRBUF_INIT; const char *one, *how = _("bug"); - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (d->stagemask) { case 1: how = _("both deleted:"); break; @@@ -298,8 -299,8 +299,8 @@@ static void wt_status_print_change_data change_type); } - one = quote_path(one_name, -1, &onebuf, s->prefix); - two = quote_path(two_name, -1, &twobuf, s->prefix); + one = quote_path(one_name, s->prefix, &onebuf); + two = quote_path(two_name, s->prefix, &twobuf); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); switch (status) { @@@ -372,7 -373,7 +373,7 @@@ static void wt_status_collect_changed_c static int unmerged_mask(const char *path) { int pos, mask; - struct cache_entry *ce; + const struct cache_entry *ce; pos = cache_name_pos(path, strlen(path)); if (0 <= pos) @@@ -438,7 -439,7 +439,7 @@@ static void wt_status_collect_changes_w } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; - init_pathspec(&rev.prune_data, s->pathspec); + copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_files(&rev, 0); } @@@ -463,22 -464,20 +464,20 @@@ static void wt_status_collect_changes_i rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; - init_pathspec(&rev.prune_data, s->pathspec); + copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_index(&rev, 1); } static void wt_status_collect_changes_initial(struct wt_status *s) { - struct pathspec pathspec; int i; - init_pathspec(&pathspec, s->pathspec); for (i = 0; i < active_nr; i++) { struct string_list_item *it; struct wt_status_change_data *d; - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; - if (!ce_path_match(ce, &pathspec)) + if (!ce_path_match(ce, &s->pathspec)) continue; it = string_list_insert(&s->change, ce->name); d = it->util; @@@ -493,7 -492,6 +492,6 @@@ else d->index_status = DIFF_STATUS_ADDED; } - free_pathspec(&pathspec); } static void wt_status_collect_untracked(struct wt_status *s) @@@ -516,12 -514,12 +514,12 @@@ dir.flags |= DIR_SHOW_IGNORED_TOO; setup_standard_excludes(&dir); - fill_directory(&dir, s->pathspec); + fill_directory(&dir, &s->pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; if (cache_name_is_other(ent->name, ent->len) && - match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL)) string_list_insert(&s->untracked, ent->name); free(ent); } @@@ -529,7 -527,7 +527,7 @@@ for (i = 0; i < dir.ignored_nr; i++) { struct dir_entry *ent = dir.ignored[i]; if (cache_name_is_other(ent->name, ent->len) && - match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL)) + match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL)) string_list_insert(&s->ignored, ent->name); free(ent); } @@@ -707,7 -705,8 +705,7 @@@ static void wt_status_print_other(struc struct string_list_item *it; const char *path; it = &(l->items[i]); - path = quote_path(it->string, strlen(it->string), - &buf, s->prefix); + path = quote_path(it->string, s->prefix, &buf); if (column_active(s->colopts)) { string_list_append(&output, path); continue; @@@ -1292,7 -1291,7 +1290,7 @@@ static void wt_shortstatus_unmerged(str } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); printf(" %s\n", one); strbuf_release(&onebuf); } @@@ -1320,7 -1319,7 +1318,7 @@@ static void wt_shortstatus_status(struc struct strbuf onebuf = STRBUF_INIT; const char *one; if (d->head_path) { - one = quote_path(d->head_path, -1, &onebuf, s->prefix); + one = quote_path(d->head_path, s->prefix, &onebuf); if (*one != '"' && strchr(one, ' ') != NULL) { putchar('"'); strbuf_addch(&onebuf, '"'); @@@ -1329,7 -1328,7 +1327,7 @@@ printf("%s -> ", one); strbuf_release(&onebuf); } - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); if (*one != '"' && strchr(one, ' ') != NULL) { putchar('"'); strbuf_addch(&onebuf, '"'); @@@ -1348,7 -1347,7 +1346,7 @@@ static void wt_shortstatus_other(struc } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, -1, &onebuf, s->prefix); + one = quote_path(it->string, s->prefix, &onebuf); color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); printf(" %s\n", one); strbuf_release(&onebuf); diff --combined wt-status.h index fb7152e187,8463672ed3..9966c13deb --- a/wt-status.h +++ b/wt-status.h @@@ -44,7 -44,7 +44,7 @@@ struct wt_status int is_initial; char *branch; const char *reference; - const char **pathspec; + struct pathspec pathspec; int verbose; int amend; enum commit_whence whence; @@@ -96,9 -96,9 +96,9 @@@ void wt_status_get_state(struct wt_stat void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); -void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...) - ; -void status_printf(struct wt_status *s, const char *color, const char *fmt, ...) - ; +__attribute__((format (printf, 3, 4))) +void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...); +__attribute__((format (printf, 3, 4))) +void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); #endif /* STATUS_H */