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
  ...
This commit is contained in:
Junio C Hamano 2013-09-09 14:36:15 -07:00
commit b02f5aeda6
62 changed files with 1886 additions and 876 deletions

View File

@ -13,7 +13,7 @@ SYNOPSIS
DESCRIPTION
-----------
This script is used to move or rename a file, directory or symlink.
Move or rename a file, directory or symlink.
git mv [-v] [-f] [-n] [-k] <source> <destination>
git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
@ -44,6 +44,14 @@ OPTIONS
--verbose::
Report the names of files as they are moved.
SUBMODULES
----------
Moving a submodule using a gitfile (which means they were cloned
with a Git version 1.7.8 or newer) will update the gitfile and
core.worktree setting to make the submodule work in the new location.
It also will attempt to update the submodule.<name>.path setting in
the linkgit:gitmodules[5] file and stage that file (unless -n is used).
GIT
---
Part of the linkgit:git[1] suite

View File

@ -134,14 +134,16 @@ use the following command:
git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
----------------
Submodules
~~~~~~~~~~
SUBMODULES
----------
Only submodules using a gitfile (which means they were cloned
with a Git version 1.7.8 or newer) will be removed from the work
tree, as their repository lives inside the .git directory of the
superproject. If a submodule (or one of those nested inside it)
still uses a .git directory, `git rm` will fail - no matter if forced
or not - to protect the submodule's history.
or not - to protect the submodule's history. If it exists the
submodule.<name> section in the linkgit:gitmodules[5] file will also
be removed and that file will be staged (unless --cached or -n are used).
A submodule is considered up-to-date when the HEAD is the same as
recorded in the index, no tracked files are modified and no untracked

View File

@ -457,10 +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
------------
@ -867,6 +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]]
------------------------

View File

@ -322,10 +322,54 @@ and a close parentheses `)`, and the remainder is the pattern to match
against the path.
+
The "magic signature" consists of an ASCII symbol that is not
alphanumeric. Currently only the slash `/` is recognized as a
"magic signature": it makes the pattern match from the root of
the working tree, even when you are running the command from
inside a subdirectory.
alphanumeric.
+
--
top `/`;;
The magic word `top` (mnemonic: `/`) makes the pattern match
from the root of the working tree, even when you are running
the command from inside a subdirectory.
literal;;
Wildcards in the pattern such as `*` or `?` are treated
as literal characters.
icase;;
Case insensitive match.
glob;;
Git treats the pattern as a shell glob suitable for
consumption by fnmatch(3) with the FNM_PATHNAME flag:
wildcards in the pattern will not match a / in the pathname.
For example, "Documentation/{asterisk}.html" matches
"Documentation/git.html" but not "Documentation/ppc/ppc.html"
or "tools/perf/Documentation/perf.html".
+
Two consecutive asterisks ("`**`") in patterns matched against
full pathname may have special meaning:
- A leading "`**`" followed by a slash means match in all
directories. For example, "`**/foo`" matches file or directory
"`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar"
matches file or directory "`bar`" anywhere that is directly
under directory "`foo`".
- A trailing "/**" matches everything inside. For example,
"abc/**" matches all files inside directory "abc", relative
to the location of the `.gitignore` file, with infinite depth.
- A slash followed by two consecutive asterisks then a slash
matches zero or more directories. For example, "`a/**/b`"
matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on.
- Other consecutive asterisks are considered invalid.
+
Glob magic is incompatible with literal magic.
--
+
Currently only the slash `/` is recognized as the "magic signature",
but it is envisioned that we will support more types of magic in later
versions of Git.
+
A pathspec with only a colon means "there is no pathspec". This form
should not be combined with other pathspec.

View File

@ -8,6 +8,42 @@ Talk about
* is_inside_git_dir()
* is_inside_work_tree()
* setup_work_tree()
* get_pathspec()
(Dscho)
Pathspec
--------
See glossary-context.txt for the syntax of pathspec. In memory, a
pathspec set is represented by "struct pathspec" and is prepared by
parse_pathspec(). This function takes several arguments:
- magic_mask specifies what features that are NOT supported by the
following code. If a user attempts to use such a feature,
parse_pathspec() can reject it early.
- flags specifies other things that the caller wants parse_pathspec to
perform.
- prefix and args come from cmd_* functions
get_pathspec() is obsolete and should never be used in new code.
parse_pathspec() helps catch unsupported features and reject them
politely. At a lower level, different pathspec-related functions may
not support the same set of features. Such pathspec-sensitive
functions are guarded with GUARD_PATHSPEC(), which will die in an
unfriendly way when an unsupported feature is requested.
The command designers are supposed to make sure that GUARD_PATHSPEC()
never dies. They have to make sure all unsupported features are caught
by parse_pathspec(), not by GUARD_PATHSPEC. grepping GUARD_PATHSPEC()
should give the designers all pathspec-sensitive codepaths and what
features they support.
A similar process is applied when a new pathspec magic is added. The
designer lifts the GUARD_PATHSPEC restriction in the functions that
support the new magic. At the same time (s)he has to make sure this
new feature will be caught at parse_pathspec() in commands that cannot
handle the new magic in some cases. grepping parse_pathspec() should
help.

View File

