diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index b0944e57d5..5c501a299e 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] - [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N] + [--edit | -e] [--[no-]all | [--update | -u]] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--] [...] @@ -121,6 +121,18 @@ If no is given, the current version of Git defaults to and its subdirectories. This default will change in a future version of Git, hence the form without should not be used. +--no-all:: + Update the index by adding new files that are unknown to the + index and files modified in the working tree, but ignore + files that have been removed from the working tree. This + option is a no-op when no is used. ++ +This option is primarily to help the current users of Git, whose +"git add ..." ignores removed files. In future versions +of Git, "git add ..." will be a synonym to "git add -A +..." and "git add --no-all ..." will behave like +today's "git add ...", ignoring removed files. + -N:: --intent-to-add:: Record only the fact that the path will be added later. An entry diff --git a/builtin/add.c b/builtin/add.c index daf02c6aff..54cd2d417d 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -28,6 +28,9 @@ struct update_callback_data { 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; @@ -93,6 +96,24 @@ 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 '--no-all', whose\n" +"behaviour will change in Git 2.0 with respect to paths you removed from\n" +"your working tree. Paths like '%s' that are\n" +"removed are ignored with this version of Git.\n" +"\n" +"* 'git add --no-all ', which is the current default, ignores\n" +" paths you removed from your working tree.\n" +"\n" +"* 'git add --all ' 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) { @@ -130,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)) @@ -141,32 +166,28 @@ 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; - memset(&data, 0, sizeof(data)); - data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT; - 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. - */ - data.implicit_dot = prefix; - data.implicit_dot_len = strlen(prefix); - pathspec = NULL; - } - 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; - 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; } @@ -354,23 +375,27 @@ 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 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")), + 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(), }; @@ -416,6 +441,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int require_pathspec; char *seen = NULL; int implicit_dot = 0; + struct update_callback_data update_data; git_config(add_config, NULL); @@ -431,8 +457,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) { @@ -521,8 +568,20 @@ int cmd_add(int argc, const char **argv, const char *prefix) 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); diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index c317254b9a..b2bd41918e 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -166,9 +166,9 @@ test_expect_success 'add -u resolves unmerged paths' ' echo 2 >path3 && echo 2 >path5 && - # Explicit resolving by adding removed paths should fail - test_must_fail git add path4 && - test_must_fail git add path6 && + # Fail to explicitly resolve removed paths with "git add" + test_must_fail git add --no-all path4 && + test_must_fail git add --no-all path6 && # "add -u" should notice removals no matter what stages # the index entries are in.