diff options
Diffstat (limited to 'builtin/add.c')
-rw-r--r-- | builtin/add.c | 242 |
1 files changed, 183 insertions, 59 deletions
diff --git a/builtin/add.c b/builtin/add.c index ab1c9e8fb7..8266a9cb70 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -26,8 +26,55 @@ static int take_worktree_changes; struct update_callback_data { int flags; int add_errors; + const char *implicit_dot; + size_t implicit_dot_len; + + /* only needed for 2.0 transition preparation */ + int warn_add_would_remove; }; +static const char *option_with_implicit_dot; +static const char *short_option_with_implicit_dot; + +static void warn_pathless_add(void) +{ + static int shown; + assert(option_with_implicit_dot && short_option_with_implicit_dot); + + if (shown) + return; + shown = 1; + + /* + * To be consistent with "git add -p" and most Git + * commands, we should default to being tree-wide, but + * this is not the original behavior and can't be + * changed until users trained themselves not to type + * "git add -u" or "git add -A". For now, we warn and + * keep the old behavior. Later, the behavior can be changed + * to tree-wide, keeping the warning for a while, and + * eventually we can drop the warning. + */ + warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n" + "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n" + "To add content for the whole tree, run:\n" + "\n" + " git add %s :/\n" + " (or git add %s :/)\n" + "\n" + "To restrict the command to the current directory, run:\n" + "\n" + " git add %s .\n" + " (or git add %s .)\n" + "\n" + "With the current Git version, the command is restricted to " + "the current directory.\n" + ""), + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot); +} + static int fix_unmerged_status(struct diff_filepair *p, struct update_callback_data *data) { @@ -49,15 +96,49 @@ static int fix_unmerged_status(struct diff_filepair *p, return DIFF_STATUS_MODIFIED; } +static const char *add_would_remove_warning = N_( + "You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n" +"whose behaviour will change in Git 2.0 with respect to paths you removed.\n" +"Paths like '%s' that are\n" +"removed from your working tree are ignored with this version of Git.\n" +"\n" +"* 'git add --ignore-removal <pathspec>', which is the current default,\n" +" ignores paths you removed from your working tree.\n" +"\n" +"* 'git add --all <pathspec>' will let you also record the removals.\n" +"\n" +"Run 'git status' to check the paths you removed from your working tree.\n"); + +static void warn_add_would_remove(const char *path) +{ + warning(_(add_would_remove_warning), path); +} + static void update_callback(struct diff_queue_struct *q, struct diff_options *opt, void *cbdata) { int i; struct update_callback_data *data = cbdata; + const char *implicit_dot = data->implicit_dot; + size_t implicit_dot_len = data->implicit_dot_len; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + /* + * Check if "git add -A" or "git add -u" was run from a + * subdirectory with a modified file outside that directory, + * and warn if so. + * + * "git add -u" will behave like "git add -u :/" instead of + * "git add -u ." in the future. This warning prepares for + * that change. + */ + if (implicit_dot && + strncmp_icase(path, implicit_dot, implicit_dot_len)) { + warn_pathless_add(); + continue; + } switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -70,6 +151,10 @@ static void update_callback(struct diff_queue_struct *q, } break; case DIFF_STATUS_DELETED: + if (data->warn_add_would_remove) { + warn_add_would_remove(path); + data->warn_add_would_remove = 0; + } if (data->flags & ADD_CACHE_IGNORE_REMOVAL) break; if (!(data->flags & ADD_CACHE_PRETEND)) @@ -81,24 +166,34 @@ static void update_callback(struct diff_queue_struct *q, } } -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +static void update_files_in_cache(const char *prefix, const char **pathspec, + struct update_callback_data *data) { - 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); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; + rev.diffopt.format_callback_data = data; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); +} + +int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +{ + struct update_callback_data data; + + memset(&data, 0, sizeof(data)); + data.flags = flags; + update_files_in_cache(prefix, pathspec, &data); return !!data.add_errors; } -static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +#define WARN_IMPLICIT_DOT (1u << 0) +static char *prune_directory(struct dir_struct *dir, const char **pathspec, + int prefix, unsigned flag) { char *seen; int i, specs; @@ -115,6 +210,16 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int if (match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) *dst++ = entry; + else if (flag & WARN_IMPLICIT_DOT) + /* + * "git add -A" was run from a subdirectory with a + * new file outside that directory. + * + * "git add -A" will behave like "git add -A :/" + * instead of "git add -A ." in the future. + * Warn about the coming behavior change. + */ + warn_pathless_add(); } dir->nr = dst - dir->entries; add_pathspec_matches_against_index(pathspec, seen, specs); @@ -238,6 +343,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; + rev.diffopt.use_color = 0; DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) @@ -270,23 +376,38 @@ static struct lock_file lock_file; static const char ignore_error[] = N_("The following paths are ignored by one of your .gitignore files:\n"); -static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0; +static int verbose, show_only, ignored_too, refresh_only; +static int ignore_add_errors, intent_to_add, ignore_missing; + +#define ADDREMOVE_DEFAULT 0 /* Change to 1 in Git 2.0 */ +static int addremove = ADDREMOVE_DEFAULT; +static int addremove_explicit = -1; /* unspecified */ + +static int ignore_removal_cb(const struct option *opt, const char *arg, int unset) +{ + /* if we are told to ignore, we are not adding removals */ + *(int *)opt->value = !unset ? 0 : 1; + return 0; +} static struct option builtin_add_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), OPT__VERBOSE(&verbose, N_("be verbose")), OPT_GROUP(""), - OPT_BOOLEAN('i', "interactive", &add_interactive, N_("interactive picking")), - OPT_BOOLEAN('p', "patch", &patch_interactive, N_("select hunks interactively")), - OPT_BOOLEAN('e', "edit", &edit_interactive, N_("edit current diff and apply")), + OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), + OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), + OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")), - OPT_BOOLEAN('u', "update", &take_worktree_changes, N_("update tracked files")), - OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), - OPT_BOOLEAN('A', "all", &addremove, N_("add changes from all tracked and untracked files")), - OPT_BOOLEAN( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), - OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), - OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), + OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), + OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), + OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")), + { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit, + NULL /* takes no arguments */, + N_("ignore paths removed in the working tree (same as --no-all)"), + PARSE_OPT_NOARG, ignore_removal_cb }, + OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), + OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), + OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), OPT_END(), }; @@ -321,35 +442,6 @@ static int add_files(struct dir_struct *dir, int flags) return exit_status; } -static void warn_pathless_add(const char *option_name, const char *short_name) { - /* - * To be consistent with "git add -p" and most Git - * commands, we should default to being tree-wide, but - * this is not the original behavior and can't be - * changed until users trained themselves not to type - * "git add -u" or "git add -A". For now, we warn and - * keep the old behavior. Later, the behavior can be changed - * to tree-wide, keeping the warning for a while, and - * eventually we can drop the warning. - */ - warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n" - "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n" - "To add content for the whole tree, run:\n" - "\n" - " git add %s :/\n" - " (or git add %s :/)\n" - "\n" - "To restrict the command to the current directory, run:\n" - "\n" - " git add %s .\n" - " (or git add %s .)\n" - "\n" - "With the current Git version, the command is restricted to the current directory."), - option_name, short_name, - option_name, short_name, - option_name, short_name); -} - int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; @@ -360,8 +452,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) int add_new_files; int require_pathspec; char *seen = NULL; - const char *option_with_implicit_dot = NULL; - const char *short_option_with_implicit_dot = NULL; + int implicit_dot = 0; + struct update_callback_data update_data; git_config(add_config, NULL); @@ -377,8 +469,29 @@ int cmd_add(int argc, const char **argv, const char *prefix) argc--; argv++; + if (0 <= addremove_explicit) + addremove = addremove_explicit; + else if (take_worktree_changes && ADDREMOVE_DEFAULT) + addremove = 0; /* "-u" was given but not "-A" */ + if (addremove && take_worktree_changes) die(_("-A and -u are mutually incompatible")); + + /* + * Warn when "git add pathspec..." was given without "-u" or "-A" + * and pathspec... covers a removed path. + */ + memset(&update_data, 0, sizeof(update_data)); + if (!take_worktree_changes && addremove_explicit < 0) + update_data.warn_add_would_remove = 1; + + if (!take_worktree_changes && addremove_explicit < 0 && argc) + /* + * Turn "git add pathspec..." to "git add -A pathspec..." + * in Git 2.0 but not yet + */ + ; /* addremove = 1; */ + if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); if (addremove) { @@ -391,11 +504,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) } if (option_with_implicit_dot && !argc) { static const char *here[2] = { ".", NULL }; - if (prefix) - warn_pathless_add(option_with_implicit_dot, - short_option_with_implicit_dot); argc = 1; argv = here; + implicit_dot = 1; } add_new_files = !take_worktree_changes && !refresh_only; @@ -408,7 +519,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) (intent_to_add ? ADD_CACHE_INTENT : 0) | (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); + ? ADD_CACHE_IGNORE_REMOVAL : 0)) | + (implicit_dot ? ADD_CACHE_IMPLICIT_DOT : 0); if (require_pathspec && argc == 0) { fprintf(stderr, _("Nothing specified, nothing added.\n")); @@ -432,21 +544,22 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, pathspec); + baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec); if (pathspec) - seen = prune_directory(&dir, pathspec, baselen); + seen = prune_directory(&dir, pathspec, baselen, + implicit_dot ? WARN_IMPLICIT_DOT : 0); } if (refresh_only) { refresh(verbose, pathspec); goto finish; } + if (implicit_dot && prefix) + refresh_cache(REFRESH_QUIET); if (pathspec) { int i; - struct path_exclude_check check; - path_exclude_check_init(&check, &dir); if (!seen) seen = find_pathspecs_matching_against_index(pathspec); for (i = 0; pathspec[i]; i++) { @@ -454,7 +567,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_path_excluded(&check, pathspec[i], -1, &dtype)) + if (is_excluded(&dir, pathspec[i], &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die(_("pathspec '%s' did not match any files"), @@ -462,13 +575,24 @@ int cmd_add(int argc, const char **argv, const char *prefix) } } free(seen); - path_exclude_check_clear(&check); } plug_bulk_checkin(); - exit_status |= add_files_to_cache(prefix, pathspec, flags); + if ((flags & ADD_CACHE_IMPLICIT_DOT) && prefix) { + /* + * Check for modified files throughout the worktree so + * update_callback has a chance to warn about changes + * outside the cwd. + */ + update_data.implicit_dot = prefix; + update_data.implicit_dot_len = strlen(prefix); + pathspec = NULL; + } + update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT; + update_files_in_cache(prefix, pathspec, &update_data); + exit_status |= !!update_data.add_errors; if (add_new_files) exit_status |= add_files(&dir, flags); |