@ -151,7 +151,6 @@ int write_archive_entries(struct archiver_args *args,
struct archiver_context context;
struct unpack_trees_options opts;
struct tree_desc t;
struct pathspec pathspec;
int err;
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
@ -186,10 +185,8 @@ int write_archive_entries(struct archiver_args *args,
git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
}
init_pathspec(&pathspec, args->pathspec);
err = read_tree_recursive(args->tree, "", 0, 0, &pathspec,
err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
write_archive_entry, &context);
free_pathspec(&pathspec);
if (err == READ_TREE_RECURSIVE)
err = 0;
return err;
@ -222,7 +219,7 @@ static int path_exists(struct tree *tree, const char *path)
struct pathspec pathspec;
int ret;
init_pathspec(&pathspec, paths);
parse_pathspec(&pathspec, 0, 0, "", paths);
ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
free_pathspec(&pathspec);
return ret != 0;
@ -231,11 +228,18 @@ static int path_exists(struct tree *tree, const char *path)
static void parse_pathspec_arg(const char **pathspec,
struct archiver_args *ar_args)
{
ar_args->pathspec = pathspec = get_pathspec("", pathspec);
/*
* must be consistent with parse_pathspec in path_exists()
* Also if pathspec patterns are dependent, we're in big
* trouble as we test each one separately
*/
parse_pathspec(&ar_args->pathspec, 0,
PATHSPEC_PREFER_FULL,
"", pathspec);
if (pathspec) {
while (*pathspec) {
if (**pathspec && !path_exists(ar_args->tree, *pathspec))
die("path not found: %s", *pathspec);
die(_("pathspec '%s' did not match any files"), *pathspec);
pathspec++;
}
}

View File

@ -1,6 +1,8 @@
#ifndef ARCHIVE_H
#define ARCHIVE_H
#include "pathspec.h"
struct archiver_args {
const char *base;
size_t baselen;
@ -8,7 +10,7 @@ struct archiver_args {
const unsigned char *commit_sha1;
const struct commit *commit;
time_t time;
const char **pathspec;
struct pathspec pathspec;
unsigned int verbose : 1;
unsigned int worktree_attributes : 1;
unsigned int convert : 1;

View File

@ -166,14 +166,16 @@ static void update_callback(struct diff_queue_struct *q,
}
}
static void update_files_in_cache(const char *prefix, const char **pathspec,
static void update_files_in_cache(const char *prefix,
const struct pathspec *pathspec,
struct update_callback_data *data)
{
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
init_pathspec(&rev.prune_data, pathspec);
if (pathspec)
copy_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = data;
@ -181,7 +183,8 @@ static void update_files_in_cache(const char *prefix, const char **pathspec,
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
}
int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
int add_files_to_cache(const char *prefix,
const struct pathspec *pathspec, int flags)
{
struct update_callback_data data;
@ -192,23 +195,21 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
}
#define WARN_IMPLICIT_DOT (1u << 0)
static char *prune_directory(struct dir_struct *dir, const char **pathspec,
static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
int prefix, unsigned flag)
{
char *seen;
int i, specs;
int i;
struct dir_entry **src, **dst;
for (specs = 0; pathspec[specs]; specs++)
/* nothing */;
seen = xcalloc(specs, 1);
seen = xcalloc(pathspec->nr, 1);
src = dst = dir->entries;
i = dir->nr;
while (--i >= 0) {
struct dir_entry *entry = *src++;
if (match_pathspec(pathspec, entry->name, entry->len,
prefix, seen))
if (match_pathspec_depth(pathspec, entry->name, entry->len,
prefix, seen))
*dst++ = entry;
else if (flag & WARN_IMPLICIT_DOT)
/*
@ -222,72 +223,33 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec,
warn_pathless_add();
}
dir->nr = dst - dir->entries;
add_pathspec_matches_against_index(pathspec, seen, specs);
add_pathspec_matches_against_index(pathspec, seen);
return seen;
}
/*
* Checks the index to see whether any path in pathspec refers to
* something inside a submodule. If so, dies with an error message.
*/
static void treat_gitlinks(const char **pathspec)
{
int i;
if (!pathspec || !*pathspec)
return;
for (i = 0; pathspec[i]; i++)
pathspec[i] = check_path_for_gitlink(pathspec[i]);
}
static void refresh(int verbose, const char **pathspec)
static void refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
int i, specs;
int i;
for (specs = 0; pathspec[specs]; specs++)
/* nothing */;
seen = xcalloc(specs, 1);
seen = xcalloc(pathspec->nr, 1);
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
pathspec, seen, _("Unstaged changes after refreshing the index:"));
for (i = 0; i < specs; i++) {
for (i = 0; i < pathspec->nr; i++) {
if (!seen[i])
die(_("pathspec '%s' did not match any files"), pathspec[i]);
die(_("pathspec '%s' did not match any files"),
pathspec->items[i].match);
}
free(seen);
}
/*
* Normalizes argv relative to prefix, via get_pathspec(), and then
* runs die_if_path_beyond_symlink() on each path in the normalized
* list.
*/
static const char **validate_pathspec(const char **argv, const char *prefix)
{
const char **pathspec = get_pathspec(prefix, argv);
if (pathspec) {
const char **p;
for (p = pathspec; *p; p++) {
die_if_path_beyond_symlink(*p, prefix);
}
}
return pathspec;
}
int run_add_interactive(const char *revision, const char *patch_mode,
const char **pathspec)
const struct pathspec *pathspec)
{
int status, ac, pc = 0;
int status, ac, i;
const char **args;
if (pathspec)
while (pathspec[pc])
pc++;
args = xcalloc(sizeof(const char *), (pc + 5));
args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
ac = 0;
args[ac++] = "add--interactive";
if (patch_mode)
@ -295,11 +257,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,
if (revision)
args[ac++] = revision;
args[ac++] = "--";
if (pc) {
memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
ac += pc;
}
args[ac] = NULL;
for (i = 0; i < pathspec->nr; i++)
/* pass original pathspec, to be re-parsed */
args[ac++] = pathspec->items[i].original;
status = run_command_v_opt(args, RUN_GIT_CMD);
free(args);
@ -308,17 +268,23 @@ int run_add_interactive(const char *revision, const char *patch_mode,
int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
const char **pathspec = NULL;
struct pathspec pathspec;
if (argc) {
pathspec = validate_pathspec(argv, prefix);
if (!pathspec)
return -1;
}
/*
* 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)
@ -446,7 +412,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
{
int exit_status = 0;
int newfd;
const char **pathspec;
struct pathspec pathspec;
struct dir_struct dir;
int flags;
int add_new_files;
@ -527,14 +493,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0;
}
pathspec = validate_pathspec(argv, prefix);
if (read_cache() < 0)
die(_("index file corrupt"));
treat_gitlinks(pathspec);
/*
* Check the "pathspec '%s' did not match any files" block
* below before enabling new magic.
*/
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_FULL |
PATHSPEC_SYMLINK_LEADING_PATH |
PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
prefix, argv);
if (add_new_files) {
int baselen;
struct pathspec empty_pathspec;
/* Set up the default git porcelain excludes */
memset(&dir, 0, sizeof(dir));
@ -543,35 +518,49 @@ int cmd_add(int argc, const char **argv, const char *prefix)
setup_standard_excludes(&dir);
}
memset(&empty_pathspec, 0, sizeof(empty_pathspec));
/* This picks up the paths that are not tracked */
baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec);
if (pathspec)
seen = prune_directory(&dir, pathspec, baselen,
baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec);
if (pathspec.nr)
seen = prune_directory(&dir, &pathspec, baselen,
implicit_dot ? WARN_IMPLICIT_DOT : 0);
}
if (refresh_only) {
refresh(verbose, pathspec);
refresh(verbose, &pathspec);
goto finish;
}
if (implicit_dot && prefix)
refresh_cache(REFRESH_QUIET);
if (pathspec) {
if (pathspec.nr) {
int i;
if (!seen)
seen = find_pathspecs_matching_against_index(pathspec);
for (i = 0; pathspec[i]; i++) {
if (!seen[i] && pathspec[i][0]
&& !file_exists(pathspec[i])) {
seen = find_pathspecs_matching_against_index(&pathspec);
/*
* file_exists() assumes exact match
*/
GUARD_PATHSPEC(&pathspec,
PATHSPEC_FROMTOP |
PATHSPEC_LITERAL |
PATHSPEC_GLOB |
PATHSPEC_ICASE);
for (i = 0; i < pathspec.nr; i++) {
const char *path = pathspec.items[i].match;
if (!seen[i] &&
((pathspec.items[i].magic &
(PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
!file_exists(path))) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
if (is_excluded(&dir, pathspec[i], &dtype))
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
if (is_excluded(&dir, path, &dtype))
dir_add_ignored(&dir, path, pathspec.items[i].len);
} else
die(_("pathspec '%s' did not match any files"),
pathspec[i]);
pathspec.items[i].original);
}
}
free(seen);
@ -587,10 +576,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
*/
update_data.implicit_dot = prefix;
update_data.implicit_dot_len = strlen(prefix);
pathspec = NULL;
free_pathspec(&pathspec);
memset(&pathspec, 0, sizeof(pathspec));
}
update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT;
update_files_in_cache(prefix, pathspec, &update_data);
update_files_in_cache(prefix, &pathspec, &update_data);
exit_status |= !!update_data.add_errors;
if (add_new_files)

View File

@ -409,7 +409,7 @@ static struct origin *find_origin(struct scoreboard *sb,
paths[0] = origin->path;
paths[1] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC, 0, "", paths);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
@ -459,7 +459,7 @@ static struct origin *find_origin(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
diff_tree_release_paths(&diff_opts);
free_pathspec(&diff_opts.pathspec);
if (porigin) {
/*
* Create a freestanding copy that is not part of
@ -487,15 +487,12 @@ static struct origin *find_rename(struct scoreboard *sb,
struct origin *porigin = NULL;
struct diff_options diff_opts;
int i;
const char *paths[2];
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = origin->path;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
@ -517,7 +514,7 @@ static struct origin *find_rename(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
diff_tree_release_paths(&diff_opts);
free_pathspec(&diff_opts.pathspec);
return porigin;
}
@ -1065,7 +1062,6 @@ static int find_copy_in_parent(struct scoreboard *sb,
int opt)
{
struct diff_options diff_opts;
const char *paths[1];
int i, j;
int retval;
struct blame_list *blame_list;
@ -1079,8 +1075,6 @@ static int find_copy_in_parent(struct scoreboard *sb,
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
@ -1163,7 +1157,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
}
reset_scanned_flag(sb);
diff_flush(&diff_opts);
diff_tree_release_paths(&diff_opts);
free_pathspec(&diff_opts.pathspec);
return retval;
}

View File

@ -64,37 +64,45 @@ static void output_exclude(const char *path, struct exclude *exclude)
}
static int check_ignore(struct dir_struct *dir,
const char *prefix, const char **pathspec)
const char *prefix, int argc, const char **argv)
{
const char *path, *full_path;
const char *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct exclude *exclude;
struct pathspec pathspec;
if (!pathspec || !*pathspec) {
if (!argc) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
return 0;
}
/*
* check-ignore just needs paths. Magic beyond :/ is really
* irrelevant.
*/
parse_pathspec(&pathspec,
PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
PATHSPEC_SYMLINK_LEADING_PATH |
PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE |
PATHSPEC_KEEP_ORDER,
prefix, argv);
/*
* look for pathspecs matching entries in the index, since these
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
seen = find_pathspecs_matching_against_index(pathspec);
for (i = 0; pathspec[i]; i++) {
path = pathspec[i];
full_path = prefix_path(prefix, prefix
? strlen(prefix) : 0, path);
full_path = check_path_for_gitlink(full_path);
die_if_path_beyond_symlink(full_path, prefix);
seen = find_pathspecs_matching_against_index(&pathspec);
for (i = 0; i < pathspec.nr; i++) {
full_path = pathspec.items[i].match;
exclude = NULL;
if (!seen[i]) {
exclude = last_exclude_matching(dir, full_path, &dtype);
}
if (!quiet && (exclude || show_non_matching))
output_exclude(path, exclude);
output_exclude(pathspec.items[i].original, exclude);
if (exclude)
num_ignored++;
}
@ -120,7 +128,8 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
strbuf_swap(&buf, &nbuf);
}
pathspec[0] = buf.buf;
num_ignored += check_ignore(dir, prefix, (const char **)pathspec);
num_ignored += check_ignore(dir, prefix,
1, (const char **)pathspec);
maybe_flush_or_die(stdout, "check-ignore to stdout");
}
strbuf_release(&buf);
@ -166,7 +175,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (stdin_paths) {
num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
num_ignored = check_ignore(&dir, prefix, argv);
num_ignored = check_ignore(&dir, prefix, argc, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}

View File

@ -46,7 +46,7 @@ struct checkout_opts {
int branch_exists;
const char *prefix;
const char **pathspec;
struct pathspec pathspec;
struct tree *source_tree;
};
@ -83,12 +83,9 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
return 0;
}
static int read_tree_some(struct tree *tree, const char **pathspec)
static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
struct pathspec ps;
init_pathspec(&ps, pathspec);
read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
free_pathspec(&ps);
read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
@ -255,20 +252,18 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
opts->pathspec);
&opts->pathspec);
lock_file = xcalloc(1, sizeof(struct lock_file));
newfd = hold_locked_index(lock_file, 1);
if (read_cache_preload(opts->pathspec) < 0)
if (read_cache_preload(&opts->pathspec) < 0)
return error(_("corrupt index file"));
if (opts->source_tree)
read_tree_some(opts->source_tree, opts->pathspec);
read_tree_some(opts->source_tree, &opts->pathspec);
for (pos = 0; opts->pathspec[pos]; pos++)
;
ps_matched = xcalloc(1, pos);
ps_matched = xcalloc(1, opts->pathspec.nr);
/*
* Make sure all pathspecs participated in locating the paths
@ -302,12 +297,12 @@ static int checkout_paths(const struct checkout_opts *opts,
* match_pathspec() for _all_ entries when
* opts->source_tree != NULL.
*/
if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
if (match_pathspec_depth(&opts->pathspec, ce->name, ce_namelen(ce),
0, ps_matched))
ce->ce_flags |= CE_MATCHED;
}
if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
free(ps_matched);
return 1;
}
@ -1000,7 +995,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
static int checkout_branch(struct checkout_opts *opts,
struct branch_info *new)
{
if (opts->pathspec)
if (opts->pathspec.nr)
die(_("paths cannot be used with switching branches"));
if (opts->patch_mode)
@ -1151,9 +1146,18 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
}
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 +1189,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
if (opts.patch_mode || opts.pathspec)
if (opts.patch_mode || opts.pathspec.nr)
return checkout_paths(&opts, new.name);
else
return checkout_branch(&opts, &new);

View File

@ -15,6 +15,7 @@
#include "quote.h"
#include "column.h"
#include "color.h"
#include "pathspec.h"
static int force = -1; /* unset */
static int interactive;
@ -863,13 +864,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf abs_path = STRBUF_INIT;
struct dir_struct dir;
static const char **pathspec;
struct pathspec pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct exclude_list *el;
struct string_list_item *item;
const char *qname;
char *seen = NULL;
struct option options[] = {
OPT__QUIET(&quiet, N_("do not print names of files removed")),
OPT__DRY_RUN(&dry_run, N_("dry run")),
@ -925,12 +925,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
pathspec = get_pathspec(prefix, argv);
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,
prefix, argv);
fill_directory(&dir, pathspec);
if (pathspec)
seen = xmalloc(argc > 0 ? argc : 1);
fill_directory(&dir, &pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
@ -961,11 +960,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
if (pathspec) {
memset(seen, 0, argc > 0 ? argc : 1);
matches = match_pathspec(pathspec, ent->name, len,
0, seen);
}
if (pathspec.nr)
matches = match_pathspec_depth(&pathspec, ent->name,
len, 0, NULL);
if (S_ISDIR(st.st_mode)) {
if (remove_directories || (matches == MATCHED_EXACTLY)) {
@ -973,7 +970,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
string_list_append(&del_list, rel);
}
} else {
if (pathspec && !matches)
if (pathspec.nr && !matches)
continue;
rel = relative_path(ent->name, prefix, &buf);
string_list_append(&del_list, rel);
@ -1019,7 +1016,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
}
strbuf_reset(&abs_path);
}
free(seen);
strbuf_release(&abs_path);
strbuf_release(&buf);

View File

@ -202,17 +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);
@ -226,7 +224,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
if (ce->ce_flags & CE_UPDATE)
continue;
if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
continue;
item = string_list_insert(list, ce->name);
if (ce_skip_worktree(ce))
@ -298,17 +296,17 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
{
int fd;
struct string_list partial;
const char **pathspec = NULL;
struct pathspec pathspec;
char *old_index_env = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_FULL,
prefix, argv);
if (*argv)
pathspec = get_pathspec(prefix, argv);
if (read_cache_preload(pathspec) < 0)
if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
if (interactive) {
@ -350,9 +348,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* (A) if all goes well, commit the real index;
* (B) on failure, rollback the real index.
*/
if (all || (also && pathspec && *pathspec)) {
if (all || (also && pathspec.nr)) {
fd = hold_locked_index(&index_lock, 1);
add_files_to_cache(also ? prefix : NULL, pathspec, 0);
add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
@ -371,7 +369,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* and create commit from the_index.
* We still need to refresh the index here.
*/
if (!only && (!pathspec || !*pathspec)) {
if (!only && !pathspec.nr) {
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
if (active_cache_changed) {
@ -416,7 +414,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
discard_cache();
@ -1259,11 +1257,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
if (*argv)
s.pathspec = get_pathspec(prefix, argv);
parse_pathspec(&s.pathspec, 0,
PATHSPEC_PREFER_FULL,
prefix, argv);
read_cache_preload(s.pathspec);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
read_cache_preload(&s.pathspec);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)

View File

@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}

View File

@ -43,7 +43,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
usage(diff_cache_usage);
if (!cached) {
setup_work_tree();
if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}

View File

@ -140,7 +140,7 @@ static int builtin_diff_index(struct rev_info *revs,
usage(builtin_diff_usage);
if (!cached) {
setup_work_tree();
if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
@ -242,7 +242,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
@ -367,6 +367,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
}
if (rev.prune_data.nr) {
/* builtin_diff_b_f() */
GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
if (!path)
path = rev.prune_data.items[0].match;
paths += rev.prune_data.nr;

View File

@ -17,6 +17,7 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
#include "pathspec.h"
static char const * const grep_usage[] = {
N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@ -521,7 +522,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
if (exc_std)
setup_standard_excludes(&dir);
fill_directory(&dir, pathspec->raw);
fill_directory(&dir, pathspec);
for (i = 0; i < dir.nr; i++) {
const char *name = dir.entries[i]->name;
int namelen = strlen(name);
@ -629,7 +630,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
const char *show_in_pager = NULL, *default_pager = "dummy";
struct grep_opt opt;
struct object_array list = OBJECT_ARRAY_INIT;
const char **paths = NULL;
struct pathspec pathspec;
struct string_list path_list = STRING_LIST_INIT_NODUP;
int i;
@ -856,8 +856,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
verify_filename(prefix, argv[j], j == i);
}
paths = get_pathspec(prefix, argv + i);
init_pathspec(&pathspec, paths);
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD |
(opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
prefix, argv + i);
pathspec.max_depth = opt.max_depth;
pathspec.recursive = 1;

View File

@ -503,7 +503,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
init_grep_defaults();
git_config(git_log_config, NULL);
init_pathspec(&match_all, NULL);
memset(&match_all, 0, sizeof(match_all));
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;

View File

@ -13,6 +13,7 @@
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
#include "pathspec.h"
static int abbrev;
static int show_deleted;
@ -30,7 +31,7 @@ static int debug_mode;
static const char *prefix;
static int max_prefix_len;
static int prefix_len;
static const char **pathspec;
static struct pathspec pathspec;
static int error_unmatch;
static char *ps_matched;
static const char *with_tree;
@ -63,7 +64,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
if (len >= ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
if (!match_pathspec_depth(&pathspec, ent->name, ent->len, len, ps_matched))
return;
fputs(tag, stdout);
@ -138,7 +139,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce)
if (len >= ce_namelen(ce))
die("git ls-files: internal error - cache entry not superset of prefix");
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), len, ps_matched))
return;
if (tag && *tag && show_valid_bit &&
@ -194,7 +195,7 @@ static void show_ru_info(void)
len = strlen(path);
if (len < max_prefix_len)
continue; /* outside of the prefix */
if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
if (!match_pathspec_depth(&pathspec, path, len, max_prefix_len, ps_matched))
continue; /* uninterested */
for (i = 0; i < 3; i++) {
if (!ui->mode[i])
@ -219,7 +220,7 @@ static void show_files(struct dir_struct *dir)
/* For cached/deleted files we don't need to even do the readdir */
if (show_others || show_killed) {
fill_directory(dir, pathspec);
fill_directory(dir, &pathspec);
if (show_others)
show_other_files(dir);
if (show_killed)
@ -287,21 +288,6 @@ static void prune_cache(const char *prefix)
active_nr = last;
}
static void strip_trailing_slash_from_submodules(void)
{
const char **p;
for (p = pathspec; *p != NULL; p++) {
int len = strlen(*p), pos;
if (len < 1 || (*p)[len - 1] != '/')
continue;
pos = cache_name_pos(*p, len - 1);
if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
*p = xstrndup(*p, len - 1);
}
}
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
@ -333,13 +319,12 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
}
if (prefix) {
static const char *(matchbuf[2]);
matchbuf[0] = prefix;
matchbuf[1] = NULL;
init_pathspec(&pathspec, matchbuf);
pathspec.items[0].nowildcard_len = pathspec.items[0].len;
static const char *(matchbuf[1]);
matchbuf[0] = NULL;
parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
init_pathspec(&pathspec, NULL);
memset(&pathspec, 0, sizeof(pathspec));
if (read_tree(tree, 1, &pathspec))
die("unable to read tree entries %s", tree_name);
@ -364,15 +349,16 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
}
}
int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
int report_path_error(const char *ps_matched,
const struct pathspec *pathspec,
const char *prefix)
{
/*
* Make sure all pathspec matched; otherwise it is an error.
*/
struct strbuf sb = STRBUF_INIT;
const char *name;
int num, errors = 0;
for (num = 0; pathspec[num]; num++) {
for (num = 0; num < pathspec->nr; num++) {
int other, found_dup;
if (ps_matched[num])
@ -380,13 +366,16 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
/*
* The caller might have fed identical pathspec
* twice. Do not barf on such a mistake.
* FIXME: parse_pathspec should have eliminated
* duplicate pathspec.
*/
for (found_dup = other = 0;
!found_dup && pathspec[other];
!found_dup && other < pathspec->nr;
other++) {
if (other == num || !ps_matched[other])
continue;
if (!strcmp(pathspec[other], pathspec[num]))
if (!strcmp(pathspec->items[other].original,
pathspec->items[num].original))
/*
* Ok, we have a match already.
*/
@ -395,9 +384,8 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
if (found_dup)
continue;
name = quote_path_relative(pathspec[num], prefix, &sb);
error("pathspec '%s' did not match any file(s) known to git.",
name);
pathspec->items[num].original);
errors++;
}
strbuf_release(&sb);
@ -555,23 +543,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
pathspec = get_pathspec(prefix, argv);
/* be nice with submodule paths ending in a slash */
if (pathspec)
strip_trailing_slash_from_submodules();
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD |
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
prefix, argv);
/* Find common prefix for all pathspec's */
max_prefix = common_prefix(pathspec);
max_prefix = common_prefix(&pathspec);
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */
if (pathspec && error_unmatch) {
int num;
for (num = 0; pathspec[num]; num++)
;
ps_matched = xcalloc(1, num);
}
if (pathspec.nr && error_unmatch)
ps_matched = xcalloc(1, pathspec.nr);
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
@ -598,7 +581,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (ps_matched) {
int bad;
bad = report_path_error(ps_matched, pathspec, prefix);
bad = report_path_error(ps_matched, &pathspec, prefix);
if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");

View File

@ -10,6 +10,7 @@
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
#include "pathspec.h"
static int line_termination = '\n';
#define LS_RECURSIVE 1
@ -35,7 +36,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
if (ls_options & LS_RECURSIVE)
return 1;
s = pathspec.raw;
s = pathspec._raw;
if (!s)
return 0;
@ -166,7 +167,15 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
if (get_sha1(argv[0], sha1))
die("Not a valid object name %s", argv[0]);
init_pathspec(&pathspec, get_pathspec(prefix, argv + 1));
/*
* show_recursive() rolls its own matching code and is
* generally ignorant of 'struct pathspec'. The magic mask
* cannot be lifted until it is converted to use
* match_pathspec_depth() or tree_entry_interesting()
*/
parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
PATHSPEC_PREFER_CWD,
prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
pathspec.items[i].nowildcard_len = pathspec.items[i].len;
pathspec.has_wildcard = 0;

View File

@ -9,14 +9,16 @@
#include "cache-tree.h"
#include "string-list.h"
#include "parse-options.h"
#include "submodule.h"
static const char * const builtin_mv_usage[] = {
N_("git mv [options] <source>... <destination>"),
NULL
};
static const char **copy_pathspec(const char *prefix, const char **pathspec,
int count, int base_name)
static const char **internal_copy_pathspec(const char *prefix,
const char **pathspec,
int count, int base_name)
{
int i;
const char **result = xmalloc((count + 1) * sizeof(const char *));
@ -56,7 +58,7 @@ 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")),
@ -65,11 +67,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
OPT_END(),
};
const char **source, **destination, **dest_path;
const char **source, **destination, **dest_path, **submodule_gitfile;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_mv_options,
@ -81,17 +84,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
source = copy_pathspec(prefix, argv, argc, 0);
source = internal_copy_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0);
submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
destination = copy_pathspec(dest_path[0], argv, argc, 1);
destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
destination = copy_pathspec(dest_path[0], argv, argc, 1);
destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
} else {
if (argc != 1)
die("destination '%s' is not a directory", dest_path[0]);
@ -117,55 +121,68 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
&& lstat(dst, &st) == 0)
bad = _("cannot move directory over file");
else if (src_is_dir) {
const char *src_w_slash = add_slash(src);
int len_w_slash = length + 1;
int first, last;
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;
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 = 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));
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);
dst = add_slash(dst);
dst_len = strlen(dst);
if (last - first < 1)
bad = _("source directory is empty");
else {
int j, dst_len;
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 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
int pos;
if (show_only || verbose)
printf(_("Renaming %s to %s\n"), src, dst);
if (!show_only && mode != INDEX &&
rename(src, dst) < 0 && !ignore_errors)
die_errno (_("renaming '%s' failed"), src);
if (!show_only && mode != INDEX) {
if (rename(src, dst) < 0 && !ignore_errors)
die_errno (_("renaming '%s' failed"), src);
if (submodule_gitfile[i])
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 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
rename_cache_entry_at(pos, dst);
}
if (gitmodules_modified)
stage_updated_gitmodules();
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))

View File

@ -6,6 +6,7 @@
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
#include "pathspec.h"
static const char * const rerere_usage[] = {
N_("git rerere [clear | forget path... | status | remaining | diff | gc]"),
@ -68,11 +69,12 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
return rerere(flags);
if (!strcmp(argv[0], "forget")) {
const char **pathspec;
struct pathspec pathspec;
if (argc < 2)
warning("'git rerere forget' without paths is deprecated");
pathspec = get_pathspec(prefix, argv + 1);
return rerere_forget(pathspec);
parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
prefix, argv + 1);
return rerere_forget(&pathspec);
}
fd = setup_rerere(&merge_rr, flags);

View File

@ -133,12 +133,13 @@ static void update_index_from_diff(struct diff_queue_struct *q,
}
}
static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
static int read_from_tree(const struct pathspec *pathspec,
unsigned char *tree_sha1)
{
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
diff_tree_setup_paths(pathspec, &opt);
copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
@ -147,7 +148,7 @@ static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
return 1;
diffcore_std(&opt);
diff_flush(&opt);
diff_tree_release_paths(&opt);
free_pathspec(&opt.pathspec);
return 0;
}
@ -174,7 +175,10 @@ static void die_if_unmerged_cache(int reset_type)
}
static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
static void parse_args(struct pathspec *pathspec,
const char **argv, const char *prefix,
int patch_mode,
const char **rev_ret)
{
const char *rev = "HEAD";
unsigned char unused[20];
@ -216,7 +220,10 @@ static const char **parse_args(const char **argv, const char *prefix, const char
}
}
*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 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
int patch_mode = 0, unborn;
const char *rev;
unsigned char sha1[20];
const char **pathspec = NULL;
struct pathspec pathspec;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_SET_INT(0, "mixed", &reset_type,
@ -266,13 +273,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, git_reset_usage,
PARSE_OPT_KEEP_DASHDASH);
pathspec = parse_args(argv, prefix, &rev);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);
unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
} else if (!pathspec) {
} else if (!pathspec.nr) {
struct commit *commit;
if (get_sha1_committish(rev, sha1))
die(_("Failed to resolve '%s' as a valid revision."), rev);
@ -293,13 +300,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
return run_add_interactive(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 -- <paths>' instead."));
else if (reset_type != NONE)
@ -326,7 +333,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
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 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
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 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
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;

View File

@ -11,6 +11,7 @@
#include "parse-options.h"
#include "string-list.h"
#include "submodule.h"
#include "pathspec.h"
static const char * const builtin_rm_usage[] = {
N_("git rm [options] [--] <file>..."),
@ -277,10 +278,11 @@ static struct option builtin_rm_options[] = {
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 +313,45 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
}
}
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++) {
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)
exit(0);
}
if (!seen_any)
exit(0);
/*
* If not forced, the file, the index and the HEAD (if exists)
@ -392,13 +399,15 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* in the middle)
*/
if (!index_only) {
int removed = 0;
int removed = 0, gitmodules_modified = 0;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
if (is_empty_dir(path)) {
if (!rmdir(path)) {
removed = 1;
if (!remove_path_from_gitmodules(path))
gitmodules_modified = 1;
continue;
}
} else {
@ -406,9 +415,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
strbuf_addstr(&buf, path);
if (!remove_dir_recursively(&buf, 0)) {
removed = 1;
if (!remove_path_from_gitmodules(path))
gitmodules_modified = 1;
strbuf_release(&buf);
continue;
}
} else if (!file_exists(path))
/* Submodule was removed by user */
if (!remove_path_from_gitmodules(path))
gitmodules_modified = 1;
strbuf_release(&buf);
/* Fallthrough and let remove_path() fail. */
}
@ -420,6 +434,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!removed)
die_errno("git rm: '%s'", path);
}
if (gitmodules_modified)
stage_updated_gitmodules();
}
if (active_cache_changed) {

View File

@ -11,6 +11,7 @@
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
#include "pathspec.h"
/*
* Default to not allowing changes to the list of files. The
@ -546,10 +547,11 @@ static int do_reupdate(int ac, const char **av,
*/
int pos;
int has_head = 1;
const char **paths = get_pathspec(prefix, av + 1);
struct pathspec pathspec;
init_pathspec(&pathspec, paths);
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,
prefix, av + 1);
if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial

