From 83b327ba4ec6d29fd59e343b734f642d266aeafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:55 +0700 Subject: update-index: refactor mark_valid() in preparation for new options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-update-index.c b/builtin-update-index.c index 92beaaf..f1b6c8e 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,8 +24,8 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; -#define MARK_VALID 1 -#define UNMARK_VALID 2 +#define MARK_FLAG 1 +#define UNMARK_FLAG 2 static void report(const char *fmt, ...) { @@ -40,19 +40,15 @@ static void report(const char *fmt, ...) va_end(vp); } -static int mark_valid(const char *path) +static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); int pos = cache_name_pos(path, namelen); if (0 <= pos) { - switch (mark_valid_only) { - case MARK_VALID: - active_cache[pos]->ce_flags |= CE_VALID; - break; - case UNMARK_VALID: - active_cache[pos]->ce_flags &= ~CE_VALID; - break; - } + if (mark) + active_cache[pos]->ce_flags |= flag; + else + active_cache[pos]->ce_flags &= ~flag; cache_tree_invalidate_path(active_cache_tree, path); active_cache_changed = 1; return 0; @@ -276,7 +272,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) goto free_return; } if (mark_valid_only) { - if (mark_valid(p)) + if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG)) die("Unable to mark file %s", path); goto free_return; } @@ -647,11 +643,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_VALID; + mark_valid_only = MARK_FLAG; continue; } if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_VALID; + mark_valid_only = UNMARK_FLAG; continue; } if (!strcmp(path, "--info-only")) { -- cgit v0.10.2-6-g49f6 From dbd57f99680eac33626d5058459efd7f118f5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:56 +0700 Subject: Add test-index-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 06aaaa0bf70fe37d198893f4e25fa73b6516f8a9 may step index format version up and down, depends on whether extended flags present in the index. This adds a test to check for index format version. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 41c0b20..e3a864c 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ test-date test-delta test-dump-cache-tree test-genrandom +test-index-version test-match-trees test-parse-options test-path-utils diff --git a/Makefile b/Makefile index daf4296..3c5b890 100644 --- a/Makefile +++ b/Makefile @@ -1580,6 +1580,7 @@ TEST_PROGRAMS += test-parse-options$X TEST_PROGRAMS += test-path-utils$X TEST_PROGRAMS += test-sha1$X TEST_PROGRAMS += test-sigchain$X +TEST_PROGRAMS += test-index-version$X all:: $(TEST_PROGRAMS) diff --git a/test-index-version.c b/test-index-version.c new file mode 100644 index 0000000..bfaad9e --- /dev/null +++ b/test-index-version.c @@ -0,0 +1,14 @@ +#include "cache.h" + +int main(int argc, const char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} -- cgit v0.10.2-6-g49f6 From 44a3691362dc71241a5d68d90b07642c46992e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:57 +0700 Subject: Introduce "skip-worktree" bit in index, teach Git to get/set this bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detail about this bit is in Documentation/git-update-index.txt. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 021066e..6f9d880 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -107,6 +107,7 @@ OPTIONS Identify the file status with the following tags (followed by a space) at the start of each line: H:: cached + S:: skip-worktree M:: unmerged R:: removed/deleted C:: modified/changed diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 25e0bbe..a10f355 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,6 +15,7 @@ SYNOPSIS [--cacheinfo ]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] + [--skip-worktree | --no-skip-worktree] [--ignore-submodules] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] @@ -99,6 +100,13 @@ in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually. +--skip-worktree:: +--no-skip-worktree:: + When one of these flags is specified, the object name recorded + for the paths are not updated. Instead, these options + set and unset the "skip-worktree" bit for the paths. See + section "Skip-worktree bit" below for more information. + -g:: --again:: Runs 'git-update-index' itself on the paths whose index @@ -304,6 +312,27 @@ M foo.c <9> now it checks with lstat(2) and finds it has been changed. +Skip-worktree bit +----------------- + +Skip-worktree bit can be defined in one (long) sentence: When reading +an entry, if it is marked as skip-worktree, then Git pretends its +working directory version is up to date and read the index version +instead. + +To elaborate, "reading" means checking for file existence, reading +file attributes or file content. The working directory version may be +present or absent. If present, its content may match against the index +version or not. Writing is not affected by this bit, content safety +is still first priority. Note that Git _can_ update working directory +file, that is marked skip-worktree, if it is safe to do so (i.e. +working directory version matches index version) + +Although this bit looks similar to assume-unchanged bit, its goal is +different from assume-unchanged bit's. Skip-worktree also takes +precedence over assume-unchanged bit when both are set. + + Configuration ------------- diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f473220..c1afbad 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -37,6 +37,7 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; +static const char *tag_skip_worktree = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -178,7 +179,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce); + show_ce_entry(ce_stage(ce) ? tag_unmerged : + (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } if (show_deleted | show_modified) { @@ -490,6 +492,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + tag_skip_worktree = "S "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; diff --git a/builtin-update-index.c b/builtin-update-index.c index f1b6c8e..5e97d09 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -24,6 +24,7 @@ static int info_only; static int force_remove; static int verbose; static int mark_valid_only; +static int mark_skip_worktree_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 @@ -276,6 +277,11 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } + if (mark_skip_worktree_only) { + if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG)) + die("Unable to mark file %s", path); + goto free_return; + } if (force_remove) { if (remove_file_from_cache(p)) @@ -384,7 +390,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; +"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] ..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -650,6 +656,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) mark_valid_only = UNMARK_FLAG; continue; } + if (!strcmp(path, "--no-skip-worktree")) { + mark_skip_worktree_only = UNMARK_FLAG; + continue; + } + if (!strcmp(path, "--skip-worktree")) { + mark_skip_worktree_only = MARK_FLAG; + continue; + } if (!strcmp(path, "--info-only")) { info_only = 1; continue; diff --git a/cache.h b/cache.h index 9222774..f266246 100644 --- a/cache.h +++ b/cache.h @@ -181,10 +181,11 @@ struct cache_entry { * Extended on-disk flags */ #define CE_INTENT_TO_ADD 0x20000000 +#define CE_SKIP_WORKTREE 0x40000000 /* CE_EXTENDED2 is for future extension */ #define CE_EXTENDED2 0x80000000 -#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD) +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE) /* * Safeguard to avoid saving wrong flags: @@ -233,6 +234,7 @@ static inline size_t ce_namelen(const struct cache_entry *ce) ondisk_cache_entry_size(ce_namelen(ce))) #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE) #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh new file mode 100755 index 0000000..1d0879b --- /dev/null +++ b/t/t2104-update-index-skip-worktree.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < Date: Thu, 20 Aug 2009 20:46:58 +0700 Subject: Teach Git to respect skip-worktree bit (reading part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep: turn on --cached for files that is marked skip-worktree ls-files: do not check for deleted file that is marked skip-worktree update-index: ignore update request if it's skip-worktree, while still allows removing diff*: skip worktree version Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06..a0b1fd3 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -180,6 +180,11 @@ static void add_remove_files(struct string_list *list) for (i = 0; i < list->nr; i++) { struct stat st; struct string_list_item *p = &(list->items[i]); + int pos = index_name_pos(&the_index, p->string, strlen(p->string)); + struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; + + if (ce && ce_skip_worktree(ce)) + continue; if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) diff --git a/builtin-grep.c b/builtin-grep.c index ad0e0a5..813fe97 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -517,7 +517,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, * are identical, even if worktree file has been modified, so use * cache version instead */ - if (cached || (ce->ce_flags & CE_VALID)) { + if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index c1afbad..ad7e447 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -194,6 +194,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) continue; if (ce->ce_flags & CE_UPDATE) continue; + if (ce_skip_worktree(ce)) + continue; err = lstat(ce->name, &st); if (show_deleted && err) show_ce_entry(tag_removed, ce); diff --git a/builtin-update-index.c b/builtin-update-index.c index 5e97d09..97b9ea6 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -172,29 +172,29 @@ static int process_directory(const char *path, int len, struct stat *st) return error("%s: is a directory - add files inside instead", path); } -/* - * Process a regular file - */ -static int process_file(const char *path, int len, struct stat *st) -{ - int pos = cache_name_pos(path, len); - struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - - return add_one_path(ce, path, len, st); -} - static int process_path(const char *path) { - int len; + int pos, len; struct stat st; + struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); + pos = cache_name_pos(path, len); + ce = pos < 0 ? NULL : active_cache[pos]; + if (ce && ce_skip_worktree(ce)) { + /* + * working directory version is assumed "good" + * so updating it does not make sense. + * On the other hand, removing it from index should work + */ + if (allow_remove && remove_file_from_cache(path)) + return error("%s: cannot remove from the index", path); + return 0; + } + /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -205,7 +205,13 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - return process_file(path, len, &st); + /* + * Process a regular file + */ + if (ce && S_ISGITLINK(ce->ce_mode)) + return error("%s is already a gitlink, not replacing", path); + + return add_one_path(ce, path, len, &st); } static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, diff --git a/diff-lib.c b/diff-lib.c index 22da66e..b0b379d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -159,7 +159,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ @@ -339,7 +339,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, int match_missing, cached; /* if the entry is not checked out, don't examine work tree */ - cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); + cached = o->index_only || + (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". diff --git a/diff.c b/diff.c index cd35e0c..3970df4 100644 --- a/diff.c +++ b/diff.c @@ -1805,7 +1805,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * If ce is marked as "assume unchanged", there is no * guarantee that work tree matches what we are looking for. */ - if (ce->ce_flags & CE_VALID) + if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) return 0; /* diff --git a/read-cache.c b/read-cache.c index 4e3e272..5ee7d9d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -265,7 +265,7 @@ int ie_match_stat(const struct index_state *istate, * If it's marked as always valid in the index, it's * valid whatever the checked-out copy says. */ - if (!ignore_valid && (ce->ce_flags & CE_VALID)) + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) return 0; /* @@ -1004,11 +1004,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, if (ce_uptodate(ce)) return ce; - /* - * CE_VALID means the user promised us that the change to - * the work tree does not matter and told us not to worry. - */ - if (!ignore_valid && (ce->ce_flags & CE_VALID)) { + if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) { ce_mark_uptodate(ce); return ce; } diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh new file mode 100755 index 0000000..e996928 --- /dev/null +++ b/t/t7011-skip-worktree-reading.sh @@ -0,0 +1,163 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='skip-worktree bit test' + +. ./test-lib.sh + +cat >expect.full <expect.skip < expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +test_expect_success 'setup' ' + test_commit init && + mkdir sub && + touch ./1 ./2 sub/1 sub/2 && + git add 1 2 sub/1 sub/2 && + git update-index --skip-worktree 1 sub/1 && + git ls-files -t > result && + test_cmp expect.skip result +' + +test_expect_success 'update-index' ' + setup_absent && + git update-index 1 && + test_absent +' + +test_expect_success 'update-index' ' + setup_dirty && + git update-index 1 && + test_dirty +' + +test_expect_success 'update-index --remove' ' + setup_absent && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + test ! -f 1 +' + +test_expect_success 'update-index --remove' ' + setup_dirty && + git update-index --remove 1 && + test -z "$(git ls-files 1)" && + echo dirty > expected && + test_cmp expected 1 +' + +test_expect_success 'ls-files --delete' ' + setup_absent && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --delete' ' + setup_dirty && + test -z "$(git ls-files -d)" +' + +test_expect_success 'ls-files --modified' ' + setup_absent && + test -z "$(git ls-files -m)" +' + +test_expect_success 'ls-files --modified' ' + setup_dirty && + test -z "$(git ls-files -m)" +' + +test_expect_success 'grep with skip-worktree file' ' + git update-index --no-skip-worktree 1 && + echo test > 1 && + git update-index 1 && + git update-index --skip-worktree 1 && + rm 1 && + test "$(git grep --no-ext-grep test)" = "1:test" +' + +echo ":000000 100644 $ZERO_SHA0 $NULL_SHA1 A 1" > expected +test_expect_success 'diff-index does not examine skip-worktree absent entries' ' + setup_absent && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-index does not examine skip-worktree dirty entries' ' + setup_dirty && + git diff-index HEAD -- 1 > result && + test_cmp expected result +' + +test_expect_success 'diff-files does not examine skip-worktree absent entries' ' + setup_absent && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'diff-files does not examine skip-worktree dirty entries' ' + setup_dirty && + test -z "$(git diff-files -- one)" +' + +test_expect_success 'git-rm succeeds on skip-worktree absent entries' ' + setup_absent && + git rm 1 +' + +test_expect_failure 'commit on skip-worktree absent entries' ' + git reset && + setup_absent && + test_must_fail git commit -m null 1 +' + +test_expect_failure 'commit on skip-worktree dirty entries' ' + git reset && + setup_dirty && + test_must_fail git commit -m null 1 +' + +test_done -- cgit v0.10.2-6-g49f6 From 52030836943c0d28d8be4202762ede28e921dc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:46:59 +0700 Subject: Teach Git to respect skip-worktree bit (writing part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This part is mainly to remove CE_VALID shortcuts (and as a consequence, ce_uptodate() shortcuts as it may be turned on by CE_VALID) in writing code path if skip-worktree is used. Various tests are added to avoid future breakages. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh new file mode 100755 index 0000000..8d8b1c0 --- /dev/null +++ b/t/t7012-skip-worktree-writing.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# +# Copyright (c) 2008 Nguyễn Thái Ngọc Duy +# + +test_description='test worktree writing operations when skip-worktree is used' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + echo modified >> init.t && + touch added && + git add init.t added && + git commit -m "modified and added" && + git tag top +' + +test_expect_success 'read-tree updates worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + rm init.t && + git read-tree -m -u HEAD^ && + echo init > expected && + test_cmp expected init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree init.t && + echo dirty >> init.t && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty init.t && + test "$(git ls-files -t init.t)" = "S init.t" && + git update-index --no-skip-worktree init.t +' + +test_expect_success 'read-tree removes worktree, absent case' ' + git checkout -f top && + git update-index --skip-worktree added && + rm added && + git read-tree -m -u HEAD^ && + test ! -f added +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + git checkout -f top && + git update-index --skip-worktree added && + echo dirty >> added && + test_must_fail git read-tree -m -u HEAD^ && + grep -q dirty added && + test "$(git ls-files -t added)" = "S added" && + git update-index --no-skip-worktree added +' + +NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +ZERO_SHA0=0000000000000000000000000000000000000000 +setup_absent() { + test -f 1 && rm 1 + git update-index --remove 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_absent() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + test ! -f 1 +} + +setup_dirty() { + git update-index --force-remove 1 && + echo dirty > 1 && + git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --skip-worktree 1 +} + +test_dirty() { + echo "100644 $NULL_SHA1 0 1" > expected && + git ls-files --stage 1 > result && + test_cmp expected result && + echo dirty > expected + test_cmp expected 1 +} + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-add ignores worktree content' ' + setup_absent && + git add 1 && + test_absent +' + +test_expect_success 'git-add ignores worktree content' ' + setup_dirty && + git add 1 && + test_dirty +' + +test_expect_success 'git-rm fails if worktree is dirty' ' + setup_dirty && + test_must_fail git rm 1 && + test_dirty +' + +cat >expected < result && + test_cmp expected result +' + +test_expect_success 'git-clean, dirty case' ' + setup_dirty && + git clean -n > result && + test_cmp expected result +' + +test_expect_failure 'git-apply adds file' false +test_expect_failure 'git-apply updates file' false +test_expect_failure 'git-apply removes file' false +test_expect_failure 'git-mv to skip-worktree' false +test_expect_failure 'git-mv from skip-worktree' false +test_expect_failure 'git-checkout' false + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 720f7a1..3eda263 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -450,7 +450,7 @@ static int verify_uptodate(struct cache_entry *ce, { struct stat st; - if (o->index_only || o->reset || ce_uptodate(ce)) + if (o->index_only || (!ce_skip_worktree(ce) && (o->reset || ce_uptodate(ce)))) return 0; if (!lstat(ce->name, &st)) { @@ -1004,7 +1004,7 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) if (old && same(old, a)) { int update = 0; - if (o->reset && !ce_uptodate(old)) { + if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) { struct stat st; if (lstat(old->name, &st) || ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) -- cgit v0.10.2-6-g49f6 From b5041c5f3b9ea70ce7aa9711af6ed6f2d02909b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:00 +0700 Subject: Avoid writing to buffer in add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next patch, the buffer that is being used within add_excludes_from_file_1() comes from another function and does not have extra space to put \n at the end. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index e05b850..1170d64 100644 --- a/dir.c +++ b/dir.c @@ -229,10 +229,9 @@ static int add_excludes_from_file_1(const char *fname, if (buf_p) *buf_p = buf; - buf[size++] = '\n'; entry = buf; - for (i = 0; i < size; i++) { - if (buf[i] == '\n') { + for (i = 0; i <= size; i++) { + if (i == size || buf[i] == '\n') { if (entry != buf + i && entry[0] != '#') { buf[i - (i && buf[i-1] == '\r')] = 0; add_exclude(entry, base, baselen, which); -- cgit v0.10.2-6-g49f6 From c28b3d6e7b0471a02f81324a90b26effae0f4bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:01 +0700 Subject: Read .gitignore from index if it is skip-worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds index as a prerequisite for directory listing (with exclude). At the moment directory listing is used by "git clean", "git add", "git ls-files" and "git status"/"git commit" and unpack_trees()-related commands. These commands have been checked/modified to populate index before doing directory listing. add_excludes_from_file() does not enable this feature, because it is used to read .git/info/exclude and some explicit files specified by "git ls-files". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 5bbd18f..add6f43 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -58,6 +58,9 @@ The result of the enumeration is left in these fields:: Calling sequence ---------------- +Note: index may be looked at for .gitignore files that are CE_SKIP_WORKTREE +marked. If you to exclude files, make sure you have loaded index first. + * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`. diff --git a/builtin-clean.c b/builtin-clean.c index 2d8c735..e424b77 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -71,11 +71,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (read_cache() < 0) + die("index file corrupt"); + if (!ignored) setup_standard_excludes(&dir); pathspec = get_pathspec(prefix, argv); - read_cache(); fill_directory(&dir, pathspec); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad7e447..2e47242 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -485,6 +485,9 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) prefix_offset = strlen(prefix); git_config(git_default_config, NULL); + if (read_cache() < 0) + die("index file corrupt"); + argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); if (show_tag || show_valid_bit) { @@ -513,7 +516,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv); /* be nice with submodule paths ending in a slash */ - read_cache(); if (pathspec) strip_trailing_slash_from_submodules(); diff --git a/dir.c b/dir.c index 1170d64..e8e5b79 100644 --- a/dir.c +++ b/dir.c @@ -200,11 +200,35 @@ void add_exclude(const char *string, const char *base, which->excludes[which->nr++] = x; } +static void *read_skip_worktree_file_from_index(const char *path, size_t *size) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + struct index_state *istate = &the_index; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) + return NULL; + if (!ce_skip_worktree(istate->cache[pos])) + return NULL; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + *size = xsize_t(sz); + return data; +} + static int add_excludes_from_file_1(const char *fname, const char *base, int baselen, char **buf_p, - struct exclude_list *which) + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -212,20 +236,26 @@ static int add_excludes_from_file_1(const char *fname, char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0) - goto err; - size = xsize_t(st.st_size); - if (size == 0) { - close(fd); - return 0; + if (fd < 0 || fstat(fd, &st) < 0) { + if (0 <= fd) + close(fd); + if (!check_index || + (buf = read_skip_worktree_file_from_index(fname, &size)) == NULL) + return -1; } - buf = xmalloc(size+1); - if (read_in_full(fd, buf, size) != size) - { - free(buf); - goto err; + else { + size = xsize_t(st.st_size); + if (size == 0) { + close(fd); + return 0; + } + buf = xmalloc(size); + if (read_in_full(fd, buf, size) != size) { + close(fd); + return -1; + } + close(fd); } - close(fd); if (buf_p) *buf_p = buf; @@ -240,17 +270,12 @@ static int add_excludes_from_file_1(const char *fname, } } return 0; - - err: - if (0 <= fd) - close(fd); - return -1; } void add_excludes_from_file(struct dir_struct *dir, const char *fname) { if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE]) < 0) + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -301,7 +326,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); add_excludes_from_file_1(dir->basebuf, dir->basebuf, stk->baselen, - &stk->filebuf, el); + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index c65bca8..132c476 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -64,6 +64,8 @@ two/*.4 echo '!*.2 !*.8' >one/two/.gitignore +allignores='.gitignore one/.gitignore one/two/.gitignore' + test_expect_success \ 'git ls-files --others with various exclude options.' \ 'git ls-files --others \ @@ -85,6 +87,26 @@ test_expect_success \ >output && test_cmp expect output' +test_expect_success 'setup skip-worktree gitignore' ' + git add $allignores && + git update-index --skip-worktree $allignores && + rm $allignores +' + +test_expect_success \ + 'git ls-files --others with various exclude options.' \ + 'git ls-files --others \ + --exclude=\*.6 \ + --exclude-per-directory=.gitignore \ + --exclude-from=.git/ignore \ + >output && + test_cmp expect output' + +test_expect_success 'restore gitignore' ' + git checkout $allignores && + rm .git/index +' + cat > excludes-file <<\EOF *.[1-8] e* diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 929d5d4..8073d02 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -22,6 +22,25 @@ test_expect_success 'setup' ' ' +test_expect_success 'git clean with skip-worktree .gitignore' ' + git update-index --skip-worktree .gitignore && + rm .gitignore && + mkdir -p build docs && + touch a.out src/part3.c docs/manual.txt obj.o build/lib.so && + git clean && + test -f Makefile && + test -f README && + test -f src/part1.c && + test -f src/part2.c && + test ! -f a.out && + test ! -f src/part3.c && + test -f docs/manual.txt && + test -f obj.o && + test -f build/lib.so && + git update-index --no-skip-worktree .gitignore && + git checkout .gitignore +' + test_expect_success 'git clean' ' mkdir -p build docs && -- cgit v0.10.2-6-g49f6 From 32f54ca31747c47f56abb7ae87a6da0f5b8d97c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:02 +0700 Subject: unpack-trees(): carry skip-worktree bit over in merged_entry() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this code path, we would remove "old" and replace it with "merge". "old" may have skip-worktree bit, so re-add it to "merge". Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 3eda263..dc6d74a 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -680,6 +680,8 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; + if (ce_skip_worktree(old)) + update |= CE_SKIP_WORKTREE; invalidate_ce_path(old, o); } } -- cgit v0.10.2-6-g49f6 From c84de70781674a35b9bfd20aa5bc8c47582615df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:03 +0700 Subject: excluded_1(): support exclude files in index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Index does not really have "directories", attempts to match "foo/" against index will fail unless someone tries to reconstruct directories from a list of file. Observing that dtype in this function can never be NULL (otherwise it would segfault), dtype NULL will be used to say "hey.. you are matching against index" and behave properly. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index e8e5b79..7735cea 100644 --- a/dir.c +++ b/dir.c @@ -349,6 +349,12 @@ static int excluded_1(const char *pathname, int to_exclude = x->to_exclude; if (x->flags & EXC_FLAG_MUSTBEDIR) { + if (!dtype) { + if (!prefixcmp(pathname, exclude)) + return to_exclude; + else + continue; + } if (*dtype == DT_UNKNOWN) *dtype = get_dtype(NULL, pathname, pathlen); if (*dtype != DT_DIR) -- cgit v0.10.2-6-g49f6 From cb097534230a6bd7438e19ce6dff719697cbf983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:04 +0700 Subject: dir.c: export excluded_1() and add_excludes_from_file_1() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are used to handle .gitignore. They are now exported so that sparse checkout can reuse. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index 7735cea..6b1c478 100644 --- a/dir.c +++ b/dir.c @@ -223,12 +223,12 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size) return data; } -static int add_excludes_from_file_1(const char *fname, - const char *base, - int baselen, - char **buf_p, - struct exclude_list *which, - int check_index) +int add_excludes_from_file_to_list(const char *fname, + const char *base, + int baselen, + char **buf_p, + struct exclude_list *which, + int check_index) { struct stat st; int fd, i; @@ -274,8 +274,8 @@ static int add_excludes_from_file_1(const char *fname, void add_excludes_from_file(struct dir_struct *dir, const char *fname) { - if (add_excludes_from_file_1(fname, "", 0, NULL, - &dir->exclude_list[EXC_FILE], 0) < 0) + if (add_excludes_from_file_to_list(fname, "", 0, NULL, + &dir->exclude_list[EXC_FILE], 0) < 0) die("cannot use %s as an exclude file", fname); } @@ -324,9 +324,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) memcpy(dir->basebuf + current, base + current, stk->baselen - current); strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir); - add_excludes_from_file_1(dir->basebuf, - dir->basebuf, stk->baselen, - &stk->filebuf, el, 1); + add_excludes_from_file_to_list(dir->basebuf, + dir->basebuf, stk->baselen, + &stk->filebuf, el, 1); dir->exclude_stack = stk; current = stk->baselen; } @@ -336,9 +336,9 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ -static int excluded_1(const char *pathname, - int pathlen, const char *basename, int *dtype, - struct exclude_list *el) +int excluded_from_list(const char *pathname, + int pathlen, const char *basename, int *dtype, + struct exclude_list *el) { int i; @@ -412,8 +412,8 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) prep_exclude(dir, pathname, basename-pathname); for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, basename, - dtype_p, &dir->exclude_list[st])) { + switch (excluded_from_list(pathname, pathlen, basename, + dtype_p, &dir->exclude_list[st])) { case 0: return 0; case 1: diff --git a/dir.h b/dir.h index a631446..472e11e 100644 --- a/dir.h +++ b/dir.h @@ -69,7 +69,11 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen, 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 excluded_from_list(const char *pathname, int pathlen, const char *basename, + int *dtype, struct exclude_list *el); extern int excluded(struct dir_struct *, const char *, int *); +extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, + char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); -- cgit v0.10.2-6-g49f6 From ed5336a7541e19b267de53afc8d15cffdbde8286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:05 +0700 Subject: Introduce "sparse checkout" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With skip-worktree bit, you can manually set it to unwanted files, then remove them: you would have the so-called sparse checkout. The disadvantages are: - Porcelain tools are not aware of this. Everytime you do an operation that may update working directory, skip-worktree may be cleared out. You have to set them again. - You still have to remove skip-worktree'd files manually, which is boring and ineffective. These will be addressed in the following patches. This patch gives an idea what is "sparse checkout" in Documentation/git-read-tree.txt. This file is chosen instead of git-checkout.txt because it is quite technical and user-unfriendly. I'd expect git-checkout.txt to have something when Porcelain support is done. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 4a932b0..8b39716 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -360,6 +360,50 @@ middle of doing, and when your working tree is ready (i.e. you have finished your work-in-progress), attempt the merge again. +Sparse checkout +--------------- + +"Sparse checkout" allows to sparsely populate working directory. +It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell +Git whether a file on working directory is worth looking at. + +"git read-tree" and other merge-based commands ("git merge", "git +checkout"...) can help maintaining skip-worktree bitmap and working +directory update. `$GIT_DIR/info/sparse-checkout` is used to +define the skip-worktree reference bitmap. When "git read-tree" needs +to update working directory, it will reset skip-worktree bit in index +based on this file, which uses the same syntax as .gitignore files. +If an entry matches a pattern in this file, skip-worktree will be +set on that entry. Otherwise, skip-worktree will be unset. + +Then it compares the new skip-worktree value with the previous one. If +skip-worktree turns from unset to set, it will add the corresponding +file back. If it turns from set to unset, that file will be removed. + +While `$GIT_DIR/info/sparse-checkout` is usually used to specify what +files are in. You can also specify what files are _not_ in, using +negate patterns. For example, to remove file "unwanted": + +---------------- +* +!unwanted +---------------- + +Another tricky thing is fully repopulating working directory when you +no longer want sparse checkout. You cannot just disable "sparse +checkout" because skip-worktree are still in the index and you working +directory is still sparsely populated. You should re-populate working +directory with the `$GIT_DIR/info/sparse-checkout` file content as +follows: + +---------------- +* +---------------- + +Then you can disable sparse checkout. Sparse checkout support in "git +read-tree" and similar commands is disabled by default. + + SEE ALSO -------- linkgit:git-write-tree[1]; linkgit:git-ls-files[1]; -- cgit v0.10.2-6-g49f6 From e663db2f446b84bfe39b4dcb3d26b33826d52c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:06 +0700 Subject: unpack-trees(): add CE_WT_REMOVE to remove on worktree alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE_REMOVE now removes both worktree and index versions. Sparse checkout must be able to remove worktree version while keep the index intact when checkout area is narrowed. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index f266246..d3f81a1 100644 --- a/cache.h +++ b/cache.h @@ -177,6 +177,9 @@ struct cache_entry { #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +/* Only remove in work directory, not index */ +#define CE_WT_REMOVE (0x400000) + /* * Extended on-disk flags */ diff --git a/unpack-trees.c b/unpack-trees.c index dc6d74a..6a51a69 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -78,7 +78,7 @@ static int check_updates(struct unpack_trees_options *o) if (o->update && o->verbose_update) { for (total = cnt = 0; cnt < index->cache_nr; cnt++) { struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) + if (ce->ce_flags & (CE_UPDATE | CE_REMOVE | CE_WT_REMOVE)) total++; } @@ -92,6 +92,13 @@ static int check_updates(struct unpack_trees_options *o) for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; + if (ce->ce_flags & CE_WT_REMOVE) { + display_progress(progress, ++cnt); + if (o->update) + unlink_entry(ce); + continue; + } + if (ce->ce_flags & CE_REMOVE) { display_progress(progress, ++cnt); if (o->update) -- cgit v0.10.2-6-g49f6 From 35a5aa79d040e2121b6f6bbb17b173f4644699e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:07 +0700 Subject: unpack-trees.c: generalize verify_* functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 6a51a69..8eb4b70 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -452,8 +452,9 @@ static int same(struct cache_entry *a, struct cache_entry *b) * When a CE gets turned into an unmerged entry, we * want it to be up-to-date */ -static int verify_uptodate(struct cache_entry *ce, - struct unpack_trees_options *o) +static int verify_uptodate_1(struct cache_entry *ce, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -478,7 +479,13 @@ static int verify_uptodate(struct cache_entry *ce, if (errno == ENOENT) return 0; return o->gently ? -1 : - error(ERRORMSG(o, not_uptodate_file), ce->name); + error(error_msg, ce->name); +} + +static int verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) @@ -586,8 +593,9 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. */ -static int verify_absent(struct cache_entry *ce, const char *action, - struct unpack_trees_options *o) +static int verify_absent_1(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o, + const char *error_msg) { struct stat st; @@ -667,6 +675,11 @@ static int verify_absent(struct cache_entry *ce, const char *action, } return 0; } +static int verify_absent(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); +} static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) -- cgit v0.10.2-6-g49f6 From 08aefc9e47eb2eaf231352223c939d69b0fb3759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:08 +0700 Subject: unpack-trees(): "enable" sparse checkout and load $GIT_DIR/info/sparse-checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces core.sparseCheckout, which will control whether sparse checkout support is enabled in unpack_trees() It also loads sparse-checkout file that will be used in the next patch. I split it out so the next patch will be shorter, easier to read. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 7791c32..5825c91 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -439,6 +439,10 @@ On some file system/operating system combinations, this is unreliable. Set this config setting to 'rename' there; However, This will remove the check that makes sure that existing object files will not get overwritten. +core.sparseCheckout:: + Enable "sparse checkout" feature. See section "Sparse checkout" in + linkgit:git-read-tree[1] for more information. + add.ignore-errors:: Tells 'git-add' to continue adding files when some files cannot be added due to indexing errors. Equivalent to the '--ignore-errors' diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 8b39716..fc3f08b 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -401,7 +401,9 @@ follows: ---------------- Then you can disable sparse checkout. Sparse checkout support in "git -read-tree" and similar commands is disabled by default. +read-tree" and similar commands is disabled by default. You need to +turn `core.sparseCheckout` on in order to have sparse checkout +support. SEE ALSO diff --git a/cache.h b/cache.h index d3f81a1..2de00f8 100644 --- a/cache.h +++ b/cache.h @@ -526,6 +526,7 @@ extern size_t delta_base_cache_limit; extern int auto_crlf; extern int fsync_object_files; extern int core_preload_index; +extern int core_apply_sparse_checkout; enum safe_crlf { SAFE_CRLF_FALSE = 0, diff --git a/config.c b/config.c index e87edea..abd762e 100644 --- a/config.c +++ b/config.c @@ -503,6 +503,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.sparsecheckout")) { + core_apply_sparse_checkout = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 8f5eaa7..020422c 100644 --- a/environment.c +++ b/environment.c @@ -48,6 +48,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_replace_parents = 1; +int core_apply_sparse_checkout; /* Parallel index stat data preload? */ int core_preload_index = 0; diff --git a/unpack-trees.c b/unpack-trees.c index 8eb4b70..44f8fdf 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -378,6 +378,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options { int ret; static struct cache_entry *dfc; + struct exclude_list el; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); @@ -387,6 +388,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.quiet = 1; state.refresh_cache = 1; + memset(&el, 0, sizeof(el)); + if (!core_apply_sparse_checkout || !o->update) + o->skip_sparse_checkout = 1; + if (!o->skip_sparse_checkout) { + if (add_excludes_from_file_to_list(git_path("info/sparse-checkout"), "", 0, NULL, &el, 0) < 0) + o->skip_sparse_checkout = 1; + else + o->el = ⪙ + } + memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; if (o->src_index) { @@ -407,26 +418,39 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; - if (traverse_trees(len, t, &info) < 0) - return unpack_failed(o, NULL); + if (traverse_trees(len, t, &info) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } /* Any left-over entries in the index? */ if (o->merge) { while (o->pos < o->src_index->cache_nr) { struct cache_entry *ce = o->src_index->cache[o->pos]; - if (unpack_index_entry(ce, o) < 0) - return unpack_failed(o, NULL); + if (unpack_index_entry(ce, o) < 0) { + ret = unpack_failed(o, NULL); + goto done; + } } } - if (o->trivial_merges_only && o->nontrivial_merge) - return unpack_failed(o, "Merge requires file-level merging"); + if (o->trivial_merges_only && o->nontrivial_merge) { + ret = unpack_failed(o, "Merge requires file-level merging"); + goto done; + } o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) *o->dst_index = o->result; + +done: + for (i = 0;i < el.nr;i++) + free(el.excludes[i]); + if (el.excludes) + free(el.excludes); + return ret; } diff --git a/unpack-trees.h b/unpack-trees.h index d19df44..5c9e98a 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,6 +4,7 @@ #define MAX_UNPACK_TREES 8 struct unpack_trees_options; +struct exclude_list; typedef int (*merge_fn_t)(struct cache_entry **src, struct unpack_trees_options *options); @@ -28,6 +29,7 @@ struct unpack_trees_options { skip_unmerged, initial_checkout, diff_index_cached, + skip_sparse_checkout, gently; const char *prefix; int pos; @@ -44,6 +46,8 @@ struct unpack_trees_options { struct index_state *dst_index; struct index_state *src_index; struct index_state result; + + struct exclude_list *el; /* for internal use */ }; extern int unpack_trees(unsigned n, struct tree_desc *t, -- cgit v0.10.2-6-g49f6 From e800ec9d72a0fafbc323c41e90f7757062c2680e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:09 +0700 Subject: unpack_trees(): apply $GIT_DIR/info/sparse-checkout to the final index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 44f8fdf..2d8ecb7 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -32,6 +32,12 @@ static struct unpack_trees_error_msgs unpack_plumbing_errors = { /* bind_overlap */ "Entry '%s' overlaps with '%s'. Cannot bind.", + + /* sparse_not_uptodate_file */ + "Entry '%s' not uptodate. Cannot update sparse checkout.", + + /* would_lose_orphaned */ + "Working tree file '%s' would be %s by sparse checkout update.", }; #define ERRORMSG(o,fld) \ @@ -125,6 +131,57 @@ static int check_updates(struct unpack_trees_options *o) return errs != 0; } +static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o); +static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o); + +static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o) +{ + const char *basename; + + if (ce_stage(ce)) + return 0; + + basename = strrchr(ce->name, '/'); + basename = basename ? basename+1 : ce->name; + return excluded_from_list(ce->name, ce_namelen(ce), basename, NULL, o->el) <= 0; +} + +static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) +{ + int was_skip_worktree = ce_skip_worktree(ce); + + if (will_have_skip_worktree(ce, o)) + ce->ce_flags |= CE_SKIP_WORKTREE; + else + ce->ce_flags &= ~CE_SKIP_WORKTREE; + + /* + * We only care about files getting into the checkout area + * If merge strategies want to remove some, go ahead, this + * flag will be removed eventually in unpack_trees() if it's + * outside checkout area. + */ + if (ce->ce_flags & CE_REMOVE) + return 0; + + if (!was_skip_worktree && ce_skip_worktree(ce)) { + /* + * If CE_UPDATE is set, verify_uptodate() must be called already + * also stat info may have lost after merged_entry() so calling + * verify_uptodate() again may fail + */ + if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o)) + return -1; + ce->ce_flags |= CE_WT_REMOVE; + } + if (was_skip_worktree && !ce_skip_worktree(ce)) { + if (verify_absent_sparse(ce, "overwritten", o)) + return -1; + ce->ce_flags |= CE_UPDATE; + } + return 0; +} + static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) { int ret = o->fn(src, o); @@ -376,7 +433,7 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - int ret; + int i, ret; static struct cache_entry *dfc; struct exclude_list el; @@ -440,6 +497,17 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options goto done; } + if (!o->skip_sparse_checkout) { + for (i = 0;i < o->result.cache_nr;i++) { + struct cache_entry *ce = o->result.cache[i]; + + if (apply_sparse_checkout(ce, o)) { + ret = -1; + goto done; + } + } + } + o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) @@ -512,6 +580,12 @@ static int verify_uptodate(struct cache_entry *ce, return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } +static int verify_uptodate_sparse(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file)); +} + static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) @@ -705,6 +779,12 @@ static int verify_absent(struct cache_entry *ce, const char *action, return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } +static int verify_absent_sparse(struct cache_entry *ce, const char *action, + struct unpack_trees_options *o) +{ + return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned)); +} + static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { diff --git a/unpack-trees.h b/unpack-trees.h index 5c9e98a..95ff36c 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -15,6 +15,8 @@ struct unpack_trees_error_msgs { const char *not_uptodate_dir; const char *would_lose_untracked; const char *bind_overlap; + const char *sparse_not_uptodate_file; + const char *would_lose_orphaned; }; struct unpack_trees_options { -- cgit v0.10.2-6-g49f6 From f1f523eae99020d58ad6e7a1ef8187569e15c270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:10 +0700 Subject: unpack-trees(): ignore worktree check outside checkout area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify_absent() and verify_uptodate() are used to ensure worktree is safe to be updated, then CE_REMOVE or CE_UPDATE will be set. Finally check_updates() bases on CE_REMOVE, CE_UPDATE and the recently added CE_WT_REMOVE to update working directory accordingly. The entries that are checked may eventually be left out of checkout area (done later in apply_sparse_checkout()). We don't want to update outside checkout area. This patch teaches Git to assume "good", skip these checks when it's sure those entries will be outside checkout area, and clear CE_REMOVE|CE_UPDATE that could be set due to this assumption. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 2d8ecb7..72743b3 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -505,6 +505,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ret = -1; goto done; } + /* + * Merge strategies may set CE_UPDATE|CE_REMOVE outside checkout + * area as a result of ce_skip_worktree() shortcuts in + * verify_absent() and verify_uptodate(). Clear them. + */ + if (ce_skip_worktree(ce)) + ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + } } @@ -577,6 +585,8 @@ static int verify_uptodate_1(struct cache_entry *ce, static int verify_uptodate(struct cache_entry *ce, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file)); } @@ -776,6 +786,8 @@ static int verify_absent_1(struct cache_entry *ce, const char *action, static int verify_absent(struct cache_entry *ce, const char *action, struct unpack_trees_options *o) { + if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o)) + return 0; return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked)); } -- cgit v0.10.2-6-g49f6 From a5d07d0f5c838acb2c9d69a19907c50f72848b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:11 +0700 Subject: read-tree: add --no-sparse-checkout to disable sparse checkout support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index fc3f08b..ea7b0b2 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] - [--index-output=] + [--index-output=] [--no-sparse-checkout] [ []] @@ -110,6 +110,10 @@ OPTIONS directories the index file and index output file are located in. +--no-sparse-checkout:: + Disable sparse checkout support even if `core.sparseCheckout` + is true. + :: The id of the tree object(s) to be read/merged. diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 9c2d634..f5acb1a 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -31,7 +31,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--index-output=] [ []]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=] [-u [--exclude-per-directory=] | -i]] [--no-sparse-checkout] [--index-output=] [ []]", NULL }; @@ -98,6 +98,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, + "skip applying sparse checkout filter", 1), OPT_END() }; -- cgit v0.10.2-6-g49f6 From d6b38f61c8125423abdf2b9c10f2187c5fecd80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:12 +0700 Subject: Add tests for sparse checkout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh new file mode 100755 index 0000000..2192f5a --- /dev/null +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +test_description='sparse checkout tests' + +. ./test-lib.sh + +cat >expected <> init.t && + mkdir sub && + touch sub/added && + git add init.t sub/added && + git commit -m "modified and added" && + git tag top && + git rm sub/added && + git commit -m removed && + git tag removed && + git checkout top && + git ls-files --stage > result && + test_cmp expected result +' + +cat >expected.swt < result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result +' + +test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + echo > .git/info/sparse-checkout + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + git read-tree --no-sparse-checkout -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test ! -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + echo sub >> .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test ! -f sub/added +' + +test_expect_success 'read-tree updates worktree, absent case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + git read-tree -m -u HEAD^ && + test ! -f init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > init.t && + git read-tree -m -u HEAD^ && + grep -q dirty init.t && + rm init.t +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > added && + git read-tree -m -u HEAD^ && + grep -q dirty added +' + +test_expect_success 'read-tree adds to worktree, absent case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + git read-tree -u -m HEAD^ && + test ! -f sub/added +' + +test_expect_success 'read-tree adds to worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + mkdir sub && + echo dirty > sub/added && + git read-tree -u -m HEAD^ && + grep -q dirty sub/added +' + +test_done -- cgit v0.10.2-6-g49f6 From 9e1afb16753d583a696c988d33f45655f81e8f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 20 Aug 2009 20:47:13 +0700 Subject: sparse checkout: inhibit empty worktree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The way sparse checkout works, users may empty their worktree completely, because of non-matching sparse-checkout spec, or empty spec. I believe this is not desired. This patch makes Git refuse to produce such worktree. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh index 2192f5a..62246db 100755 --- a/t/t1009-read-tree-sparse-checkout.sh +++ b/t/t1009-read-tree-sparse-checkout.sh @@ -55,20 +55,16 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse- test -f sub/added ' -cat >expected.swt < .git/info/sparse-checkout && - git read-tree -m -u HEAD && + test_must_fail git read-tree -m -u HEAD && git ls-files --stage > result && test_cmp expected result && git ls-files -t > result && test_cmp expected.swt result && - test ! -f init.t && - test ! -f sub/added + test -f init.t && + test -f sub/added ' cat >expected.swt <skip_sparse_checkout) { + int empty_worktree = 1; for (i = 0;i < o->result.cache_nr;i++) { struct cache_entry *ce = o->result.cache[i]; @@ -512,8 +513,14 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options */ if (ce_skip_worktree(ce)) ce->ce_flags &= ~(CE_UPDATE | CE_REMOVE); + else + empty_worktree = 0; } + if (o->result.cache_nr && empty_worktree) { + ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); + goto done; + } } o->src_index = NULL; -- cgit v0.10.2-6-g49f6 From bbbe508d771f6a4c65fea43343597ecfa1eb540e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 25 Nov 2009 17:46:16 -0500 Subject: tests: rename duplicate t1009 We should avoid duplicate test numbers, since things like GIT_SKIP_TESTS consider something like t1009.5 to be unambiguous. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/t/t1009-read-tree-sparse-checkout.sh b/t/t1009-read-tree-sparse-checkout.sh deleted file mode 100755 index 62246db..0000000 --- a/t/t1009-read-tree-sparse-checkout.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/sh - -test_description='sparse checkout tests' - -. ./test-lib.sh - -cat >expected <> init.t && - mkdir sub && - touch sub/added && - git add init.t sub/added && - git commit -m "modified and added" && - git tag top && - git rm sub/added && - git commit -m removed && - git tag removed && - git checkout top && - git ls-files --stage > result && - test_cmp expected result -' - -cat >expected.swt < result && - test_cmp expected result && - git ls-files -t > result && - test_cmp expected.swt result -' - -test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' - echo > .git/info/sparse-checkout - git read-tree -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && - test -f init.t && - test -f sub/added -' - -test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' - git config core.sparsecheckout true && - echo > .git/info/sparse-checkout && - git read-tree --no-sparse-checkout -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && - test -f init.t && - test -f sub/added -' - -test_expect_success 'read-tree with empty .git/info/sparse-checkout' ' - git config core.sparsecheckout true && - echo > .git/info/sparse-checkout && - test_must_fail git read-tree -m -u HEAD && - git ls-files --stage > result && - test_cmp expected result && - git ls-files -t > result && - test_cmp expected.swt result && - test -f init.t && - test -f sub/added -' - -cat >expected.swt < .git/info/sparse-checkout && - git read-tree -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && - test ! -f init.t && - test -f sub/added -' - -cat >expected.swt < .git/info/sparse-checkout && - echo sub >> .git/info/sparse-checkout && - git read-tree -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && - test ! -f init.t && - test -f sub/added -' - -cat >expected.swt < .git/info/sparse-checkout && - git read-tree -m -u HEAD && - git ls-files -t > result && - test_cmp expected.swt result && - test -f init.t && - test ! -f sub/added -' - -test_expect_success 'read-tree updates worktree, absent case' ' - echo sub/added > .git/info/sparse-checkout && - git checkout -f top && - git read-tree -m -u HEAD^ && - test ! -f init.t -' - -test_expect_success 'read-tree updates worktree, dirty case' ' - echo sub/added > .git/info/sparse-checkout && - git checkout -f top && - echo dirty > init.t && - git read-tree -m -u HEAD^ && - grep -q dirty init.t && - rm init.t -' - -test_expect_success 'read-tree removes worktree, dirty case' ' - echo init.t > .git/info/sparse-checkout && - git checkout -f top && - echo dirty > added && - git read-tree -m -u HEAD^ && - grep -q dirty added -' - -test_expect_success 'read-tree adds to worktree, absent case' ' - echo init.t > .git/info/sparse-checkout && - git checkout -f removed && - git read-tree -u -m HEAD^ && - test ! -f sub/added -' - -test_expect_success 'read-tree adds to worktree, dirty case' ' - echo init.t > .git/info/sparse-checkout && - git checkout -f removed && - mkdir sub && - echo dirty > sub/added && - git read-tree -u -m HEAD^ && - grep -q dirty sub/added -' - -test_done diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh new file mode 100755 index 0000000..62246db --- /dev/null +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +test_description='sparse checkout tests' + +. ./test-lib.sh + +cat >expected <> init.t && + mkdir sub && + touch sub/added && + git add init.t sub/added && + git commit -m "modified and added" && + git tag top && + git rm sub/added && + git commit -m removed && + git tag removed && + git checkout top && + git ls-files --stage > result && + test_cmp expected result +' + +cat >expected.swt < result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result +' + +test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + echo > .git/info/sparse-checkout + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-checkout and enabled' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + git read-tree --no-sparse-checkout -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +test_expect_success 'read-tree with empty .git/info/sparse-checkout' ' + git config core.sparsecheckout true && + echo > .git/info/sparse-checkout && + test_must_fail git read-tree -m -u HEAD && + git ls-files --stage > result && + test_cmp expected result && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + echo sub >> .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test ! -f init.t && + test -f sub/added +' + +cat >expected.swt < .git/info/sparse-checkout && + git read-tree -m -u HEAD && + git ls-files -t > result && + test_cmp expected.swt result && + test -f init.t && + test ! -f sub/added +' + +test_expect_success 'read-tree updates worktree, absent case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + git read-tree -m -u HEAD^ && + test ! -f init.t +' + +test_expect_success 'read-tree updates worktree, dirty case' ' + echo sub/added > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > init.t && + git read-tree -m -u HEAD^ && + grep -q dirty init.t && + rm init.t +' + +test_expect_success 'read-tree removes worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f top && + echo dirty > added && + git read-tree -m -u HEAD^ && + grep -q dirty added +' + +test_expect_success 'read-tree adds to worktree, absent case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + git read-tree -u -m HEAD^ && + test ! -f sub/added +' + +test_expect_success 'read-tree adds to worktree, dirty case' ' + echo init.t > .git/info/sparse-checkout && + git checkout -f removed && + mkdir sub && + echo dirty > sub/added && + git read-tree -u -m HEAD^ && + grep -q dirty sub/added +' + +test_done -- cgit v0.10.2-6-g49f6 From 56cac48c3550de88d71b3944576d44337261d71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 14 Dec 2009 18:43:58 +0700 Subject: ie_match_stat(): do not ignore skip-worktree bit with CE_MATCH_IGNORE_VALID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously CE_MATCH_IGNORE_VALID flag is used by both valid and skip-worktree bits. While the two bits have similar behaviour, sharing this flag means "git update-index --really-refresh" will ignore skip-worktree while it should not. Instead another flag is introduced to ignore skip-worktree bit, CE_MATCH_IGNORE_VALID only applies to valid bit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 39dc96a..7717a66 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2505,7 +2505,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st) return -1; return 0; } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID); + return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) diff --git a/cache.h b/cache.h index 2de00f8..9dcaf41 100644 --- a/cache.h +++ b/cache.h @@ -464,7 +464,9 @@ extern int index_name_is_other(const struct index_state *, const char *, int); /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 /* do not check the contents but report dirty on racily-clean entries */ -#define CE_MATCH_RACY_IS_DIRTY 02 +#define CE_MATCH_RACY_IS_DIRTY 02 +/* do stat comparison even if CE_SKIP_WORKTREE is true */ +#define CE_MATCH_IGNORE_SKIP_WORKTREE 04 extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); diff --git a/entry.c b/entry.c index f276cf3..efee21f 100644 --- a/entry.c +++ b/entry.c @@ -202,7 +202,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t len += ce_namelen(ce); if (!check_path(path, len, &st)) { - unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); if (!changed) return 0; if (!state->force) { diff --git a/read-cache.c b/read-cache.c index 5ee7d9d..b31861c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -259,13 +259,18 @@ int ie_match_stat(const struct index_state *istate, { unsigned int changed; int ignore_valid = options & CE_MATCH_IGNORE_VALID; + int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE; int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY; /* * If it's marked as always valid in the index, it's * valid whatever the checked-out copy says. + * + * skip-worktree has the same effect with higher precedence */ - if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + return 0; + if (!ignore_valid && (ce->ce_flags & CE_VALID)) return 0; /* @@ -564,7 +569,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int size, namelen, was_same; mode_t st_mode = st->st_mode; struct cache_entry *ce, *alias; - unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY; + unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY; int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND); int pretend = flags & ADD_CACHE_PRETEND; int intent_only = flags & ADD_CACHE_INTENT; @@ -1000,11 +1005,21 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, struct cache_entry *updated; int changed, size; int ignore_valid = options & CE_MATCH_IGNORE_VALID; + int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE; if (ce_uptodate(ce)) return ce; - if (!ignore_valid && ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))) { + /* + * CE_VALID or CE_SKIP_WORKTREE means the user promised us + * that the change to the work tree does not matter and told + * us not to worry. + */ + if (!ignore_skip_worktree && ce_skip_worktree(ce)) { + ce_mark_uptodate(ce); + return ce; + } + if (!ignore_valid && (ce->ce_flags & CE_VALID)) { ce_mark_uptodate(ce); return ce; } diff --git a/unpack-trees.c b/unpack-trees.c index 80ae2a0..d33b39e 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -569,7 +569,7 @@ static int verify_uptodate_1(struct cache_entry *ce, return 0; if (!lstat(ce->name, &st)) { - unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); if (!changed) return 0; /* @@ -701,7 +701,7 @@ static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct cache_entry *src; src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1); - return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID); + return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } /* @@ -1152,7 +1152,7 @@ int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) if (o->reset && !ce_uptodate(old) && !ce_skip_worktree(old)) { struct stat st; if (lstat(old->name, &st) || - ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) + ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE)) update |= CE_UPDATE; } add_entry(o, old, update, 0); -- cgit v0.10.2-6-g49f6 From 7fce6e3c9a3cf652099ee5f0e52b59c407692044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 14 Dec 2009 18:43:59 +0700 Subject: commit: correctly respect skip-worktree bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit b4d1690 (Teach Git to respect skip-worktree bit (reading part)) fails to make "git commit -- a b c" respect skip-worktree (i.e. not committing paths that are skip-worktree). This is because when the index is reset back to HEAD, all skip-worktree information is gone. This patch saves skip-worktree information in the string list of committed paths, then reuse it later on to skip skip-worktree paths. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-commit.c b/builtin-commit.c index a0b1fd3..9d59690 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -164,11 +164,15 @@ static int list_paths(struct string_list *list, const char *with_tree, for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + struct string_list_item *item; + if (ce->ce_flags & CE_UPDATE) continue; if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) continue; - string_list_insert(ce->name, list); + item = string_list_insert(ce->name, list); + if (ce_skip_worktree(ce)) + item->util = item; /* better a valid pointer than a fake one */ } return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); @@ -180,10 +184,9 @@ static void add_remove_files(struct string_list *list) for (i = 0; i < list->nr; i++) { struct stat st; struct string_list_item *p = &(list->items[i]); - int pos = index_name_pos(&the_index, p->string, strlen(p->string)); - struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos]; - if (ce && ce_skip_worktree(ce)) + /* p->util is skip-worktree */ + if (p->util) continue; if (!lstat(p->string, &st)) { diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index e996928..bb4066f 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -148,13 +148,13 @@ test_expect_success 'git-rm succeeds on skip-worktree absent entries' ' git rm 1 ' -test_expect_failure 'commit on skip-worktree absent entries' ' +test_expect_success 'commit on skip-worktree absent entries' ' git reset && setup_absent && test_must_fail git commit -m null 1 ' -test_expect_failure 'commit on skip-worktree dirty entries' ' +test_expect_success 'commit on skip-worktree dirty entries' ' git reset && setup_dirty && test_must_fail git commit -m null 1 -- cgit v0.10.2-6-g49f6 From a67e28116287ea84b162b22a5b69ec02ebd34023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Wed, 30 Dec 2009 21:11:44 +0700 Subject: grep: do not do external grep on skip-worktree entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip-worktree entries are not on disk. We cannot use external grep in such cases. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index 813fe97..25ee75d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -357,6 +357,21 @@ static void grep_add_color(struct strbuf *sb, const char *escape_seq) strbuf_setlen(sb, sb->len - 1); } +static int has_skip_worktree_entry(struct grep_opt *opt, const char **paths) +{ + int nr; + for (nr = 0; nr < active_nr; nr++) { + struct cache_entry *ce = active_cache[nr]; + if (!S_ISREG(ce->ce_mode)) + continue; + if (!pathspec_matches(paths, ce->name, opt->max_depth)) + continue; + if (ce_skip_worktree(ce)) + return 1; + } + return 0; +} + static int external_grep(struct grep_opt *opt, const char **paths, int cached) { int i, nr, argc, hit, len, status; @@ -365,7 +380,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) char *argptr = randarg; struct grep_pat *p; - if (opt->extended || (opt->relative && opt->prefix_length)) + if (opt->extended || (opt->relative && opt->prefix_length) + || has_skip_worktree_entry(opt, paths)) return -1; len = nr = 0; push_arg("grep"); -- cgit v0.10.2-6-g49f6 From cd83ac4156a211e019b47fa58de0784cee547ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 4 Jan 2010 19:34:14 +0700 Subject: t7002: set test prerequisite "external-grep" if supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add another test to set prerequisite EXTGREP if the current build supports external grep. This can be used to skip external grep only tests on builds that do not support this optimization. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index b4709e2..23eeb76 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -8,6 +8,18 @@ test_description='git grep various. . ./test-lib.sh +test_expect_success 'Check for external grep support' ' + case "$(git grep -h 2>&1|grep ext-grep)" in + *"(default)"*) + test_set_prereq EXTGREP + true;; + *"(ignored by this build)"*) + true;; + *) + false;; + esac +' + cat >hello.c < int main(int argc, const char **argv) -- cgit v0.10.2-6-g49f6 From 8740773ee5ef450cefd03d3576f348fe65e92edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 4 Jan 2010 19:34:15 +0700 Subject: t7002: test for not using external grep on skip-worktree paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index 25ee75d..04ac60a 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -232,6 +232,7 @@ static int exec_grep(int argc, const char **argv) int status; argv[argc] = NULL; + trace_argv_printf(argv, "trace: grep:"); pid = fork(); if (pid < 0) return pid; diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 23eeb76..99142fd 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -340,4 +340,16 @@ test_expect_success 'grep -p -B5' ' test_cmp expected actual ' +test_expect_success EXTGREP 'external grep is called' ' + GIT_TRACE=2 git grep foo >/dev/null 2>actual && + grep "trace: grep:.*foo" actual >/dev/null +' + +test_expect_success EXTGREP 'no external grep when skip-worktree entries exist' ' + git update-index --skip-worktree file && + GIT_TRACE=2 git grep foo >/dev/null 2>actual && + ! grep "trace: grep:" actual >/dev/null && + git update-index --no-skip-worktree file +' + test_done -- cgit v0.10.2-6-g49f6