From c972ec04204b6edf4fe7ec0d59a3ce3564378303 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Jul 2008 19:22:25 -0700 Subject: [PATCH 1/4] builtin-add.c: restructure the code for maintainability The implementation of "git add" has four major codepaths that are mutually exclusive: - if "--interactive" or "--patch" is given, spawn "git add--interactive" and exit without doing anything else. Otherwise things are handled internally in this C code; - if "--update" is given, update the modified files and exit without doing anything else; - if "--refresh" is given, do refresh and exit without doing anything else; - otherwise, find the paths that match pathspecs and stage their contents. It led to an unholy mess in the code structure; each of the latter three codepaths has a separate call to read_cache(), even though they are all about "read the current index, update it and write it back", and logically they should read the index once _anyway_. This cleans up the latter three cases by introducing a pair of helper variables: - "add_new_files" is set if we need to scan the working tree for paths that match the pathspec. This variable is false for "--update" and "--refresh", because they only work on already tracked files. - "require_pathspec" is set if the user must give at least one pathspec. "--update" does not need it but all the other cases do. This is in preparation for introducing a new option "--all", that does the equivalent of "git add -u && git add ." (aka "addremove"). Signed-off-by: Junio C Hamano --- builtin-add.c | 75 +++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index bf13aa3ad6..9b2ee8c136 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -140,8 +140,6 @@ static void refresh(int verbose, const char **pathspec) for (specs = 0; pathspec[specs]; specs++) /* nothing */; seen = xcalloc(specs, 1); - if (read_cache() < 0) - die("index file corrupt"); refresh_index(&the_index, verbose ? 0 : REFRESH_QUIET, pathspec, seen); for (i = 0; i < specs; i++) { if (!seen[i]) @@ -216,13 +214,36 @@ static int add_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int add_files(struct dir_struct *dir, int flags) +{ + int i, exit_status = 0; + + if (dir->ignored_nr) { + fprintf(stderr, ignore_error); + for (i = 0; i < dir->ignored_nr; i++) + fprintf(stderr, "%s\n", dir->ignored[i]->name); + fprintf(stderr, "Use -f if you really want to add them.\n"); + die("no files added"); + } + + for (i = 0; i < dir->nr; i++) + if (add_file_to_cache(dir->entries[i]->name, flags)) { + if (!ignore_add_errors) + die("adding files failed"); + exit_status = 1; + } + return exit_status; +} + int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; - int i, newfd; + int newfd; const char **pathspec; struct dir_struct dir; int flags; + int add_new_files; + int require_pathspec; argc = parse_options(argc, argv, builtin_add_options, builtin_add_usage, 0); @@ -233,53 +254,43 @@ int cmd_add(int argc, const char **argv, const char *prefix) git_config(add_config, NULL); + add_new_files = !take_worktree_changes && !refresh_only; + require_pathspec = !take_worktree_changes; + newfd = hold_locked_index(&lock_file, 1); flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0)); - if (take_worktree_changes) { - const char **pathspec; - if (read_cache() < 0) - die("index file corrupt"); - pathspec = get_pathspec(prefix, argv); - exit_status = add_files_to_cache(prefix, pathspec, flags); - goto finish; - } - - if (argc == 0) { + if (require_pathspec && argc == 0) { fprintf(stderr, "Nothing specified, nothing added.\n"); fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); return 0; } pathspec = get_pathspec(prefix, argv); + /* + * If we are adding new files, we need to scan the working + * tree to find the ones that match pathspecs; this needs + * to be done before we read the index. + */ + if (add_new_files) + fill_directory(&dir, pathspec, ignored_too); + + if (read_cache() < 0) + die("index file corrupt"); + if (refresh_only) { refresh(verbose, pathspec); goto finish; } - fill_directory(&dir, pathspec, ignored_too); + if (take_worktree_changes) + exit_status |= add_files_to_cache(prefix, pathspec, flags); - if (read_cache() < 0) - die("index file corrupt"); - - if (dir.ignored_nr) { - fprintf(stderr, ignore_error); - for (i = 0; i < dir.ignored_nr; i++) { - fprintf(stderr, "%s\n", dir.ignored[i]->name); - } - fprintf(stderr, "Use -f if you really want to add them.\n"); - die("no files added"); - } - - for (i = 0; i < dir.nr; i++) - if (add_file_to_cache(dir.entries[i]->name, flags)) { - if (!ignore_add_errors) - die("adding files failed"); - exit_status = 1; - } + if (add_new_files) + exit_status |= add_files(&dir, flags); finish: if (active_cache_changed) { From 3ba1f114267b19a458df0f1d714dc4010ec9cc56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Jul 2008 19:51:11 -0700 Subject: [PATCH 2/4] git-add --all: add all files People sometimes find that "git add -u && git add ." are 13 keystrokes too many. This reduces it by nine. The support of this has been very low priority for me personally, because I almost never do "git add ." in a directory with already tracked files, and in a new directory, there is no point saying "git add -u". However, for two types of people (that are very different from me), this mode of operation may make sense and there is no reason to leave it unsupported. That is: (1) If you are extremely well disciplined and keep perfect .gitignore, it always is safe to say "git add ."; or (2) If you are extremely undisciplined and do not even know what files you created, and you do not very much care what goes in your history, it does not matter if "git add ." included everything. So there it is, although I suspect I will not use it myself, ever. It will be too much of a change that is against the expectation of the existing users to allow "git commit -a" to include untracked files, and it would be inconsistent if we named this new option "-a", so the short option is "-A". We _might_ want to later add "git commit -A" but that is a separate topic. Signed-off-by: Junio C Hamano --- builtin-add.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index 9b2ee8c136..6f5672a616 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -190,7 +190,7 @@ static const char ignore_error[] = "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; +static int ignore_add_errors, addremove; static struct option builtin_add_options[] = { OPT__DRY_RUN(&show_only), @@ -200,6 +200,7 @@ static struct option builtin_add_options[] = { OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), + OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), OPT_END(), @@ -254,6 +255,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) git_config(add_config, NULL); + if (addremove && take_worktree_changes) + die("-A and -u are mutually incompatible"); + if (addremove && !argc) { + static const char *here[2] = { ".", NULL }; + argc = 1; + argv = here; + } + add_new_files = !take_worktree_changes && !refresh_only; require_pathspec = !take_worktree_changes; @@ -286,7 +295,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) goto finish; } - if (take_worktree_changes) + if (take_worktree_changes || addremove) exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) From 340ce9e25f40ef2fead9fcc027844c66737c85dd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Jul 2008 20:32:38 -0700 Subject: [PATCH 3/4] git-add --all: tests And here is a small test script that makes sure that: - both modified and new files are included, - removed file is noticed, and - no ignored file is included. Signed-off-by: Junio C Hamano --- t/t2202-add-addremove.sh | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 t/t2202-add-addremove.sh diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh new file mode 100755 index 0000000000..6a8151064c --- /dev/null +++ b/t/t2202-add-addremove.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='git add --all' + +. ./test-lib.sh + +test_expect_success setup ' + ( + echo .gitignore + echo will-remove + ) >expect && + ( + echo actual + echo expect + echo ignored + ) >.gitignore && + >will-remove && + git add --all && + test_tick && + git commit -m initial && + git ls-files >actual && + test_cmp expect actual +' + +test_expect_success 'git add --all' ' + ( + echo .gitignore + echo not-ignored + echo "M .gitignore" + echo "A not-ignored" + echo "D will-remove" + ) >expect && + >ignored && + >not-ignored && + echo modification >>.gitignore && + rm -f will-remove && + git add --all && + git update-index --refresh && + git ls-files >actual && + git diff-index --name-status --cached HEAD >>actual && + test_cmp expect actual +' + +test_done From da98053aa699b2c462e35b777f4817fc46f111eb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 19 Jul 2008 22:34:16 -0700 Subject: [PATCH 4/4] git-add --all: documentation Signed-off-by: Junio C Hamano --- Documentation/git-add.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 3558905a92..2b6d6c8654 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] - [--update | -u] [--refresh] [--ignore-errors] [--] + [--all | [--update | -u]] [--refresh] [--ignore-errors] [--] ... DESCRIPTION @@ -86,6 +86,12 @@ OPTIONS command line. If no paths are specified, all tracked files in the current directory and its subdirectories are updated. +-A:: +--all:: + Update files that git already knows about (same as '\--update') + and add all untracked files that are not ignored by '.gitignore' + mechanism. + --refresh:: Don't add the file(s), but only refresh their stat() information in the index.