35
cache.h
View File

@ -189,6 +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 @@ static inline enum object_type object_type(unsigned int mode)
#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 @@ 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,
@ -449,7 +455,7 @@ extern void sanitize_stdfds(void);
/* 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 *);
@ -491,28 +497,8 @@ extern void *read_blob_data_from_index(struct index_state *, const char *, unsig
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 +526,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
#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;
@ -762,6 +748,7 @@ 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 *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);
@ -1243,7 +1230,7 @@ void packet_trace_identity(const char *prog);
* 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 +1264,7 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
#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);

View File

@ -1306,7 +1306,7 @@ void diff_tree_combined(const unsigned char *sha1,
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,7 +1378,7 @@ void diff_tree_combined(const unsigned char *sha1,
free(tmp);
}
diff_tree_release_paths(&diffopts);
free_pathspec(&diffopts.pathspec);
}
void diff_tree_combined_merge(const struct commit *commit, int dense,

View File

@ -208,7 +208,7 @@ int in_merge_bases_many(struct commit *, int, 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)
{

View File

@ -474,7 +474,6 @@ static int diff_cache(struct rev_info *revs,
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 +499,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
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))

3
diff.h
View File

@ -5,6 +5,7 @@
#define DIFF_H
#include "tree-walk.h"
#include "pathspec.h"
struct rev_info;
struct diff_options;
@ -182,8 +183,6 @@ const char *diff_line_prefix(struct diff_options *);
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,

317
dir.c
View File

@ -11,6 +11,7 @@
#include "dir.h"
#include "refs.h"
#include "wildmatch.h"
#include "pathspec.h"
struct path_simplify {
int len;
@ -51,26 +52,32 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
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 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen,
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;
/*
* ":(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);
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)))
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 @@ static size_t common_prefix_len(const char **pathspec)
* 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 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
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 @@ int within_depth(const char *name, int namelen,
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 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
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 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
}
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 @@ int match_pathspec_depth(const struct pathspec *ps,
{
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 @@ int match_pathspec_depth(const struct pathspec *ps,
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 @@ int match_pathspec_depth(const struct pathspec *ps,
/*
* 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 @@ static int simple_length(const char *match)
}
}
static int no_wildcard(const char *string)
int no_wildcard(const char *string)
{
return string[simple_length(string)] == '\0';
}
@ -1381,14 +1338,25 @@ static int treat_leading_path(struct dir_struct *dir,
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 @@ 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.

18
dir.h
View File

@ -128,15 +128,16 @@ struct dir_struct {
#define MATCHED_RECURSIVELY 1
#define MATCHED_FNMATCH 2
#define MATCHED_EXACTLY 3
extern char *common_prefix(const char **pathspec);
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
extern int simple_length(const char *match);
extern int no_wildcard(const char *string);
extern char *common_prefix(const struct pathspec *pathspec);
extern int match_pathspec_depth(const struct pathspec *pathspec,
const char *name, int namelen,
int prefix, char *seen);
extern int within_depth(const char *name, int namelen, int depth, int max_depth);
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
extern int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec);
extern int read_directory(struct dir_struct *, const char *path, int len, const struct pathspec *pathspec);
extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename,
int *dtype, struct exclude_list *el);
@ -198,10 +199,9 @@ extern int fnmatch_icase(const char *pattern, const char *string, int flags);
/*
* The prefix part of pattern must not contains wildcards.
*/
#define GFNM_PATHNAME 1 /* similar to FNM_PATHNAME */
#define GFNM_ONESTAR 2 /* there is only _one_ wildcard, a star */
extern int git_fnmatch(const char *pattern, const char *string,
int flags, int prefix);
struct pathspec_item;
extern int git_fnmatch(const struct pathspec_item *item,
const char *pattern, const char *string,
int prefix);
#endif

12
git.c
View File

@ -147,6 +147,18 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
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)--;

View File

@ -760,7 +760,7 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list
r = r->next;
}
paths[count] = NULL;
init_pathspec(&rev->diffopt.pathspec, paths);
parse_pathspec(&rev->diffopt.pathspec, 0, 0, "", paths);
free(paths);
}
}

View File

@ -298,7 +298,7 @@ static int get_files_dirs(struct merge_options *o, struct tree *tree)
{
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;

View File

@ -170,7 +170,7 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o,
sha1_to_hex(mp->remote));
}
diff_flush(&opt);
diff_tree_release_paths(&opt);
free_pathspec(&opt.pathspec);
*num_changes = len;
return changes;
@ -256,7 +256,7 @@ static void diff_tree_local(struct notes_merge_options *o,
sha1_to_hex(mp->local));
}
diff_flush(&opt);
diff_tree_release_paths(&opt);
free_pathspec(&opt.pathspec);
}
static void check_notes_merge_worktree(struct notes_merge_options *o)

15
path.c
View File

@ -543,8 +543,14 @@ const char *relative_path(const char *in, const char *prefix,
*
* 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 +625,18 @@ int normalize_path_copy(char *dst, const char *src)
/* 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

View File

@ -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 @@ void add_pathspec_matches_against_index(const char **pathspec,
* 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++) {
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 @@ void add_pathspec_matches_against_index(const char **pathspec,
* 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" },
*
*/
const char *check_path_for_gitlink(const char *path)
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.
*/
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';
}
return path;
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;
}
/* 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;
}

View File

@ -1,8 +1,92 @@
#ifndef PATHSPEC_H
#define PATHSPEC_H
extern char *find_pathspecs_matching_against_index(const char **pathspec);
extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs);
/* Pathspec magic */
#define PATHSPEC_FROMTOP (1<<0)
#define PATHSPEC_MAXDEPTH (1<<1)
#define PATHSPEC_LITERAL (1<<2)
#define PATHSPEC_GLOB (1<<3)
#define PATHSPEC_ICASE (1<<4)
#define PATHSPEC_ALL_MAGIC \
(PATHSPEC_FROMTOP | \
PATHSPEC_MAXDEPTH | \
PATHSPEC_LITERAL | \
PATHSPEC_GLOB | \
PATHSPEC_ICASE)
#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies 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;
unsigned magic;
int max_depth;
struct pathspec_item {
const char *match;
const char *original;
unsigned magic;
int len, prefix;
int nowildcard_len;
int flags;
} *items;
};
#define GUARD_PATHSPEC(ps, mask) \
do { \
if ((ps)->magic & ~(mask)) \
die("BUG:%s:%d: unsupported magic %x", \
__FILE__, __LINE__, (ps)->magic & ~(mask)); \
} while (0)
/* parse_pathspec flags */
#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */
#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */
#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */
/* strip the trailing slash if the given path is a gitlink */
#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3)
/* die if a symlink is part of the given path's directory */
#define PATHSPEC_SYMLINK_LEADING_PATH (1<<4)
/*
* This is like a combination of ..LEADING_PATH and .._SLASH_CHEAP
* (but not the same): it strips the trailing slash if the given path
* is a gitlink but also checks and dies if gitlink is part of the
* leading path (i.e. the given path goes beyond a submodule). It's
* safer than _SLASH_CHEAP and also more expensive.
*/
#define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5)
#define PATHSPEC_PREFIX_ORIGIN (1<<6)
#define PATHSPEC_KEEP_ORDER (1<<7)
extern void parse_pathspec(struct pathspec *pathspec,
unsigned magic_mask,
unsigned flags,
const char *prefix,
const char **args);
extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
extern void free_pathspec(struct pathspec *);
static inline int ps_strncmp(const struct pathspec_item *item,
const char *s1, const char *s2, size_t n)
{
if (item->magic & PATHSPEC_ICASE)
return strncasecmp(s1, s2, n);
else
return strncmp(s1, s2, n);
}
static inline int ps_strcmp(const struct pathspec_item *item,
const char *s1, const char *s2)
{
if (item->magic & PATHSPEC_ICASE)
return strcasecmp(s1, s2);
else
return strcmp(s1, s2);
}
extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec);
extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen);
extern const char *check_path_for_gitlink(const char *path);
extern void die_if_path_beyond_symlink(const char *path, const char *prefix);

View File

@ -2,9 +2,11 @@
* Copyright (C) 2008 Linus Torvalds
*/
#include "cache.h"
#include "pathspec.h"
#ifdef NO_PTHREADS
static void preload_index(struct index_state *index, const char **pathspec)
static void preload_index(struct index_state *index,
const struct pathspec *pathspec)
{
; /* nothing */
}
@ -24,7 +26,7 @@ static void preload_index(struct index_state *index, const char **pathspec)
struct thread_data {
pthread_t pthread;
struct index_state *index;
const char **pathspec;
struct pathspec pathspec;
int offset, nr;
};
@ -35,9 +37,7 @@ static void *preload_thread(void *_data)
struct index_state *index = p->index;
struct cache_entry **cep = index->cache + p->offset;
struct cache_def cache;
struct pathspec pathspec;
init_pathspec(&pathspec, p->pathspec);
memset(&cache, 0, sizeof(cache));
nr = p->nr;
if (nr + p->offset > index->cache_nr)
@ -53,7 +53,7 @@ static void *preload_thread(void *_data)
continue;
if (ce_uptodate(ce))
continue;
if (!ce_path_match(ce, &pathspec))
if (!ce_path_match(ce, &p->pathspec))
continue;
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
continue;
@ -63,11 +63,11 @@ static void *preload_thread(void *_data)
continue;
ce_mark_uptodate(ce);
} while (--nr > 0);
free_pathspec(&pathspec);
return NULL;
}
static void preload_index(struct index_state *index, const char **pathspec)
static void preload_index(struct index_state *index,
const struct pathspec *pathspec)
{
int threads, i, work, offset;
struct thread_data data[MAX_PARALLEL];
@ -82,10 +82,12 @@ static void preload_index(struct index_state *index, const char **pathspec)
threads = MAX_PARALLEL;
offset = 0;
work = DIV_ROUND_UP(index->cache_nr, threads);
memset(&data, 0, sizeof(data));
for (i = 0; i < threads; i++) {
struct thread_data *p = data+i;
p->index = index;
p->pathspec = pathspec;
if (pathspec)
copy_pathspec(&p->pathspec, pathspec);
p->offset = offset;
p->nr = work;
offset += work;
@ -100,7 +102,8 @@ static void preload_index(struct index_state *index, const char **pathspec)
}
#endif
int read_index_preload(struct index_state *index, const char **pathspec)
int read_index_preload(struct index_state *index,
const struct pathspec *pathspec)
{
int retval = read_index(index);

View File

@ -1114,7 +1114,8 @@ static void show_file(const char * fmt, const char * name, int in_porcelain,
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 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
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)) {

View File

@ -6,6 +6,7 @@
#include "resolve-undo.h"
#include "ll-merge.h"
#include "attr.h"
#include "pathspec.h"
#define RESOLVED 0
#define PUNTED 1
@ -656,7 +657,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
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 @@ int rerere_forget(const char **pathspec)
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);
}

View File

@ -3,6 +3,8 @@
#include "string-list.h"
struct pathspec;
#define RERERE_AUTOUPDATE 01
#define RERERE_NOAUTOUPDATE 02
@ -16,7 +18,7 @@ extern void *RERERE_RESOLVED;
extern int setup_rerere(struct string_list *, int);
extern int rerere(int);
extern const char *rerere_path(const char *hex, const char *file);
extern int rerere_forget(const char **);
extern int rerere_forget(struct pathspec *);
extern int rerere_remaining(struct string_list *);
extern void rerere_clear(struct string_list *);
extern void rerere_gc(struct string_list *);

View File

@ -173,7 +173,7 @@ void unmerge_marked_index(struct index_state *istate)
}
}
void unmerge_index(struct index_state *istate, const char **pathspec)
void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
{
int i;
@ -182,7 +182,7 @@ void unmerge_index(struct index_state *istate, const char **pathspec)
for (i = 0; i < istate->cache_nr; 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);
}

View File

@ -11,7 +11,7 @@ extern void resolve_undo_write(struct strbuf *, struct string_list *);
extern struct string_list *resolve_undo_read(const char *, unsigned long);
extern void resolve_undo_clear_index(struct index_state *);
extern int unmerge_index_entry_at(struct index_state *, int);
extern void unmerge_index(struct index_state *, const char **);
extern void unmerge_index(struct index_state *, const struct pathspec *);
extern void unmerge_marked_index(struct index_state *);
#endif

View File

@ -1373,7 +1373,7 @@ static void prepare_show_merge(struct rev_info *revs)
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 +2121,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
*/
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 +2155,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
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;

173
setup.c
View File

@ -5,7 +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 @@ static char *prefix_path_gently(const char *prefix, int len, const char *path)
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 @@ static char *prefix_path_gently(const char *prefix, int len, const char *path)
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 @@ char *prefix_path(const char *prefix, int len, const char *path)
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 @@ void verify_non_filename(const char *prefix, const char *arg)
"'git <command> [<revision>...] -- [<file>...]'", 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.

View File

@ -10,6 +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 @@ static struct sha1_array ref_tips_after_fetch;
*/
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.<name>" section of the
* .gitmodules file. Return 0 only if a .gitmodules file was found, a section
* with the correct path=<oldpath> 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.<name>" section from .gitmodules where the given
* path is configured. Return 0 only if a .gitmodules file was found, a section
* with the correct path=<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(&sect, "submodule.");
strbuf_addstr(&sect, 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(&sect);
return -1;
}
strbuf_release(&sect);
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 @@ 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)
@ -1010,3 +1128,34 @@ int merge_submodule(unsigned char result[20], const char *path,
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);
}

View File

@ -11,6 +11,10 @@ enum {
RECURSE_SUBMODULES_ON = 2
};
int is_staging_gitmodules_ok(void);
int update_path_in_gitmodules(const char *oldpath, const char *newpath);
int remove_path_from_gitmodules(const char *path);
void stage_updated_gitmodules(void);
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path);
int submodule_config(const char *var, const char *value, void *cb);
@ -36,5 +40,6 @@ int merge_submodule(unsigned char result[20], const char *path, const unsigned c
int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
struct string_list *needs_pushing);
int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
#endif

View File

@ -432,7 +432,7 @@ test_expect_success_multi SYMLINKS 'symlink' ':: a/symlink' '
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 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
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 @@ test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
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 @@ test_expect_success_multi 'submodule from subdirectory' '' '
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'\''"
'
############################################################################

View File

@ -263,6 +263,7 @@ test_expect_success 'rm removes subdirectories recursively' '
'
cat >expect <<EOF
M .gitmodules
D submod
EOF
@ -270,6 +271,15 @@ cat >expect.modified <<EOF
M submod
EOF
cat >expect.cached <<EOF
D submod
EOF
cat >expect.both_deleted<<EOF
D .gitmodules
D submod
EOF
test_expect_success 'rm removes empty submodules from work tree' '
mkdir submod &&
git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) submod &&
@ -281,16 +291,20 @@ test_expect_success 'rm removes empty submodules from work tree' '
git rm submod &&
test ! -e submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm removes removed submodule from index' '
test_expect_success 'rm removes removed submodule from index and .gitmodules' '
git reset --hard &&
git submodule update &&
rm -rf submod &&
git rm submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm removes work tree of unmodified submodules' '
@ -299,7 +313,9 @@ test_expect_success 'rm removes work tree of unmodified submodules' '
git rm submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm removes a submodule with a trailing /' '
@ -333,6 +349,72 @@ test_expect_success 'rm of a populated submodule with different HEAD fails unles
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm --cached leaves work tree of populated submodules and .gitmodules alone' '
git reset --hard &&
git submodule update &&
git rm --cached submod &&
test -d submod &&
test -f submod/.git &&
git status -s -uno >actual &&
test_cmp expect.cached actual &&
git config -f .gitmodules submodule.sub.url &&
git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm --dry-run does not touch the submodule or .gitmodules' '
git reset --hard &&
git submodule update &&
git rm -n submod &&
test -f submod/.git &&
git diff-index --exit-code HEAD
'
test_expect_success 'rm does not complain when no .gitmodules file is found' '
git reset --hard &&
git submodule update &&
git rm .gitmodules &&
git rm submod >actual 2>actual.err &&
! test -s actual.err &&
! test -d submod &&
! test -f submod/.git &&
git status -s -uno >actual &&
test_cmp expect.both_deleted actual
'
test_expect_success 'rm will error out on a modified .gitmodules file unless staged' '
git reset --hard &&
git submodule update &&
git config -f .gitmodules foo.bar true &&
test_must_fail git rm submod >actual 2>actual.err &&
test -s actual.err &&
test -d submod &&
test -f submod/.git &&
git diff-files --quiet -- submod &&
git add .gitmodules &&
git rm submod >actual 2>actual.err &&
! test -s actual.err &&
! test -d submod &&
! test -f submod/.git &&
git status -s -uno >actual &&
test_cmp expect actual
'
test_expect_success 'rm issues a warning when section is not found in .gitmodules' '
git reset --hard &&
git submodule update &&
git config -f .gitmodules --remove-section submodule.sub &&
git add .gitmodules &&
echo "warning: Could not find section in .gitmodules where path=submod" >expect.err &&
git rm submod >actual 2>actual.err &&
test_i18ncmp expect.err actual.err &&
! test -d submod &&
! test -f submod/.git &&
git status -s -uno >actual &&
test_cmp expect actual
'
@ -427,7 +509,9 @@ test_expect_success 'rm of a conflicted populated submodule with different HEAD
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm of a conflicted populated submodule with modifications fails unless forced' '
@ -446,7 +530,9 @@ test_expect_success 'rm of a conflicted populated submodule with modifications f
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
test_cmp expect actual
test_cmp expect actual &&
test_must_fail git config -f .gitmodules submodule.sub.url &&
test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm of a conflicted populated submodule with untracked files fails unless forced' '

View File

@ -32,6 +32,16 @@ test_expect_success 'star pathspec globs' '
test_cmp expect actual
'
test_expect_success 'star pathspec globs' '
cat >expect <<-\EOF &&
bracket
star
vanilla
EOF
git log --format=%s -- ":(glob)f*" >actual &&
test_cmp expect actual
'
test_expect_success 'bracket pathspec globs and matches literal brackets' '
cat >expect <<-\EOF &&
bracket
@ -41,28 +51,105 @@ test_expect_success 'bracket pathspec globs and matches literal brackets' '
test_cmp expect actual
'
test_expect_success 'bracket pathspec globs and matches literal brackets' '
cat >expect <<-\EOF &&
bracket
vanilla
EOF
git log --format=%s -- ":(glob)f[o][o]" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (vanilla)' '
echo vanilla >expect &&
git --literal-pathspecs log --format=%s -- foo >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (vanilla)' '
echo vanilla >expect &&
git log --format=%s -- ":(literal)foo" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (star)' '
echo star >expect &&
git --literal-pathspecs log --format=%s -- "f*" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (star)' '
echo star >expect &&
git log --format=%s -- ":(literal)f*" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (bracket)' '
echo bracket >expect &&
git --literal-pathspecs log --format=%s -- "f[o][o]" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option matches literally (bracket)' '
echo bracket >expect &&
git log --format=%s -- ":(literal)f[o][o]" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob option disables :(literal)' '
: >expect &&
git --literal-pathspecs log --format=%s -- ":(literal)foo" >actual &&
test_cmp expect actual
'
test_expect_success 'no-glob environment variable works' '
echo star >expect &&
GIT_LITERAL_PATHSPECS=1 git log --format=%s -- "f*" >actual &&
test_cmp expect actual
'
test_expect_success 'setup xxx/bar' '
mkdir xxx &&
test_commit xxx xxx/bar
'
test_expect_success '**/ works with :(glob)' '
cat >expect <<-\EOF &&
xxx
unrelated
EOF
git log --format=%s -- ":(glob)**/bar" >actual &&
test_cmp expect actual
'
test_expect_success '**/ does not work with --noglob-pathspecs' '
: >expect &&
git --noglob-pathspecs log --format=%s -- "**/bar" >actual &&
test_cmp expect actual
'
test_expect_success '**/ works with :(glob) and --noglob-pathspecs' '
cat >expect <<-\EOF &&
xxx
unrelated
EOF
git --noglob-pathspecs log --format=%s -- ":(glob)**/bar" >actual &&
test_cmp expect actual
'
test_expect_success '**/ works with --glob-pathspecs' '
cat >expect <<-\EOF &&
xxx
unrelated
EOF
git --glob-pathspecs log --format=%s -- "**/bar" >actual &&
test_cmp expect actual
'
test_expect_success '**/ does not work with :(literal) and --glob-pathspecs' '
: >expect &&
git --glob-pathspecs log --format=%s -- ":(literal)**/bar" >actual &&
test_cmp expect actual
'
test_done

103
t/t6131-pathspec-icase.sh Executable file
View File

@ -0,0 +1,103 @@
#!/bin/sh
test_description='test case insensitive pathspec limiting'
. ./test-lib.sh
if test_have_prereq CASE_INSENSITIVE_FS
then
skip_all='skipping case sensitive tests - case insensitive file system'
test_done
fi
test_expect_success 'create commits with glob characters' '
test_commit bar bar &&
test_commit bAr bAr &&
test_commit BAR BAR &&
mkdir foo &&
test_commit foo/bar foo/bar &&
test_commit foo/bAr foo/bAr &&
test_commit foo/BAR foo/BAR &&
mkdir fOo &&
test_commit fOo/bar fOo/bar &&
test_commit fOo/bAr fOo/bAr &&
test_commit fOo/BAR fOo/BAR &&
mkdir FOO &&
test_commit FOO/bar FOO/bar &&
test_commit FOO/bAr FOO/bAr &&
test_commit FOO/BAR FOO/BAR
'
test_expect_success 'tree_entry_interesting matches bar' '
echo bar >expect &&
git log --format=%s -- "bar" >actual &&
test_cmp expect actual
'
test_expect_success 'tree_entry_interesting matches :(icase)bar' '
cat <<-EOF >expect &&
BAR
bAr
bar
EOF
git log --format=%s -- ":(icase)bar" >actual &&
test_cmp expect actual
'
test_expect_success 'tree_entry_interesting matches :(icase)bar with prefix' '
cat <<-EOF >expect &&
fOo/BAR
fOo/bAr
fOo/bar
EOF
( cd fOo && git log --format=%s -- ":(icase)bar" ) >actual &&
test_cmp expect actual
'
test_expect_success 'tree_entry_interesting matches :(icase)bar with empty prefix' '
cat <<-EOF >expect &&
FOO/BAR
FOO/bAr
FOO/bar
fOo/BAR
fOo/bAr
fOo/bar
foo/BAR
foo/bAr
foo/bar
EOF
( cd fOo && git log --format=%s -- ":(icase)../foo/bar" ) >actual &&
test_cmp expect actual
'
test_expect_success 'match_pathspec_depth matches :(icase)bar' '
cat <<-EOF >expect &&
BAR
bAr
bar
EOF
git ls-files ":(icase)bar" >actual &&
test_cmp expect actual
'
test_expect_success 'match_pathspec_depth matches :(icase)bar with prefix' '
cat <<-EOF >expect &&
fOo/BAR
fOo/bAr
fOo/bar
EOF
( cd fOo && git ls-files --full-name ":(icase)bar" ) >actual &&
test_cmp expect actual
'
test_expect_success 'match_pathspec_depth matches :(icase)bar with empty prefix' '
cat <<-EOF >expect &&
bar
fOo/BAR
fOo/bAr
fOo/bar
EOF
( cd fOo && git ls-files --full-name ":(icase)bar" ../bar ) >actual &&
test_cmp expect actual
'
test_done

View File

@ -259,4 +259,132 @@ test_expect_success SYMLINKS 'check moved symlink' '
rm -f moved symlink
test_expect_success 'setup submodule' '
git commit -m initial &&
git reset --hard &&
git submodule add ./. sub &&
echo content >file &&
git add file &&
git commit -m "added sub and file"
'
test_expect_success 'git mv cannot move a submodule in a file' '
test_must_fail git mv sub file
'
test_expect_success 'git mv moves a submodule with a .git directory and no .gitmodules' '
entry="$(git ls-files --stage sub | cut -f 1)" &&
git rm .gitmodules &&
(
cd sub &&
rm -f .git &&
cp -a ../.git/modules/sub .git &&
GIT_WORK_TREE=. git config --unset core.worktree
) &&
mkdir mod &&
git mv sub mod/sub &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
cd mod/sub &&
git status
) &&
git update-index --refresh &&
git diff-files --quiet
'
test_expect_success 'git mv moves a submodule with gitfile' '
rm -rf mod/sub &&
git reset --hard &&
git submodule update &&
entry="$(git ls-files --stage sub | cut -f 1)" &&
(
cd mod &&
git mv ../sub/ .
) &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
cd mod/sub &&
git status
) &&
echo mod/sub >expected &&
git config -f .gitmodules submodule.sub.path >actual &&
test_cmp expected actual &&
git update-index --refresh &&
git diff-files --quiet
'
test_expect_success 'mv does not complain when no .gitmodules file is found' '
rm -rf mod/sub &&
git reset --hard &&
git submodule update &&
git rm .gitmodules &&
entry="$(git ls-files --stage sub | cut -f 1)" &&
git mv sub mod/sub 2>actual.err &&
! test -s actual.err &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
cd mod/sub &&
git status
) &&
git update-index --refresh &&
git diff-files --quiet
'
test_expect_success 'mv will error out on a modified .gitmodules file unless staged' '
rm -rf mod/sub &&
git reset --hard &&
git submodule update &&
git config -f .gitmodules foo.bar true &&
entry="$(git ls-files --stage sub | cut -f 1)" &&
test_must_fail git mv sub mod/sub 2>actual.err &&
test -s actual.err &&
test -e sub &&
git diff-files --quiet -- sub &&
git add .gitmodules &&
git mv sub mod/sub 2>actual.err &&
! test -s actual.err &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
cd mod/sub &&
git status
) &&
git update-index --refresh &&
git diff-files --quiet
'
test_expect_success 'mv issues a warning when section is not found in .gitmodules' '
rm -rf mod/sub &&
git reset --hard &&
git submodule update &&
git config -f .gitmodules --remove-section submodule.sub &&
git add .gitmodules &&
entry="$(git ls-files --stage sub | cut -f 1)" &&
echo "warning: Could not find section in .gitmodules where path=sub" >expect.err &&
git mv sub mod/sub 2>actual.err &&
test_i18ncmp expect.err actual.err &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
cd mod/sub &&
git status
) &&
git update-index --refresh &&
git diff-files --quiet
'
test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '
rm -rf mod/sub &&
git reset --hard &&
git submodule update &&
git mv -n sub mod/sub 2>actual.err &&
test -f sub/.git &&
git diff-index --exit-code HEAD &&
git update-index --refresh &&
git diff-files --quiet -- sub .gitmodules
'
test_done

View File

@ -783,13 +783,11 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
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 +807,8 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
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 &&

View File

@ -253,7 +253,7 @@ test_expect_success 'deleted vs modified submodule' '
git checkout -b test6 branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
git rm submod &&
git rm --cached submod &&
git commit -m "Submodule deleted from branch" &&
git checkout -b test6.a test6 &&
test_must_fail git merge master &&
@ -322,7 +322,7 @@ test_expect_success 'file vs modified submodule' '
git checkout -b test7 branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
git rm submod &&
git rm --cached submod &&
echo not a submodule >submod &&
git add submod &&
git commit -m "Submodule path becomes file" &&
@ -453,7 +453,7 @@ test_expect_success 'submodule in subdirectory' '
test_expect_success 'directory vs modified submodule' '
git checkout -b test11 branch1 &&
mv submod submod-movedaside &&
git rm submod &&
git rm --cached submod &&
mkdir submod &&
echo not a submodule >submod/file16 &&
git add submod/file16 &&

View File

@ -138,7 +138,6 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
/* Enable recursion indefinitely */
opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
opt->pathspec.max_depth = -1;
strbuf_init(&base, PATH_MAX);
strbuf_add(&base, base_str, baselen);
@ -196,9 +195,27 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
struct diff_options diff_opts;
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_filepair *choice;
const char *paths[1];
int i;
/*
* follow-rename code is very specific, we need exactly one
* path. Magic that matches more than one path is not
* supported.
*/
GUARD_PATHSPEC(&opt->pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
#if 0
/*
* We should reject wildcards as well. Unfortunately we
* haven't got a reliable way to detect that 'foo\*bar' in
* fact has no wildcards. nowildcard_len is merely a hint for
* optimization. Let it slip for now until wildmatch is taught
* about dry-run mode and returns wildcard info.
*/
if (opt->pathspec.has_wildcard)
die("BUG:%s:%d: wildcards are not supported",
__FILE__, __LINE__);
#endif
/* Remove the file creation entry from the diff queue, and remember it */
choice = q->queue[0];
q->nr = 0;
@ -207,15 +224,13 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
DIFF_OPT_SET(&diff_opts, RECURSIVE);
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = opt->pathspec.raw[0];
diff_opts.single_follow = opt->pathspec.items[0].match;
diff_opts.break_opt = opt->break_opt;
diff_opts.rename_score = opt->rename_score;
paths[0] = NULL;
diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
diff_tree(t1, t2, base, &diff_opts);
diffcore_std(&diff_opts);
diff_tree_release_paths(&diff_opts);
free_pathspec(&diff_opts.pathspec);
/* Go through the new set of filepairing, and see if we find a more interesting one */
opt->found_follow = 0;
@ -228,15 +243,18 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
* the future!
*/
if ((p->status == 'R' || p->status == 'C') &&
!strcmp(p->two->path, opt->pathspec.raw[0])) {
!strcmp(p->two->path, opt->pathspec.items[0].match)) {
const char *path[2];
/* Switch the file-pairs around */
q->queue[i] = choice;
choice = p;
/* Update the path we use from now on.. */
diff_tree_release_paths(opt);
opt->pathspec.raw[0] = xstrdup(p->one->path);
diff_tree_setup_paths(opt->pathspec.raw, opt);
path[0] = p->one->path;
path[1] = NULL;
free_pathspec(&opt->pathspec);
parse_pathspec(&opt->pathspec, PATHSPEC_ALL_MAGIC, 0, "", path);
/*
* The caller expects us to return a set of vanilla
@ -310,13 +328,3 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_
free(tree);
return retval;
}
void diff_tree_release_paths(struct diff_options *opt)
{
free_pathspec(&opt->pathspec);
}
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
{
init_pathspec(&opt->pathspec, p);
}

View File

@ -3,6 +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)
{
@ -487,13 +488,25 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
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 +552,7 @@ static int match_entry(const struct name_entry *entry, int pathlen,
* 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 +560,39 @@ static int match_entry(const struct name_entry *entry, int pathlen,
* 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 +629,7 @@ static int match_wildcard_base(const struct pathspec_item *item,
*/
if (baselen >= matchlen) {
*matched = matchlen;
return !strncmp(base, match, matchlen);
return !basecmp(item, base, match, matchlen);
}
dirlen = matchlen;
@ -605,7 +642,7 @@ static int match_wildcard_base(const struct pathspec_item *item,
* 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 +671,17 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
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 +699,12 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
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 +715,14 @@ enum interesting tree_entry_interesting(const struct name_entry *entry,
}
/* 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 +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;

4
tree.c
View File

@ -47,7 +47,7 @@ static int read_one_entry_quick(const unsigned char *sha1, const char *base, int
}
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 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
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;

2
tree.h
View File

@ -25,7 +25,7 @@ typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const ch
extern 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);
extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec);

View File

@ -1,4 +1,5 @@
#include "cache.h"
#include "pathspec.h"
#include "wt-status.h"
#include "object.h"
#include "dir.h"
@ -438,7 +439,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
}
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 @@ static void wt_status_collect_changes_index(struct wt_status *s)
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;
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 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
else
d->index_status = DIFF_STATUS_ADDED;
}
free_pathspec(&pathspec);
}
static void wt_status_collect_untracked(struct wt_status *s)
@ -516,12 +514,12 @@ static void wt_status_collect_untracked(struct wt_status *s)
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 @@ static void wt_status_collect_untracked(struct wt_status *s)
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);
}

View File

@ -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;