From 29796c6ccff3e70622398379fdcdfa3fe43333ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 16:25:40 -0700 Subject: diff-index: report unmerged new entries Since an earlier change to diff-index by d1f2d7e (Make run_diff_index() use unpack_trees(), not read_tree(), 2008-01-19), we stopped reporting an unmerged path that does not exist in the tree, but we should. Signed-off-by: Junio C Hamano --- diff-lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..ad5b6cac7b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -348,8 +348,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { - if (tree) - diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1); + diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, + idx->sha1); return; } -- cgit 1.2.3-korg From 26da1d78674204c482ec90905dd4de3f6bcd3c5f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 4 Aug 2009 22:08:16 -0700 Subject: diff-index: keep the original index intact When comparing the index and a tree, we used to read the contents of the tree into stage #1 of the index and compared them with stage #0. In order not to lose sight of entries originally unmerged in the index, we hoisted them to stage #3 before reading the tree. Commit d1f2d7e (Make run_diff_index() use unpack_trees(), not read_tree(), 2008-01-19) changed all this. These days, we instead use unpack_trees() API to traverse the tree and compare the contents with the index, without modifying the index at all. There is no reason to hoist the unmerged entries to stage #3 anymore. Signed-off-by: Junio C Hamano --- diff-lib.c | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index ad5b6cac7b..2a82dac101 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -308,22 +308,6 @@ static int show_modified(struct rev_info *revs, return 0; } -/* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= CE_STAGEMASK; - } -} - /* * This gets a mix of an existing index and a tree, one pathname entry * at a time. The index entry may be a single stage-0 one, but it could @@ -435,8 +419,6 @@ int run_diff_index(struct rev_info *revs, int cached) struct unpack_trees_options opts; struct tree_desc t; - mark_merge_entries(); - ent = revs->pending.objects[0].item; tree_name = revs->pending.objects[0].name; tree = parse_tree_indirect(ent->sha1); -- cgit 1.2.3-korg From 540e694b139dd034b21de087001ac9b6d7606c94 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Tue, 11 Aug 2009 22:43:59 +0700 Subject: Prevent diff machinery from examining assume-unchanged entries on worktree 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-lib.c | 6 ++++-- t/t4039-diff-assume-unchanged.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100755 t/t4039-diff-assume-unchanged.sh (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..22da66ef14 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -162,7 +162,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (ce_uptodate(ce)) continue; - changed = check_removed(ce, &st); + /* If CE_VALID is set, don't look at workdir for file removal */ + changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st); if (changed) { if (changed < 0) { perror(ce->name); @@ -337,6 +338,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct rev_info *revs = o->unpack_data; 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)); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". @@ -344,7 +347,6 @@ static void do_oneway_diff(struct unpack_trees_options *o, * But with the revision flag parsing, that's found in * "!revs->ignore_merges". */ - cached = o->index_only; match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh new file mode 100755 index 0000000000..9d9498bd95 --- /dev/null +++ b/t/t4039-diff-assume-unchanged.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='diff with assume-unchanged entries' + +. ./test-lib.sh + +# external diff has been tested in t4020-diff-external.sh + +test_expect_success 'setup' ' + echo zero > zero && + git add zero && + git commit -m zero && + echo one > one && + echo two > two && + git add one two && + git commit -m onetwo && + git update-index --assume-unchanged one && + echo borked >> one && + test "$(git ls-files -v one)" = "h one" +' + +test_expect_success 'diff-index does not examine assume-unchanged entries' ' + git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171 +' + +test_expect_success 'diff-files does not examine assume-unchanged entries' ' + rm one && + test -z "$(git diff-files -- one)" +' + +test_done -- cgit 1.2.3-korg From b4d1690df11ae6ce382b93778616b1a20f1774ff Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy 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 --- builtin-commit.c | 5 ++ builtin-grep.c | 2 +- builtin-ls-files.c | 2 + builtin-update-index.c | 38 +++++---- diff-lib.c | 5 +- diff.c | 2 +- read-cache.c | 8 +- t/t7011-skip-worktree-reading.sh | 163 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 199 insertions(+), 26 deletions(-) create mode 100755 t/t7011-skip-worktree-reading.sh (limited to 'diff-lib.c') diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06fbf..a0b1fd35cb 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 ad0e0a5385..813fe9778a 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 c1afbad453..ad7e44784f 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 5e97d09497..97b9ea61f7 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 22da66ef14..b0b379d9d2 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 cd35e0c2d7..3970df4afc 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 4e3e272ee4..5ee7d9da9c 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 0000000000..e996928de2 --- /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 1.2.3-korg From da8ba5e7da01be597aa9417c563dbd516ea5f204 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 17 Sep 2009 22:12:17 -0700 Subject: diff-lib.c: fix misleading comments on oneway_diff() 20a16eb (unpack_trees(): fix diff-index regression., 2008-03-10) adjusted diff-index to the new world order since 34110cd (Make 'unpack_trees()' have a separate source and destination index, 2008-03-06). Callbacks are expected to return anything non-negative as "success", and instead of reporting how many index entries they have processed, they are expected to advance o->pos themselves. The code did so, but a stale comment was left behind. Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 0c74ef5cbe..adf1c5fdee 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -383,7 +383,7 @@ static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_op * For diffing, the index is more important, and we only have a * single tree. * - * We're supposed to return how many index entries we want to skip. + * We're supposed to advance o->pos to skip what we have already processed. * * This wrapper makes it all more readable, and takes care of all * the fairly complex unpack_trees() semantic requirements, including -- cgit 1.2.3-korg From da165f470ede6540873d33cb8bc2ff8e13c60520 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 7 Jan 2010 14:59:54 -0800 Subject: unpack-trees.c: prepare for looking ahead in the index This prepares but does not yet implement a look-ahead in the index entries when traverse-trees.c decides to give us tree entries in an order that does not match what is in the index. A case where a look-ahead in the index is necessary happens when merging branch B into branch A while the index matches the current branch A, using a tree O as their common ancestor, and these three trees looks like this: O A B t t t-i t-i t-i t-j t-j t/1 t/2 The traverse_trees() function gets "t", "t-i" and "t" from trees O, A and B first, and notices that A may have a matching "t" behind "t-i" and "t-j" (indeed it does), and tells A to give that entry instead. After unpacking blob "t" from tree B (as it hasn't changed since O in B and A removed it, it will result in its removal), it descends into directory "t/". The side that walked index in parallel to the tree traversal used to be implemented with one pointer, o->pos, that points at the next index entry to be processed. When this happens, the pointer o->pos still points at "t-i" that is the first entry. We should be able to skip "t-i" and "t-j" and locate "t/1" from the index while the recursive invocation of traverse_trees() walks and match entries found there, and later come back to process "t-i". While that look-ahead is not implemented yet, this adds a flag bit, CE_UNPACKED, to mark the entries in the index that has already been processed. o->pos pointer has been renamed to o->cache_bottom and it points at the first entry that may still need to be processed. Signed-off-by: Junio C Hamano --- cache.h | 2 + diff-lib.c | 18 ----- unpack-trees.c | 216 +++++++++++++++++++++++++++++++++++++++++++++------------ unpack-trees.h | 2 +- 4 files changed, 173 insertions(+), 65 deletions(-) (limited to 'diff-lib.c') diff --git a/cache.h b/cache.h index bf468e5235..9a9596386d 100644 --- a/cache.h +++ b/cache.h @@ -178,6 +178,8 @@ struct cache_entry { #define CE_HASHED (0x100000) #define CE_UNHASHED (0x200000) +#define CE_UNPACKED (0x1000000) + /* * Extended on-disk flags */ diff --git a/diff-lib.c b/diff-lib.c index adf1c5fdee..f759917d33 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -359,21 +359,6 @@ static void do_oneway_diff(struct unpack_trees_options *o, show_modified(revs, tree, idx, 1, cached, match_missing); } -static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) -{ - int len = ce_namelen(ce); - const struct index_state *index = o->src_index; - - while (o->pos < index->cache_nr) { - struct cache_entry *next = index->cache[o->pos]; - if (len != ce_namelen(next)) - break; - if (memcmp(ce->name, next->name, len)) - break; - o->pos++; - } -} - /* * The unpack_trees() interface is designed for merging, so * the different source entries are designed primarily for @@ -395,9 +380,6 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) struct cache_entry *tree = src[1]; struct rev_info *revs = o->unpack_data; - if (idx && ce_stage(idx)) - skip_same_name(idx, o); - /* * Unpack-trees generates a DF/conflict entry if * there was a directory in the index and a tree diff --git a/unpack-trees.c b/unpack-trees.c index 7413ff0b93..685adb4b77 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -126,18 +126,109 @@ static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_o return ret; } -static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) +static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o) +{ + ce->ce_flags |= CE_UNPACKED; + + if (o->cache_bottom < o->src_index->cache_nr && + o->src_index->cache[o->cache_bottom] == ce) { + int bottom = o->cache_bottom; + while (bottom < o->src_index->cache_nr && + o->src_index->cache[bottom]->ce_flags & CE_UNPACKED) + bottom++; + o->cache_bottom = bottom; + } +} + +static void mark_all_ce_unused(struct index_state *index) +{ + int i; + for (i = 0; i < index->cache_nr; i++) + index->cache[i]->ce_flags &= ~CE_UNPACKED; +} + +static int locate_in_src_index(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos = index_name_pos(index, ce->name, len); + if (pos < 0) + pos = -1 - pos; + return pos; +} + +/* + * We call unpack_index_entry() with an unmerged cache entry + * only in diff-index, and it wants a single callback. Skip + * the other unmerged entry with the same name. + */ +static void mark_ce_used_same_name(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos; + + for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) { + struct cache_entry *next = index->cache[pos]; + if (len != ce_namelen(next) || + memcmp(ce->name, next->name, len)) + break; + mark_ce_used(next, o); + } +} + +static struct cache_entry *next_cache_entry(struct unpack_trees_options *o) +{ + const struct index_state *index = o->src_index; + int pos = o->cache_bottom; + + while (pos < index->cache_nr) { + struct cache_entry *ce = index->cache[pos]; + if (!(ce->ce_flags & CE_UNPACKED)) + return ce; + pos++; + } + return NULL; +} + +static void add_same_unmerged(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct index_state *index = o->src_index; + int len = ce_namelen(ce); + int pos = index_name_pos(index, ce->name, len); + + if (0 <= pos) + die("programming error in a caller of mark_ce_used_same_name"); + for (pos = -pos - 1; pos < index->cache_nr; pos++) { + struct cache_entry *next = index->cache[pos]; + if (len != ce_namelen(next) || + memcmp(ce->name, next->name, len)) + break; + add_entry(o, next, 0, 0); + mark_ce_used(next, o); + } +} + +static int unpack_index_entry(struct cache_entry *ce, + struct unpack_trees_options *o) { struct cache_entry *src[5] = { ce, NULL, }; + int ret; - o->pos++; + mark_ce_used(ce, o); if (ce_stage(ce)) { if (o->skip_unmerged) { add_entry(o, ce, 0, 0); return 0; } } - return call_unpack_fn(src, o); + ret = call_unpack_fn(src, o); + if (ce_stage(ce)) + mark_ce_used_same_name(ce, o); + return ret; } static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) @@ -212,6 +303,20 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf return ce_namelen(ce) > traverse_path_len(info, n); } +static int ce_in_traverse_path(const struct cache_entry *ce, + const struct traverse_info *info) +{ + if (!info->prev) + return 1; + if (do_compare_entry(ce, info->prev, &info->name)) + return 0; + /* + * If ce (blob) is the same name as the path (which is a tree + * we will be descending into), it won't be inside it. + */ + return (info->pathlen < ce_namelen(ce)); +} + static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage) { int len = traverse_path_len(info, n); @@ -300,23 +405,27 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str /* Are we supposed to look at the index too? */ if (o->merge) { - while (o->pos < o->src_index->cache_nr) { - struct cache_entry *ce = o->src_index->cache[o->pos]; - int cmp = compare_entry(ce, info, p); + while (1) { + struct cache_entry *ce = next_cache_entry(o); + int cmp; + if (!ce) + break; + cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) return unpack_failed(o, NULL); continue; } if (!cmp) { - o->pos++; if (ce_stage(ce)) { /* - * If we skip unmerged index entries, we'll skip this - * entry *and* the tree entries associated with it! + * If we skip unmerged index + * entries, we'll skip this + * entry *and* the tree + * entries associated with it! */ if (o->skip_unmerged) { - add_entry(o, ce, 0, 0); + add_same_unmerged(ce, o); return mask; } } @@ -329,6 +438,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) return -1; + if (src[0]) { + if (ce_stage(src[0])) + mark_ce_used_same_name(src[0], o); + else + mark_ce_used(src[0], o); + } + /* Now handle any directories.. */ if (dirmask) { unsigned long conflicts = mask & ~dirmask; @@ -345,11 +461,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str matches = cache_tree_matches_traversal(o->src_index->cache_tree, names, info); /* - * Everything under the name matches. Adjust o->pos to - * skip the entire hierarchy. + * Everything under the name matches; skip the + * entire hierarchy. diff_index_cached codepath + * special cases D/F conflicts in such a way that + * it does not do any look-ahead, so this is safe. */ if (matches) { - o->pos += matches; + o->cache_bottom += matches; return mask; } } @@ -382,11 +500,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; - if (o->src_index) { - o->result.timestamp.sec = o->src_index->timestamp.sec; - o->result.timestamp.nsec = o->src_index->timestamp.nsec; - } + o->result.timestamp.sec = o->src_index->timestamp.sec; + o->result.timestamp.nsec = o->src_index->timestamp.nsec; o->merge_size = len; + mark_all_ce_unused(o->src_index); if (!dfc) dfc = xcalloc(1, cache_entry_size(0)); @@ -400,18 +517,38 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options info.fn = unpack_callback; info.data = o; + if (o->prefix) { + /* + * Unpack existing index entries that sort before the + * prefix the tree is spliced into. Note that o->merge + * is always true in this case. + */ + while (1) { + struct cache_entry *ce = next_cache_entry(o); + if (!ce) + break; + if (ce_in_traverse_path(ce, &info)) + break; + if (unpack_index_entry(ce, o) < 0) + goto return_failed; + } + } + if (traverse_trees(len, t, &info) < 0) - return unpack_failed(o, NULL); + goto return_failed; } /* 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]; + while (1) { + struct cache_entry *ce = next_cache_entry(o); + if (!ce) + break; if (unpack_index_entry(ce, o) < 0) - return unpack_failed(o, NULL); + goto return_failed; } } + mark_all_ce_unused(o->src_index); if (o->trivial_merges_only && o->nontrivial_merge) return unpack_failed(o, "Merge requires file-level merging"); @@ -421,6 +558,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options if (o->dst_index) *o->dst_index = o->result; return ret; + +return_failed: + mark_all_ce_unused(o->src_index); + return unpack_failed(o, NULL); } /* Here come the merge functions */ @@ -522,7 +663,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - for (i = o->pos; i < o->src_index->cache_nr; i++) { + for (i = locate_in_src_index(ce, o); + i < o->src_index->cache_nr; + i++) { struct cache_entry *ce2 = o->src_index->cache[i]; int len = ce_namelen(ce2); if (len < namelen || @@ -530,12 +673,14 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, ce2->name[namelen] != '/') break; /* - * ce2->name is an entry in the subdirectory. + * ce2->name is an entry in the subdirectory to be + * removed. */ if (!ce_stage(ce2)) { if (verify_uptodate(ce2, o)) return -1; add_entry(o, ce2, CE_REMOVE, 0); + mark_ce_used(ce2, o); } cnt++; } @@ -591,7 +736,6 @@ static int verify_absent(struct cache_entry *ce, const char *action, return 0; if (!lstat(ce->name, &st)) { - int ret; int dtype = ce_to_dtype(ce); struct cache_entry *result; @@ -619,28 +763,8 @@ static int verify_absent(struct cache_entry *ce, const char *action, * files that are in "foo/" we would lose * them. */ - ret = verify_clean_subdirectory(ce, action, o); - if (ret < 0) - return ret; - - /* - * If this removed entries from the index, - * what that means is: - * - * (1) the caller unpack_callback() saw path/foo - * in the index, and it has not removed it because - * it thinks it is handling 'path' as blob with - * D/F conflict; - * (2) we will return "ok, we placed a merged entry - * in the index" which would cause o->pos to be - * incremented by one; - * (3) however, original o->pos now has 'path/foo' - * marked with "to be removed". - * - * We need to increment it by the number of - * deleted entries here. - */ - o->pos += ret; + if (verify_clean_subdirectory(ce, action, o) < 0) + return -1; return 0; } diff --git a/unpack-trees.h b/unpack-trees.h index d19df44f40..9a0733ea85 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -30,7 +30,7 @@ struct unpack_trees_options { diff_index_cached, gently; const char *prefix; - int pos; + int cache_bottom; struct dir_struct *dir; merge_fn_t fn; struct unpack_trees_error_msgs msgs; -- cgit 1.2.3-korg From 730f72840cc50c523fe4cdd796ea2d2fc4571a28 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 20 Sep 2009 00:03:39 -0700 Subject: unpack-trees.c: look ahead in the index This makes the traversal of index be in sync with the tree traversal. When unpack_callback() is fed a set of tree entries from trees, it inspects the name of the entry and checks if the an index entry with the same name could be hiding behind the current index entry, and (1) if the name appears in the index as a leaf node, it is also fed to the n_way_merge() callback function; (2) if the name is a directory in the index, i.e. there are entries in that are underneath it, then nothing is fed to the n_way_merge() callback function; (3) otherwise, if the name comes before the first eligible entry in the index, the index entry is first unpacked alone. When traverse_trees_recursive() descends into a subdirectory, the cache_bottom pointer is moved to walk index entries within that directory. All of these are omitted for diff-index, which does not even want to be fed an index entry and a tree entry with D/F conflicts. This fixes 3-way read-tree and exposes a bug in other parts of the system in t6035, test #5. The test prepares these three trees: O = HEAD^ 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b/c/d 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/x A = HEAD 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b/c/d 100644 blob 587be6b4c3f93f93c489c0111bba5596147a26cb a/x B = master 120000 blob a36b77384451ea1de7bd340ffca868249626bc52 a/b 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/x With a clean index that matches HEAD, running git read-tree -m -u --aggressive $O $A $B now yields 120000 a36b77384451ea1de7bd340ffca868249626bc52 3 a/b 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 a/b-2/c/d 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1 a/b/c/d 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 2 a/b/c/d 100644 587be6b4c3f93f93c489c0111bba5596147a26cb 0 a/x which is correct. "master" created "a/b" symlink that did not exist, and removed "a/b/c/d" while HEAD did not do touch either path. Before this series, read-tree did not notice the situation and resolved addition of "a/b" and removal of "a/b/c/d" independently. If A = HEAD had another path "a/b/c/e" added, this merge should conflict but instead it silently resolved "a/b" and then immediately overwrote it to add "a/b/c/e", which was quite bogus. Tests in t1012 start to work with this. Signed-off-by: Junio C Hamano --- diff-lib.c | 1 + diff.c | 17 +++++++ diff.h | 1 + t/t1012-read-tree-df.sh | 8 ++-- unpack-trees.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 140 insertions(+), 7 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index f759917d33..c9998f4c91 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -425,6 +425,7 @@ int run_diff_index(struct rev_info *revs, int cached) exit(128); diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); + diffcore_fix_diff_index(&revs->diffopt); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; diff --git a/diff.c b/diff.c index 08bbd3e907..3bfb4a19d2 100644 --- a/diff.c +++ b/diff.c @@ -3628,6 +3628,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) *q = outq; } +static int diffnamecmp(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + const char *name_a, *name_b; + + name_a = a->one ? a->one->path : a->two->path; + name_b = b->one ? b->one->path : b->two->path; + return strcmp(name_a, name_b); +} + +void diffcore_fix_diff_index(struct diff_options *options) +{ + struct diff_queue_struct *q = &diff_queued_diff; + qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp); +} + void diffcore_std(struct diff_options *options) { if (options->skip_stat_unmatch) diff --git a/diff.h b/diff.h index 15fcecdecd..471f606a92 100644 --- a/diff.h +++ b/diff.h @@ -208,6 +208,7 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_REGEX 2 extern void diffcore_std(struct diff_options *); +extern void diffcore_fix_diff_index(struct diff_options *); #define COMMON_DIFF_OPTIONS_HELP \ "\ncommon diff options:\n" \ diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh index f1e650ac39..9811d467da 100755 --- a/t/t1012-read-tree-df.sh +++ b/t/t1012-read-tree-df.sh @@ -51,7 +51,7 @@ test_expect_success setup ' : ' -test_expect_failure '3-way (1)' ' +test_expect_success '3-way (1)' ' settree A-000 && git read-tree -m -u O-000 A-000 B-000 && checkindex <<-EOF @@ -63,7 +63,7 @@ test_expect_failure '3-way (1)' ' EOF ' -test_expect_failure '3-way (2)' ' +test_expect_success '3-way (2)' ' settree A-001 && git read-tree -m -u O-000 A-001 B-000 && checkindex <<-EOF @@ -76,7 +76,7 @@ test_expect_failure '3-way (2)' ' EOF ' -test_expect_failure '3-way (3)' ' +test_expect_success '3-way (3)' ' settree A-010 && git read-tree -m -u O-010 A-010 B-010 && checkindex <<-EOF @@ -90,7 +90,7 @@ test_expect_failure '3-way (3)' ' EOF ' -test_expect_failure '2-way (1)' ' +test_expect_success '2-way (1)' ' settree O-020 && git read-tree -m -u O-020 A-020 && checkindex <<-EOF diff --git a/unpack-trees.c b/unpack-trees.c index 685adb4b77..74cabc36ff 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -231,9 +231,37 @@ static int unpack_index_entry(struct cache_entry *ce, return ret; } +static int find_cache_pos(struct traverse_info *, const struct name_entry *); + +static void restore_cache_bottom(struct traverse_info *info, int bottom) +{ + struct unpack_trees_options *o = info->data; + + if (o->diff_index_cached) + return; + o->cache_bottom = bottom; +} + +static int switch_cache_bottom(struct traverse_info *info) +{ + struct unpack_trees_options *o = info->data; + int ret, pos; + + if (o->diff_index_cached) + return 0; + ret = o->cache_bottom; + pos = find_cache_pos(info->prev, &info->name); + + if (pos < -1) + o->cache_bottom = -2 - pos; + else if (pos < 0) + o->cache_bottom = o->src_index->cache_nr; + return ret; +} + static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) { - int i; + int i, ret, bottom; struct tree_desc t[MAX_UNPACK_TREES]; struct traverse_info newinfo; struct name_entry *p; @@ -254,7 +282,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long sha1 = names[i].sha1; fill_tree_descriptor(t+i, sha1); } - return traverse_trees(n, t, &newinfo); + + bottom = switch_cache_bottom(&newinfo); + ret = traverse_trees(n, t, &newinfo); + restore_cache_bottom(&newinfo, bottom); + return ret; } /* @@ -393,6 +425,82 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) return -1; } +/* NEEDSWORK: give this a better name and share with tree-walk.c */ +static int name_compare(const char *a, int a_len, + const char *b, int b_len) +{ + int len = (a_len < b_len) ? a_len : b_len; + int cmp = memcmp(a, b, len); + if (cmp) + return cmp; + return (a_len - b_len); +} + +/* + * The tree traversal is looking at name p. If we have a matching entry, + * return it. If name p is a directory in the index, do not return + * anything, as we will want to match it when the traversal descends into + * the directory. + */ +static int find_cache_pos(struct traverse_info *info, + const struct name_entry *p) +{ + int pos; + struct unpack_trees_options *o = info->data; + struct index_state *index = o->src_index; + int pfxlen = info->pathlen; + int p_len = tree_entry_len(p->path, p->sha1); + + for (pos = o->cache_bottom; pos < index->cache_nr; pos++) { + struct cache_entry *ce = index->cache[pos]; + const char *ce_name, *ce_slash; + int cmp, ce_len; + + if (!ce_in_traverse_path(ce, info)) + continue; + if (ce->ce_flags & CE_UNPACKED) + continue; + ce_name = ce->name + pfxlen; + ce_slash = strchr(ce_name, '/'); + if (ce_slash) + ce_len = ce_slash - ce_name; + else + ce_len = ce_namelen(ce) - pfxlen; + cmp = name_compare(p->path, p_len, ce_name, ce_len); + /* + * Exact match; if we have a directory we need to + * delay returning it. + */ + if (!cmp) + return ce_slash ? -2 - pos : pos; + if (0 < cmp) + continue; /* keep looking */ + /* + * ce_name sorts after p->path; could it be that we + * have files under p->path directory in the index? + * E.g. ce_name == "t-i", and p->path == "t"; we may + * have "t/a" in the index. + */ + if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) && + ce_name[p_len] < '/') + continue; /* keep looking */ + break; + } + return -1; +} + +static struct cache_entry *find_cache_entry(struct traverse_info *info, + const struct name_entry *p) +{ + int pos = find_cache_pos(info, p); + struct unpack_trees_options *o = info->data; + + if (0 <= pos) + return o->src_index->cache[pos]; + else + return NULL; +} + static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; @@ -406,8 +514,14 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str /* Are we supposed to look at the index too? */ if (o->merge) { while (1) { - struct cache_entry *ce = next_cache_entry(o); int cmp; + struct cache_entry *ce; + + if (o->diff_index_cached) + ce = next_cache_entry(o); + else + ce = find_cache_entry(info, p); + if (!ce) break; cmp = compare_entry(ce, info, p); -- cgit 1.2.3-korg From ee6fc514f2df821c2719cc49499a56ef2fb136b0 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Sat, 16 Jan 2010 18:42:24 +0100 Subject: Show submodules as modified when they contain a dirty work tree Until now a submodule only then showed up as modified in the supermodule when the last commit in the submodule differed from the one in the index or the diffed against commit of the superproject. A dirty work tree containing new untracked or modified files in a submodule was undetectable when looking at it from the superproject. Now git status and git diff (against the work tree) in the superproject will also display submodules as modified when they contain untracked or modified files, even if the compared ref matches the HEAD of the submodule. Signed-off-by: Jens Lehmann Signed-off-by: Nanako Shiraishi Signed-off-by: Junio C Hamano --- diff-lib.c | 8 +++-- submodule.c | 49 ++++++++++++++++++++++++++ submodule.h | 1 + t/t7506-status-submodule.sh | 83 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 124 insertions(+), 17 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 1c7e652a80..9cdf6daa90 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -10,6 +10,7 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "refs.h" +#include "submodule.h" /* * diff-files @@ -159,7 +160,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce) || ce_skip_worktree(ce)) + if ((ce_uptodate(ce) && !S_ISGITLINK(ce->ce_mode)) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ @@ -176,6 +177,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } changed = ce_match_stat(ce, &st, ce_option); + if (S_ISGITLINK(ce->ce_mode) && !changed) + changed = is_submodule_modified(ce->name); if (!changed) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) @@ -230,7 +233,8 @@ static int get_stat_data(struct cache_entry *ce, return -1; } changed = ce_match_stat(ce, &st, 0); - if (changed) { + if (changed + || (S_ISGITLINK(ce->ce_mode) && is_submodule_modified(ce->name))) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; } diff --git a/submodule.c b/submodule.c index 86aad653b7..3f851deb6a 100644 --- a/submodule.c +++ b/submodule.c @@ -4,6 +4,7 @@ #include "diff.h" #include "commit.h" #include "revision.h" +#include "run-command.h" int add_submodule_odb(const char *path) { @@ -112,3 +113,51 @@ void show_submodule_summary(FILE *f, const char *path, } strbuf_release(&sb); } + +int is_submodule_modified(const char *path) +{ + int len; + struct child_process cp; + const char *argv[] = { + "status", + "--porcelain", + NULL, + }; + char *env[3]; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s/.git/", path); + if (!is_directory(buf.buf)) { + strbuf_release(&buf); + /* The submodule is not checked out, so it is not modified */ + return 0; + + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "GIT_WORK_TREE=%s", path); + env[0] = strbuf_detach(&buf, NULL); + strbuf_addf(&buf, "GIT_DIR=%s/.git", path); + env[1] = strbuf_detach(&buf, NULL); + env[2] = NULL; + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = (const char *const *)env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + if (start_command(&cp)) + die("Could not run git status --porcelain"); + + len = strbuf_read(&buf, cp.out, 1024); + close(cp.out); + + if (finish_command(&cp)) + die("git status --porcelain failed"); + + free(env[0]); + free(env[1]); + strbuf_release(&buf); + return len != 0; +} diff --git a/submodule.h b/submodule.h index 4c0269d679..0773121eb5 100644 --- a/submodule.h +++ b/submodule.h @@ -4,5 +4,6 @@ void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], const char *del, const char *add, const char *reset); +int is_submodule_modified(const char *path); #endif diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 3ca17abad1..253c334319 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -5,34 +5,87 @@ test_description='git status for submodule' . ./test-lib.sh test_expect_success 'setup' ' - test_create_repo sub - cd sub && - : >bar && - git add bar && - git commit -m " Add bar" && - cd .. && - git add sub && + test_create_repo sub && + ( + cd sub && + : >bar && + git add bar && + git commit -m " Add bar" && + : >foo && + git add foo && + git commit -m " Add foo" + ) && + echo output > .gitignore && + git add sub .gitignore && git commit -m "Add submodule sub" ' test_expect_success 'status clean' ' - git status | - grep "nothing to commit" + git status >output && + grep "nothing to commit" output ' + test_expect_success 'commit --dry-run -a clean' ' - git commit --dry-run -a | - grep "nothing to commit" + test_must_fail git commit --dry-run -a >output && + grep "nothing to commit" output +' + +test_expect_success 'status with modified file in submodule' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with modified file in submodule (porcelain)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added file in submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with added file in submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with untracked file in submodule' ' + (cd sub && git reset --hard) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub" output +' + +test_expect_success 'status with untracked file in submodule (porcelain)' ' + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF ' + test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' + test_expect_success 'status clean (empty submodule dir)' ' - git status | - grep "nothing to commit" + git status >output && + grep "nothing to commit" output ' + test_expect_success 'status -a clean (empty submodule dir)' ' - git commit --dry-run -a | - grep "nothing to commit" + test_must_fail git commit --dry-run -a >output && + grep "nothing to commit" output ' test_done -- cgit 1.2.3-korg From e3d42c4773bccebb50f01b108d20b06c6a11e615 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Mon, 18 Jan 2010 21:26:18 +0100 Subject: Performance optimization for detection of modified submodules In the worst case is_submodule_modified() got called three times for each submodule. The information we got from scanning the whole submodule tree the first time can be reused instead. New parameters have been added to diff_change() and diff_addremove(), the information is stored in a new member of struct diff_filespec. Its value is then reused instead of calling is_submodule_modified() again. When no explicit "-dirty" is needed in the output the call to is_submodule_modified() is not necessary when the submodules HEAD already disagrees with the ref of the superproject, as this alone marks it as modified. To achieve that, get_stat_data() got an extra argument. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 46 +++++++++++++++++++++++++++++++--------------- diff.c | 15 +++++++++++---- diff.h | 10 ++++++---- diffcore.h | 1 + revision.c | 5 +++-- tree-diff.c | 8 ++++---- 6 files changed, 56 insertions(+), 29 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 9cdf6daa90..23e180eed1 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -73,6 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) unsigned int oldmode, newmode; struct cache_entry *ce = active_cache[i]; int changed; + unsigned dirty_submodule = 0; if (DIFF_OPT_TST(&revs->diffopt, QUICK) && DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) @@ -173,12 +174,16 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (silent_on_removed) continue; diff_addremove(&revs->diffopt, '-', ce->ce_mode, - ce->sha1, ce->name); + ce->sha1, ce->name, 0); continue; } changed = ce_match_stat(ce, &st, ce_option); - if (S_ISGITLINK(ce->ce_mode) && !changed) - changed = is_submodule_modified(ce->name); + if (S_ISGITLINK(ce->ce_mode) + && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) + && is_submodule_modified(ce->name)) { + changed = 1; + dirty_submodule = 1; + } if (!changed) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) @@ -188,7 +193,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) newmode = ce_mode_from_stat(ce, st.st_mode); diff_change(&revs->diffopt, oldmode, newmode, ce->sha1, (changed ? null_sha1 : ce->sha1), - ce->name); + ce->name, 0, dirty_submodule); } diffcore_std(&revs->diffopt); @@ -204,16 +209,18 @@ int run_diff_files(struct rev_info *revs, unsigned int option) static void diff_index_show_file(struct rev_info *revs, const char *prefix, struct cache_entry *ce, - const unsigned char *sha1, unsigned int mode) + const unsigned char *sha1, unsigned int mode, + unsigned dirty_submodule) { diff_addremove(&revs->diffopt, prefix[0], mode, - sha1, ce->name); + sha1, ce->name, dirty_submodule); } static int get_stat_data(struct cache_entry *ce, const unsigned char **sha1p, unsigned int *modep, - int cached, int match_missing) + int cached, int match_missing, + unsigned *dirty_submodule, int output_format) { const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; @@ -233,8 +240,13 @@ static int get_stat_data(struct cache_entry *ce, return -1; } changed = ce_match_stat(ce, &st, 0); - if (changed - || (S_ISGITLINK(ce->ce_mode) && is_submodule_modified(ce->name))) { + if (S_ISGITLINK(ce->ce_mode) + && (!changed || (output_format & DIFF_FORMAT_PATCH)) + && is_submodule_modified(ce->name)) { + changed = 1; + *dirty_submodule = 1; + } + if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; } @@ -251,15 +263,17 @@ static void show_new_file(struct rev_info *revs, { const unsigned char *sha1; unsigned int mode; + unsigned dirty_submodule = 0; /* * New file in the index: it might actually be different in * the working copy. */ - if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) + if (get_stat_data(new, &sha1, &mode, cached, match_missing, + &dirty_submodule, revs->diffopt.output_format) < 0) return; - diff_index_show_file(revs, "+", new, sha1, mode); + diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule); } static int show_modified(struct rev_info *revs, @@ -270,11 +284,13 @@ static int show_modified(struct rev_info *revs, { unsigned int mode, oldmode; const unsigned char *sha1; + unsigned dirty_submodule = 0; - if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { + if (get_stat_data(new, &sha1, &mode, cached, match_missing, + &dirty_submodule, revs->diffopt.output_format) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, - old->sha1, old->ce_mode); + old->sha1, old->ce_mode, 0); return -1; } @@ -309,7 +325,7 @@ static int show_modified(struct rev_info *revs, return 0; diff_change(&revs->diffopt, oldmode, mode, - old->sha1, sha1, old->name); + old->sha1, sha1, old->name, 0, dirty_submodule); return 0; } @@ -356,7 +372,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something removed from the tree? */ if (!idx) { - diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode); + diff_index_show_file(revs, "-", tree, tree->sha1, tree->ce_mode, 0); return; } diff --git a/diff.c b/diff.c index 750c066a5e..8986873c0e 100644 --- a/diff.c +++ b/diff.c @@ -2032,7 +2032,7 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) char *data = xmalloc(100), *dirty = ""; /* Are we looking at the work tree? */ - if (!s->sha1_valid && is_submodule_modified(s->path)) + if (!s->sha1_valid && s->dirty_submodule) dirty = "-dirty"; len = snprintf(data, 100, @@ -3719,7 +3719,7 @@ int diff_result_code(struct diff_options *opt, int status) void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *concatpath) + const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -3751,8 +3751,10 @@ void diff_addremove(struct diff_options *options, if (addremove != '+') fill_filespec(one, sha1, mode); - if (addremove != '-') + if (addremove != '-') { fill_filespec(two, sha1, mode); + two->dirty_submodule = dirty_submodule; + } diff_queue(&diff_queued_diff, one, two); if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) @@ -3763,7 +3765,8 @@ void diff_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *concatpath) + const char *concatpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) { struct diff_filespec *one, *two; @@ -3776,6 +3779,8 @@ void diff_change(struct diff_options *options, const unsigned char *tmp_c; tmp = old_mode; old_mode = new_mode; new_mode = tmp; tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule; + new_dirty_submodule = tmp; } if (options->prefix && @@ -3786,6 +3791,8 @@ void diff_change(struct diff_options *options, two = alloc_filespec(concatpath); fill_filespec(one, old_sha1, old_mode); fill_filespec(two, new_sha1, new_mode); + one->dirty_submodule = old_dirty_submodule; + two->dirty_submodule = new_dirty_submodule; diff_queue(&diff_queued_diff, one, two); if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) diff --git a/diff.h b/diff.h index 6f6d0ed01d..968a8dce95 100644 --- a/diff.h +++ b/diff.h @@ -14,12 +14,13 @@ typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *fullpath); + const char *fullpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule); typedef void (*add_remove_fn_t)(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath); + const char *fullpath, unsigned dirty_submodule); typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); @@ -177,13 +178,14 @@ extern void diff_addremove(struct diff_options *, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath); + const char *fullpath, unsigned dirty_submodule); extern void diff_change(struct diff_options *, unsigned mode1, unsigned mode2, const unsigned char *sha1, const unsigned char *sha2, - const char *fullpath); + const char *fullpath, + unsigned dirty_submodule1, unsigned dirty_submodule2); extern void diff_unmerge(struct diff_options *, const char *path, diff --git a/diffcore.h b/diffcore.h index 5b634585e8..66687c3fe5 100644 --- a/diffcore.h +++ b/diffcore.h @@ -42,6 +42,7 @@ struct diff_filespec { #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ + unsigned dirty_submodule : 1; /* For submodules: its work tree is dirty */ struct userdiff_driver *driver; /* data should be considered "binary"; -1 means "don't know yet" */ diff --git a/revision.c b/revision.c index 25fa14d93e..769cfd4251 100644 --- a/revision.c +++ b/revision.c @@ -268,7 +268,7 @@ static int tree_difference = REV_TREE_SAME; static void file_add_remove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, - const char *fullpath) + const char *fullpath, unsigned dirty_submodule) { int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD; @@ -281,7 +281,8 @@ static void file_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, - const char *fullpath) + const char *fullpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) { tree_difference = REV_TREE_DIFFERENT; DIFF_OPT_SET(options, HAS_CHANGES); diff --git a/tree-diff.c b/tree-diff.c index 7d745b4406..fe9f52c479 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -68,7 +68,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { newbase[baselen + pathlen1] = 0; opt->change(opt, mode1, mode2, - sha1, sha2, newbase); + sha1, sha2, newbase, 0, 0); newbase[baselen + pathlen1] = '/'; } retval = diff_tree_sha1(sha1, sha2, newbase, opt); @@ -77,7 +77,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const } fullname = malloc_fullname(base, baselen, path1, pathlen1); - opt->change(opt, mode1, mode2, sha1, sha2, fullname); + opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0); free(fullname); return 0; } @@ -241,7 +241,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { newbase[baselen + pathlen] = 0; - opt->add_remove(opt, *prefix, mode, sha1, newbase); + opt->add_remove(opt, *prefix, mode, sha1, newbase, 0); newbase[baselen + pathlen] = '/'; } @@ -252,7 +252,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree free(newbase); } else { char *fullname = malloc_fullname(base, baselen, path, pathlen); - opt->add_remove(opt, prefix[0], mode, sha1, fullname); + opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0); free(fullname); } } -- cgit 1.2.3-korg From 125fd98434ce773de45c4a40927c222ec5c43ae1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 24 Jan 2010 00:10:20 -0800 Subject: Make ce_uptodate() trustworthy again The rule has always been that a cache entry that is ce_uptodate(ce) means that we already have checked the work tree entity and we know there is no change in the work tree compared to the index, and nobody should have to double check. Note that false ce_uptodate(ce) does not mean it is known to be dirty---it only means we don't know if it is clean. There are a few codepaths (refresh-index and preload-index are among them) that mark a cache entry as up-to-date based solely on the return value from ie_match_stat(); this function uses lstat() to see if the work tree entity has been touched, and for a submodule entry, if its HEAD points at the same commit as the commit recorded in the index of the superproject (a submodule that is not even cloned is considered clean). A submodule is no longer considered unmodified merely because its HEAD matches the index of the superproject these days, in order to prevent people from forgetting to commit in the submodule and updating the superproject index with the new submodule commit, before commiting the state in the superproject. However, the patch to do so didn't update the codepath that marks cache entries up-to-date based on the updated definition and instead worked it around by saying "we don't trust the return value of ce_uptodate() for submodules." This makes ce_uptodate() trustworthy again by not marking submodule entries up-to-date. The next step _could_ be to introduce a few "in-core" flag bits to cache_entry structure to record "this entry is _known_ to be dirty", call is_submodule_modified() from ie_match_stat(), and use these new bits to avoid running this rather expensive check more than once, but that can be a separate patch. Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- preload-index.c | 2 ++ read-cache.c | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 23e180eed1..c6c425e624 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -161,7 +161,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if ((ce_uptodate(ce) && !S_ISGITLINK(ce->ce_mode)) || ce_skip_worktree(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) continue; /* If CE_VALID is set, don't look at workdir for file removal */ diff --git a/preload-index.c b/preload-index.c index 92899333c2..e3d0bda31a 100644 --- a/preload-index.c +++ b/preload-index.c @@ -47,6 +47,8 @@ static void *preload_thread(void *_data) if (ce_stage(ce)) continue; + if (S_ISGITLINK(ce->ce_mode)) + continue; if (ce_uptodate(ce)) continue; if (!ce_path_match(ce, p->pathspec)) diff --git a/read-cache.c b/read-cache.c index 79938bf09a..309b77a6c9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -612,7 +612,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) { /* Nothing changed, really */ free(ce); - ce_mark_uptodate(alias); + if (!S_ISGITLINK(alias->ce_mode)) + ce_mark_uptodate(alias); alias->ce_flags |= CE_ADDED; return 0; } @@ -1050,7 +1051,8 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, * because CE_UPTODATE flag is in-core only; * we are not going to write this change out. */ - ce_mark_uptodate(ce); + if (!S_ISGITLINK(ce->ce_mode)) + ce_mark_uptodate(ce); return ce; } } -- cgit 1.2.3-korg From 4d34477f4c5dbebc55aa1362fd705440590a85f1 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Sat, 23 Jan 2010 17:37:26 +0100 Subject: git diff: Don't test submodule dirtiness with --ignore-submodules The diff family suppresses the output of submodule changes when requested but checks them nonetheless. But since recently submodules get examined for their dirtiness, which is rather expensive. There is no need to do that when the --ignore-submodules option is used, as the gathered information is never used anyway. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index c6c425e624..899034d354 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -179,6 +179,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } changed = ce_match_stat(ce, &st, ce_option); if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) && is_submodule_modified(ce->name)) { changed = 1; @@ -220,7 +221,7 @@ static int get_stat_data(struct cache_entry *ce, const unsigned char **sha1p, unsigned int *modep, int cached, int match_missing, - unsigned *dirty_submodule, int output_format) + unsigned *dirty_submodule, struct diff_options *diffopt) { const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; @@ -241,7 +242,8 @@ static int get_stat_data(struct cache_entry *ce, } changed = ce_match_stat(ce, &st, 0); if (S_ISGITLINK(ce->ce_mode) - && (!changed || (output_format & DIFF_FORMAT_PATCH)) + && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)) && is_submodule_modified(ce->name)) { changed = 1; *dirty_submodule = 1; @@ -270,7 +272,7 @@ static void show_new_file(struct rev_info *revs, * the working copy. */ if (get_stat_data(new, &sha1, &mode, cached, match_missing, - &dirty_submodule, revs->diffopt.output_format) < 0) + &dirty_submodule, &revs->diffopt) < 0) return; diff_index_show_file(revs, "+", new, sha1, mode, dirty_submodule); @@ -287,7 +289,7 @@ static int show_modified(struct rev_info *revs, unsigned dirty_submodule = 0; if (get_stat_data(new, &sha1, &mode, cached, match_missing, - &dirty_submodule, revs->diffopt.output_format) < 0) { + &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, old->sha1, old->ce_mode, 0); -- cgit 1.2.3-korg From c7e1a73641e24340bf93f6f1792220fa9154cda3 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Thu, 4 Mar 2010 22:20:33 +0100 Subject: git diff --submodule: Show detailed dirty status of submodules When encountering a dirty submodule while doing "git diff --submodule" print an extra line for new untracked content and another for modified but already tracked content. And if the HEAD of the submodule is equal to the ref diffed against in the superproject, drop the output which would just show the same SHA1s and no commit message headlines. To achieve that, the dirty_submodule bitfield is expanded to two bits. The output of "git status" inside the submodule is parsed to set the according bits. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 16 ++++++++-------- diffcore.h | 4 +++- submodule.c | 40 +++++++++++++++++++++++++++++++++++----- submodule.h | 2 +- t/t4041-diff-submodule.sh | 17 +++++++++++------ 5 files changed, 58 insertions(+), 21 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index d7e13cb177..15ca7cdac2 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -180,10 +180,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option) changed = ce_match_stat(ce, &st, ce_option); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) - && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - && is_submodule_modified(ce->name)) { - changed = 1; - dirty_submodule = 1; + && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))) { + dirty_submodule = is_submodule_modified(ce->name); + if (dirty_submodule) + changed = 1; } if (!changed) { ce_mark_uptodate(ce); @@ -243,10 +243,10 @@ static int get_stat_data(struct cache_entry *ce, changed = ce_match_stat(ce, &st, 0); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)) - && is_submodule_modified(ce->name)) { - changed = 1; - *dirty_submodule = 1; + && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))) { + *dirty_submodule = is_submodule_modified(ce->name); + if (*dirty_submodule) + changed = 1; } if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); diff --git a/diffcore.h b/diffcore.h index 66687c3fe5..fcd00bf27a 100644 --- a/diffcore.h +++ b/diffcore.h @@ -42,7 +42,9 @@ struct diff_filespec { #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0) unsigned should_free : 1; /* data should be free()'ed */ unsigned should_munmap : 1; /* data should be munmap()'ed */ - unsigned dirty_submodule : 1; /* For submodules: its work tree is dirty */ + unsigned dirty_submodule : 2; /* For submodules: its work tree is dirty */ +#define DIRTY_SUBMODULE_UNTRACKED 1 +#define DIRTY_SUBMODULE_MODIFIED 2 struct userdiff_driver *driver; /* data should be considered "binary"; -1 means "don't know yet" */ diff --git a/submodule.c b/submodule.c index 5d286e409e..714ca976b8 100644 --- a/submodule.c +++ b/submodule.c @@ -5,6 +5,7 @@ #include "commit.h" #include "revision.h" #include "run-command.h" +#include "diffcore.h" static int add_submodule_odb(const char *path) { @@ -85,13 +86,21 @@ void show_submodule_summary(FILE *f, const char *path, message = "(revision walker failed)"; } + if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + fprintf(f, "Submodule %s contains untracked content\n", path); + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + fprintf(f, "Submodule %s contains modified content\n", path); + + if (!hashcmp(one, two)) { + strbuf_release(&sb); + return; + } + strbuf_addf(&sb, "Submodule %s %s..", path, find_unique_abbrev(one, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); - if (dirty_submodule) - strbuf_add(&sb, "-dirty", 6); if (message) strbuf_addf(&sb, " %s\n", message); else @@ -121,9 +130,10 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_release(&sb); } -int is_submodule_modified(const char *path) +unsigned is_submodule_modified(const char *path) { - int len, i; + int i; + ssize_t len; struct child_process cp; const char *argv[] = { "status", @@ -132,6 +142,8 @@ int is_submodule_modified(const char *path) }; const char *env[LOCAL_REPO_ENV_SIZE + 3]; struct strbuf buf = STRBUF_INIT; + unsigned dirty_submodule = 0; + const char *line, *next_line; for (i = 0; i < LOCAL_REPO_ENV_SIZE; i++) env[i] = local_repo_env[i]; @@ -161,6 +173,24 @@ int is_submodule_modified(const char *path) die("Could not run git status --porcelain"); len = strbuf_read(&buf, cp.out, 1024); + line = buf.buf; + while (len > 2) { + if ((line[0] == '?') && (line[1] == '?')) { + dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED; + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + break; + } else { + dirty_submodule |= DIRTY_SUBMODULE_MODIFIED; + if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + break; + } + next_line = strchr(line, '\n'); + if (!next_line) + break; + next_line++; + len -= (next_line - line); + line = next_line; + } close(cp.out); if (finish_command(&cp)) @@ -169,5 +199,5 @@ int is_submodule_modified(const char *path) for (i = LOCAL_REPO_ENV_SIZE; env[i]; i++) free((char *)env[i]); strbuf_release(&buf); - return len != 0; + return dirty_submodule; } diff --git a/submodule.h b/submodule.h index 233696555e..267881cbe4 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,6 @@ void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset); -int is_submodule_modified(const char *path); +unsigned is_submodule_modified(const char *path); #endif diff --git a/t/t4041-diff-submodule.sh b/t/t4041-diff-submodule.sh index 464305405a..11b19972ca 100755 --- a/t/t4041-diff-submodule.sh +++ b/t/t4041-diff-submodule.sh @@ -201,7 +201,7 @@ test_expect_success 'submodule contains untracked content' " echo new > sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains untracked content EOF " @@ -209,7 +209,8 @@ test_expect_success 'submodule contains untracked and modifed content' " echo new > sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains untracked content +Submodule sm1 contains modified content EOF " @@ -217,7 +218,7 @@ test_expect_success 'submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head6-dirty: +Submodule sm1 contains modified content EOF " @@ -235,7 +236,8 @@ test_expect_success 'modified submodule contains untracked content' " echo new > sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains untracked content +Submodule sm1 $head6..$head8: > change EOF " @@ -244,7 +246,9 @@ test_expect_success 'modified submodule contains untracked and modifed content' echo modification >> sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains untracked content +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: > change EOF " @@ -253,7 +257,8 @@ test_expect_success 'modified submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && diff actual - <<-EOF -Submodule sm1 $head6..$head8-dirty: +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: > change EOF " -- cgit 1.2.3-korg From 9297f77e6d350f33de961e149dc33c77e7392db4 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Mon, 8 Mar 2010 13:53:19 +0100 Subject: git status: Show detailed dirty status of submodules in long format Since 1.7.0 there are three reasons a submodule is considered modified against the work tree: It contains new commits, modified content or untracked content. Lets show all reasons in the long format of git status, so the user can better asses the nature of the modification. This change does not affect the short and porcelain formats. Two new members are added to "struct wt_status_change_data" to store the information gathered by run_diff_files(). wt-status.c uses the new flag DIFF_OPT_DIRTY_SUBMODULES to tell diff-lib.c it wants to get detailed dirty information about submodules. A hint line for submodules is printed in the dirty header when dirty submodules are present. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 6 ++++-- diff.h | 1 + t/t7506-status-submodule.sh | 6 +++--- wt-status.c | 43 ++++++++++++++++++++++++++++++++++++------- wt-status.h | 2 ++ 5 files changed, 46 insertions(+), 12 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 15ca7cdac2..1ab286a3ce 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -180,7 +180,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) changed = ce_match_stat(ce, &st, ce_option); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) - && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))) { + && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH) + || DIFF_OPT_TST(&revs->diffopt, DIRTY_SUBMODULES))) { dirty_submodule = is_submodule_modified(ce->name); if (dirty_submodule) changed = 1; @@ -243,7 +244,8 @@ static int get_stat_data(struct cache_entry *ce, changed = ce_match_stat(ce, &st, 0); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))) { + && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH) + || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { *dirty_submodule = is_submodule_modified(ce->name); if (*dirty_submodule) changed = 1; diff --git a/diff.h b/diff.h index 2ef3341fb0..95ed7f8ed7 100644 --- a/diff.h +++ b/diff.h @@ -69,6 +69,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) #define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) #define DIFF_OPT_SUBMODULE_LOG (1 << 23) +#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 253c334319..1c8d32a99e 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -34,7 +34,7 @@ test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub" output + grep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub" output + grep "modified: sub (new commits, modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -64,7 +64,7 @@ test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub" output + grep "modified: sub (new commits, untracked content)" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' diff --git a/wt-status.c b/wt-status.c index 5807fc3211..e0e915e46a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -78,7 +78,8 @@ static void wt_status_print_cached_header(struct wt_status *s) } static void wt_status_print_dirty_header(struct wt_status *s, - int has_deleted) + int has_deleted, + int has_dirty_submodules) { const char *c = color(WT_STATUS_HEADER, s); @@ -90,6 +91,8 @@ static void wt_status_print_dirty_header(struct wt_status *s, else color_fprintf_ln(s->fp, c, "# (use \"git add/rm ...\" to update what will be committed)"); color_fprintf_ln(s->fp, c, "# (use \"git checkout -- ...\" to discard changes in working directory)"); + if (has_dirty_submodules) + color_fprintf_ln(s->fp, c, "# (commit or discard the untracked or modified content in submodules)"); color_fprintf_ln(s->fp, c, "#"); } @@ -144,6 +147,7 @@ static void wt_status_print_change_data(struct wt_status *s, char *two_name; const char *one, *two; struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT; + struct strbuf extra = STRBUF_INIT; one_name = two_name = it->string; switch (change_type) { @@ -153,6 +157,17 @@ static void wt_status_print_change_data(struct wt_status *s, one_name = d->head_path; break; case WT_STATUS_CHANGED: + if (d->new_submodule_commits || d->dirty_submodule) { + strbuf_addstr(&extra, " ("); + if (d->new_submodule_commits) + strbuf_addf(&extra, "new commits, "); + if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) + strbuf_addf(&extra, "modified content, "); + if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + strbuf_addf(&extra, "untracked content, "); + strbuf_setlen(&extra, extra.len - 2); + strbuf_addch(&extra, ')'); + } status = d->worktree_status; break; } @@ -189,6 +204,10 @@ static void wt_status_print_change_data(struct wt_status *s, default: die("bug: unhandled diff status %c", status); } + if (extra.len) { + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf); + strbuf_release(&extra); + } fprintf(s->fp, "\n"); strbuf_release(&onebuf); strbuf_release(&twobuf); @@ -218,6 +237,9 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, } if (!d->worktree_status) d->worktree_status = p->status; + d->dirty_submodule = p->two->dirty_submodule; + if (S_ISGITLINK(p->two->mode)) + d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1); } } @@ -281,6 +303,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; rev.prune_data = s->pathspec; @@ -418,33 +441,39 @@ static void wt_status_print_updated(struct wt_status *s) * 0 : no change * 1 : some change but no delete */ -static int wt_status_check_worktree_changes(struct wt_status *s) +static int wt_status_check_worktree_changes(struct wt_status *s, + int *dirty_submodules) { int i; int changes = 0; + *dirty_submodules = 0; + for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; d = s->change.items[i].util; if (!d->worktree_status || d->worktree_status == DIFF_STATUS_UNMERGED) continue; - changes = 1; + if (!changes) + changes = 1; + if (d->dirty_submodule) + *dirty_submodules = 1; if (d->worktree_status == DIFF_STATUS_DELETED) - return -1; + changes = -1; } return changes; } static void wt_status_print_changed(struct wt_status *s) { - int i; - int worktree_changes = wt_status_check_worktree_changes(s); + int i, dirty_submodules; + int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules); if (!worktree_changes) return; - wt_status_print_dirty_header(s, worktree_changes < 0); + wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules); for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; diff --git a/wt-status.h b/wt-status.h index c60f40a34a..91206739f3 100644 --- a/wt-status.h +++ b/wt-status.h @@ -25,6 +25,8 @@ struct wt_status_change_data { int index_status; int stagemask; char *head_path; + unsigned dirty_submodule : 2; + unsigned new_submodule_commits : 1; }; struct wt_status { -- cgit 1.2.3-korg From 32962c9bd5149005b163dff230670872eb99286a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 8 Mar 2010 22:58:09 -0800 Subject: revision: introduce setup_revision_opt So far the last parameter to setup_revisions() was to specify the default ref when the command line did not give any (typically "HEAD"). This changes it to take a pointer to a structure so that we can add other information without touching too many codepaths in later patches. There is no functionality change. Signed-off-by: Junio C Hamano --- builtin-log.c | 30 ++++++++++++++++++++++-------- diff-lib.c | 5 ++++- revision.c | 4 ++-- revision.h | 6 +++++- wt-status.c | 15 +++++++++++---- 5 files changed, 44 insertions(+), 16 deletions(-) (limited to 'diff-lib.c') diff --git a/builtin-log.c b/builtin-log.c index 8d16832f7e..dfbf6f22d0 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -32,7 +32,7 @@ static const char * const builtin_log_usage = " or: git show [options] ..."; static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev) + struct rev_info *rev, struct setup_revision_opt *opt) { int i; int decoration_style = 0; @@ -56,7 +56,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, */ if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_log_usage); - argc = setup_revisions(argc, argv, rev, "HEAD"); + argc = setup_revisions(argc, argv, rev, opt); if (!rev->show_notes_given && !rev->pretty_given) rev->show_notes = 1; @@ -262,6 +262,7 @@ static int git_log_config(const char *var, const char *value, void *cb) int cmd_whatchanged(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -271,7 +272,9 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; return cmd_log_walk(&rev); @@ -328,6 +331,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; struct object_array_entry *objects; + struct setup_revision_opt opt; int i, count, ret = 0; git_config(git_log_config, NULL); @@ -342,7 +346,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; rev.ignore_merges = 0; rev.no_walk = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); count = rev.pending.nr; objects = rev.pending.objects; @@ -405,6 +411,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) int cmd_log_reflog(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -415,7 +422,9 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) init_reflog_walk(&rev.reflog_info); rev.abbrev_commit = 1; rev.verbose_header = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); /* * This means that we override whatever commit format the user gave @@ -438,6 +447,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct setup_revision_opt opt; git_config(git_log_config, NULL); @@ -446,7 +456,9 @@ int cmd_log(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); rev.always_show_header = 1; - cmd_log_init(argc, argv, prefix, &rev); + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -887,6 +899,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct commit *commit; struct commit **list = NULL; struct rev_info rev; + struct setup_revision_opt s_r_opt; int nr = 0, total, i; int use_stdout = 0; int start_number = -1; @@ -964,8 +977,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.combine_merges = 0; rev.ignore_merges = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - rev.subject_prefix = fmt_patch_subject_prefix; + memset(&s_r_opt, 0, sizeof(s_r_opt)); + s_r_opt.def = "HEAD"; if (default_attach) { rev.mime_boundary = default_attach; @@ -1037,7 +1051,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (keep_subject && subject_prefix) die ("--subject-prefix and -k are mutually exclusive."); - argc = setup_revisions(argc, argv, &rev, "HEAD"); + argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) die ("unrecognized argument: %s", argv[1]); diff --git a/diff-lib.c b/diff-lib.c index d7e13cb177..64976f921d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -510,9 +510,12 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) int index_differs_from(const char *def, int diff_flags) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, def); + memset(&opt, 0, sizeof(opt)); + opt.def = def; + setup_revisions(0, NULL, &rev, &opt); DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; diff --git a/revision.c b/revision.c index 3ba6d991f6..25c1bbb9a6 100644 --- a/revision.c +++ b/revision.c @@ -1328,7 +1328,7 @@ static void append_prune_data(const char ***prune_data, const char **av) * Returns the number of arguments left that weren't recognized * (which are also moved to the head of the argument list) */ -int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) +int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt) { int i, flags, left, seen_dashdash, read_from_stdin; const char **prune_data = NULL; @@ -1462,7 +1462,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->prune_data = get_pathspec(revs->prefix, prune_data); if (revs->def == NULL) - revs->def = def; + revs->def = opt ? opt->def : NULL; if (revs->show_merge) prepare_show_merge(revs); if (revs->def && !revs->pending.nr) { diff --git a/revision.h b/revision.h index a14deefc25..fcc0de8eea 100644 --- a/revision.h +++ b/revision.h @@ -137,8 +137,12 @@ struct rev_info { typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *); extern volatile show_early_output_fn_t show_early_output; +struct setup_revision_opt { + const char *def; +}; + extern void init_revisions(struct rev_info *revs, const char *prefix); -extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *); extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]); diff --git a/wt-status.c b/wt-status.c index 5807fc3211..dcaec7f09e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -290,10 +290,13 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) static void wt_status_collect_changes_index(struct wt_status *s) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, - s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); + memset(&opt, 0, sizeof(opt)); + opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; + setup_revisions(0, NULL, &rev, &opt); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_updated_cb; rev.diffopt.format_callback_data = s; @@ -512,11 +515,15 @@ static void wt_status_print_untracked(struct wt_status *s) static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; + struct setup_revision_opt opt; init_revisions(&rev, NULL); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); - setup_revisions(0, NULL, &rev, - s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); + + memset(&opt, 0, sizeof(opt)); + opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; + setup_revisions(0, NULL, &rev, &opt); + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.diffopt.detect_rename = 1; rev.diffopt.file = s->fp; -- cgit 1.2.3-korg From ae6d5c1b6f78ef48f606e5a267915fa31b37a679 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Thu, 11 Mar 2010 22:50:25 +0100 Subject: Refactor dirty submodule detection in diff-lib.c Moving duplicated code into the new function match_stat_with_submodule(). Replacing the implicit activation of detailed checks for the dirtiness of submodules when DIFF_FORMAT_PATCH was selected with explicitly setting the recently added DIFF_OPT_DIRTY_SUBMODULES option in diff_setup_done(). Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 45 +++++++++++++++++++++++++++------------------ diff.c | 6 ++++++ 2 files changed, 33 insertions(+), 18 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 1ab286a3ce..ea9cf561cd 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -55,6 +55,29 @@ static int check_removed(const struct cache_entry *ce, struct stat *st) return 0; } +/* + * Has a file changed or has a submodule new commits or a dirty work tree? + * + * Return 1 when changes are detected, 0 otherwise. If the DIRTY_SUBMODULES + * option is set, the caller does not only want to know if a submodule is + * modified at all but wants to know all the conditions that are met (new + * commits, untracked content and/or modified content). + */ +static int match_stat_with_submodule(struct diff_options *diffopt, + struct cache_entry *ce, struct stat *st, + unsigned ce_option, unsigned *dirty_submodule) +{ + int changed = ce_match_stat(ce, st, ce_option); + if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { + *dirty_submodule = is_submodule_modified(ce->name); + if (*dirty_submodule) + changed = 1; + } + return changed; +} + int run_diff_files(struct rev_info *revs, unsigned int option) { int entries, i; @@ -177,15 +200,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) ce->sha1, ce->name, 0); continue; } - changed = ce_match_stat(ce, &st, ce_option); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) - && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH) - || DIFF_OPT_TST(&revs->diffopt, DIRTY_SUBMODULES))) { - dirty_submodule = is_submodule_modified(ce->name); - if (dirty_submodule) - changed = 1; - } + changed = match_stat_with_submodule(&revs->diffopt, ce, &st, + ce_option, &dirty_submodule); if (!changed) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) @@ -241,15 +257,8 @@ static int get_stat_data(struct cache_entry *ce, } return -1; } - changed = ce_match_stat(ce, &st, 0); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH) - || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { - *dirty_submodule = is_submodule_modified(ce->name); - if (*dirty_submodule) - changed = 1; - } + changed = match_stat_with_submodule(diffopt, ce, &st, + 0, dirty_submodule); if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; diff --git a/diff.c b/diff.c index 381cc8d4fd..240401b73d 100644 --- a/diff.c +++ b/diff.c @@ -2628,6 +2628,12 @@ int diff_setup_done(struct diff_options *options) */ if (options->pickaxe) DIFF_OPT_SET(options, RECURSIVE); + /* + * When patches are generated, submodules diffed against the work tree + * must be checked for dirtiness too so it can be shown in the output + */ + if (options->output_format & DIFF_FORMAT_PATCH) + DIFF_OPT_SET(options, DIRTY_SUBMODULES); if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; -- cgit 1.2.3-korg From 85adbf2f751a91429de6b431c45737ba9d7e9e00 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 12 Mar 2010 22:23:52 +0100 Subject: git status: Fix false positive "new commits" output for dirty submodules Testing if the output "new commits" should appear in the long format of "git status" is done by comparing the hashes of the diffpair. This always resulted in printing "new commits" for submodules that contained untracked or modified content, even if they did not contain new commits. The reason was that match_stat_with_submodule() did set the "changed" flag for dirty submodules, resulting in two->sha1 being set to the null_sha1 at the call sites, which indicates that new commits are present. This is changed so that when no new commits are present, the same object names are in the sha1 field for both sides of the filepair, and the working tree side will have the "dirty_submodule" flag set when appropriate. For a submodule to be seen as modified even when it just has a dirty work tree, some conditions had to be extended to also check for the "dirty_submodule" flag. Unfortunately the test case that should have found this bug had been changed incorrectly too. It is fixed and extended to test for other combinations too. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 6 ++-- diff.c | 7 ++-- t/t7506-status-submodule.sh | 84 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 9 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index ea9cf561cd..87a259111a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -72,8 +72,6 @@ static int match_stat_with_submodule(struct diff_options *diffopt, && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { *dirty_submodule = is_submodule_modified(ce->name); - if (*dirty_submodule) - changed = 1; } return changed; } @@ -202,7 +200,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } changed = match_stat_with_submodule(&revs->diffopt, ce, &st, ce_option, &dirty_submodule); - if (!changed) { + if (!changed && !dirty_submodule) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) continue; @@ -333,7 +331,7 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !hashcmp(sha1, old->sha1) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !dirty_submodule && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; diff --git a/diff.c b/diff.c index 240401b73d..8fd020fa47 100644 --- a/diff.c +++ b/diff.c @@ -2032,7 +2032,7 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) char *data = xmalloc(100), *dirty = ""; /* Are we looking at the work tree? */ - if (!s->sha1_valid && s->dirty_submodule) + if (s->dirty_submodule) dirty = "-dirty"; len = snprintf(data, 100, @@ -3081,7 +3081,8 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1)) + !hashcmp(one->sha1, two->sha1) && + !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -3216,6 +3217,8 @@ static void diff_resolve_rename_copy(void) } else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode || + p->one->dirty_submodule || + p->two->dirty_submodule || is_null_sha1(p->one->sha1)) p->status = DIFF_STATUS_MODIFIED; else { diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 1c8d32a99e..dc9150a71f 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -34,7 +34,7 @@ test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub (new commits, modified content)" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub (new commits, modified content)" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -64,7 +64,7 @@ test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (new commits, untracked content)" output + grep "modified: sub (untracked content)" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' @@ -74,6 +74,84 @@ test_expect_success 'status with untracked file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with added and untracked file in submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with modified file in modified submodule' ' + (cd sub && git reset --hard) && + rm sub/new-file && + (cd sub && echo "next change" >foo && git commit -m "next change" foo) && + echo "changed" >sub/foo && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with modified file in modified submodule (porcelain)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with added file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with untracked file in modified submodule' ' + (cd sub && git reset --hard) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, untracked content)" output +' + +test_expect_success 'status with untracked file in modified submodule (porcelain)' ' + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added and untracked file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' -- cgit 1.2.3-korg From 3bfc45047654c7dd38b32033321228e97fc8f60e Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Sat, 13 Mar 2010 23:00:27 +0100 Subject: git status: ignoring untracked files must apply to submodules too Since 1.7.0 submodules are considered dirty when they contain untracked files. But when git status is called with the "-uno" option, the user asked to ignore untracked files, so they must be ignored in submodules too. To achieve this, the new flag DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES is introduced. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- diff.h | 1 + submodule.c | 9 +++++++-- submodule.h | 2 +- t/t7506-status-submodule.sh | 5 +++++ wt-status.c | 2 ++ 6 files changed, 17 insertions(+), 4 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 87a259111a..c50f7e3984 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -71,7 +71,7 @@ static int match_stat_with_submodule(struct diff_options *diffopt, if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { - *dirty_submodule = is_submodule_modified(ce->name); + *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); } return changed; } diff --git a/diff.h b/diff.h index 95ed7f8ed7..6a71013dc6 100644 --- a/diff.h +++ b/diff.h @@ -70,6 +70,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) #define DIFF_OPT_SUBMODULE_LOG (1 << 23) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) +#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/submodule.c b/submodule.c index 714ca976b8..b3b8bc1479 100644 --- a/submodule.c +++ b/submodule.c @@ -130,7 +130,7 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_release(&sb); } -unsigned is_submodule_modified(const char *path) +unsigned is_submodule_modified(const char *path, int ignore_untracked) { int i; ssize_t len; @@ -139,6 +139,7 @@ unsigned is_submodule_modified(const char *path) "status", "--porcelain", NULL, + NULL, }; const char *env[LOCAL_REPO_ENV_SIZE + 3]; struct strbuf buf = STRBUF_INIT; @@ -163,6 +164,9 @@ unsigned is_submodule_modified(const char *path) env[i++] = strbuf_detach(&buf, NULL); env[i] = NULL; + if (ignore_untracked) + argv[2] = "-uno"; + memset(&cp, 0, sizeof(cp)); cp.argv = argv; cp.env = env; @@ -181,7 +185,8 @@ unsigned is_submodule_modified(const char *path) break; } else { dirty_submodule |= DIRTY_SUBMODULE_MODIFIED; - if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) + if (ignore_untracked || + (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)) break; } next_line = strchr(line, '\n'); diff --git a/submodule.h b/submodule.h index 267881cbe4..dbda270873 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,6 @@ void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset); -unsigned is_submodule_modified(const char *path); +unsigned is_submodule_modified(const char *path, int ignore_untracked); #endif diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index dc9150a71f..aeec1f6142 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -67,6 +67,11 @@ test_expect_success 'status with untracked file in submodule' ' grep "modified: sub (untracked content)" output ' +test_expect_success 'status -uno with untracked file in submodule' ' + git status -uno >output && + grep "^nothing to commit" output +' + test_expect_success 'status with untracked file in submodule (porcelain)' ' git status --porcelain >output && diff output - <<-\EOF diff --git a/wt-status.c b/wt-status.c index e0e915e46a..5848f1c908 100644 --- a/wt-status.c +++ b/wt-status.c @@ -304,6 +304,8 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); + if (!s->show_untracked_files) + DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; rev.prune_data = s->pathspec; -- cgit 1.2.3-korg From dd44d419d30afa52b863efa07aeec738c4531ea9 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 8 Jun 2010 18:31:51 +0200 Subject: Add optional parameters to the diff option "--ignore-submodules" In some use cases it is not desirable that the diff family considers submodules that only contain untracked content as dirty. This may happen e.g. when the submodule is not under the developers control and not all build generated files have been added to .gitignore by the upstream developers. Using the "untracked" parameter for the "--ignore-submodules" option disables checking for untracked content and lets git diff report them as changed only when they have new commits or modified content. Sometimes it is not wanted to have submodules show up as changed when they just contain changes to their work tree. An example for that are scripts which just want to check for submodule commits while ignoring any changes to the work tree. Also users having large submodules known not to change might want to use this option, as the - sometimes substantial - time it takes to scan the submodule work tree(s) is saved. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 10 ++++- diff-lib.c | 1 + diff.c | 11 +++++- diff.h | 1 + t/t4027-diff-submodule.sh | 20 ++++++++-- t/t4041-diff-submodule-option.sh | 81 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 6 deletions(-) (limited to 'diff-lib.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c9c6c2b1cb..2dd91e930b 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -288,8 +288,14 @@ endif::git-format-patch[] --no-ext-diff:: Disallow external diff drivers. ---ignore-submodules:: - Ignore changes to submodules in the diff generation. +--ignore-submodules[=]:: + Ignore changes to submodules in the diff generation. can be + either "untracked", "dirty" or "all", which is the default. When + "untracked" is used submodules are not considered dirty when they only + contain untracked content (but they are still scanned for modified + content). Using "dirty" ignores all changes to the work tree of submodules, + only changes to the commits stored in the superproject are shown (this was + the behavior until 1.7.0). Using "all" hides all changes to submodules. --src-prefix=:: Show the given source prefix instead of "a/". diff --git a/diff-lib.c b/diff-lib.c index c9f6e05bad..8b8978ae6d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -70,6 +70,7 @@ static int match_stat_with_submodule(struct diff_options *diffopt, int changed = ce_match_stat(ce, st, ce_option); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); } diff --git a/diff.c b/diff.c index e40c1271da..804acf0b21 100644 --- a/diff.c +++ b/diff.c @@ -2867,7 +2867,16 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); - else if (!strcmp(arg, "--submodule")) + else if (!prefixcmp(arg, "--ignore-submodules=")) { + if (!strcmp(arg + 20, "all")) + DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg + 20, "untracked")) + DIFF_OPT_SET(options, IGNORE_UNTRACKED_IN_SUBMODULES); + else if (!strcmp(arg + 20, "dirty")) + DIFF_OPT_SET(options, IGNORE_DIRTY_SUBMODULES); + else + die("bad --ignore-submodules argument: %s", arg + 20); + } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { if (!strcmp(arg + 12, "log")) diff --git a/diff.h b/diff.h index 6a71013dc6..53db218b30 100644 --- a/diff.h +++ b/diff.h @@ -71,6 +71,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_SUBMODULE_LOG (1 << 23) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) +#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 83c1914771..559b41eccd 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -103,9 +103,17 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules HEAD >actual2 && + echo -n "" | test_cmp - actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git diff --ignore-submodules=dirty HEAD >actual4 && + echo -n "" | test_cmp - actual4 ' - +test_done test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' ( cd sub && @@ -129,7 +137,13 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules=all HEAD >actual2 && + echo -n "" | test_cmp - actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + echo -n "" | test_cmp - actual3 && + git diff --ignore-submodules=dirty HEAD >actual4 && + echo -n "" | test_cmp - actual4 ' test_expect_success 'git diff (empty submodule dir)' ' diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 019acb926d..f44b9064ea 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -205,6 +205,21 @@ Submodule sm1 contains untracked content EOF " +test_expect_success 'submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'submodule contains untracked and modifed content' " echo new > sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && @@ -214,6 +229,26 @@ Submodule sm1 contains modified content EOF " +test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +EOF +" + +test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked and modifed content (all ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && @@ -242,6 +277,27 @@ Submodule sm1 $head6..$head8: EOF " +test_expect_success 'modified submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'modified submodule contains untracked and modifed content' " echo modification >> sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && @@ -253,6 +309,31 @@ Submodule sm1 $head6..$head8: EOF " +test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'modified submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && -- cgit 1.2.3-korg From aee9c7d6540fe6f66fbbe7be420caa26ae0fa2d9 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 6 Aug 2010 00:39:25 +0200 Subject: Submodules: Add the new "ignore" config option for diff and status The new "ignore" config option controls the default behavior for "git status" and the diff family. It specifies under what circumstances they consider submodules as modified and can be set separately for each submodule. The command line option "--ignore-submodules=" has been extended to accept the new parameter "none" for both status and diff. Users that chose submodules to get rid of long work tree scanning times might want to set the "dirty" option for those submodules. This brings back the pre 1.7.0 behavior, where submodule work trees were never scanned for modifications. By using "--ignore-submodules=none" on the command line the status and diff commands can be told to do a full scan. This option can be set to the following values (which have the same name and meaning as for the "--ignore-submodules" option of status and diff): "all": All changes to the submodule will be ignored. "dirty": Only differences of the commit recorded in the superproject and the submodules HEAD will be considered modifications, all changes to the work tree of the submodule will be ignored. When using this value, the submodule will not be scanned for work tree changes at all, leading to a performance benefit on large submodules. "untracked": Only untracked files in the submodules work tree are ignored, a changed HEAD and/or modified files in the submodule will mark it as modified. "none" (which is the default): Either untracked or modified files in a submodules work tree or a difference between the subdmodules HEAD and the commit recorded in the superproject will make it show up as changed. This value is added as a new parameter for the "--ignore-submodules" option of the diff family and "git status" so the user can override the settings in the configuration. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/config.txt | 10 +++++ Documentation/diff-options.txt | 6 ++- Documentation/git-status.txt | 6 ++- diff-lib.c | 15 ++++--- diff.c | 35 ++++++++++++--- diff.h | 1 + submodule.c | 57 ++++++++++++++++++++++- submodule.h | 3 ++ t/t4027-diff-submodule.sh | 76 +++++++++++++++++++++++++++++++ t/t7508-status.sh | 100 +++++++++++++++++++++++++++++++++++++++-- wt-status.c | 8 +++- 11 files changed, 297 insertions(+), 20 deletions(-) (limited to 'diff-lib.c') diff --git a/Documentation/config.txt b/Documentation/config.txt index e75434b3ef..5ca09aed90 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1743,6 +1743,16 @@ submodule..update:: URL and other values found in the `.gitmodules` file. See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. +submodule..ignore:: + Defines under what circumstances "git status" and the diff family show + a submodule as modified. When set to "all", it will never be considered + modified, "dirty" will ignore all changes to the submodules work tree and + takes only differences between the HEAD of the submodule and the commit + recorded in the superproject into account. "untracked" will additionally + let submodules with modified tracked files in their work tree show up. + Using "none" (the default when this option is not set) also shows + submodules that have untracked files in their work tree as changed. + tar.umask:: This variable can be used to restrict the permission bits of tar archive entries. The default is 0002, which turns off the diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 2371262b10..9cf7506626 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -330,7 +330,11 @@ endif::git-format-patch[] --ignore-submodules[=]:: Ignore changes to submodules in the diff generation. can be - either "untracked", "dirty" or "all", which is the default. When + either "none", "untracked", "dirty" or "all", which is the default + Using "none" will consider the submodule modified when it either contains + untracked or modified files or its HEAD differs from the commit recorded + in the superproject and can be used to override any settings of the + 'ignore' option in linkgit:git-config[1]. When "untracked" is used submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified content). Using "dirty" ignores all changes to the work tree of submodules, diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 2fd054c104..a7a5d79416 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -55,7 +55,11 @@ specified. --ignore-submodules[=]:: Ignore changes to submodules when looking for changes. can be - either "untracked", "dirty" or "all", which is the default. When + either "none", "untracked", "dirty" or "all", which is the default. + Using "none" will consider the submodule modified when it either contains + untracked or modified files or its HEAD differs from the commit recorded + in the superproject and can be used to override any settings of the + 'ignore' option in linkgit:git-config[1]. When "untracked" is used submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified content). Using "dirty" ignores all changes to the work tree of submodules, diff --git a/diff-lib.c b/diff-lib.c index 8b8978ae6d..392ce2bef0 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -68,11 +68,16 @@ static int match_stat_with_submodule(struct diff_options *diffopt, unsigned ce_option, unsigned *dirty_submodule) { int changed = ce_match_stat(ce, st, ce_option); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) - && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { - *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); + if (S_ISGITLINK(ce->ce_mode)) { + unsigned orig_flags = diffopt->flags; + if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG)) + set_diffopt_flags_from_submodule_config(diffopt, ce->name); + if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)) + changed = 0; + else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) + && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) + *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); + diffopt->flags = orig_flags; } return changed; } diff --git a/diff.c b/diff.c index 17873f3d9e..8206047314 100644 --- a/diff.c +++ b/diff.c @@ -141,6 +141,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } + if (!prefixcmp(var, "submodule.")) + return parse_submodule_config_option(var, value); + return git_color_default_config(var, value, cb); } @@ -3166,11 +3169,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--no-textconv")) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); - else if (!strcmp(arg, "--ignore-submodules")) + else if (!strcmp(arg, "--ignore-submodules")) { + DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, "all"); - else if (!prefixcmp(arg, "--ignore-submodules=")) + } else if (!prefixcmp(arg, "--ignore-submodules=")) { + DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, arg + 20); - else if (!strcmp(arg, "--submodule")) + } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { if (!strcmp(arg + 12, "log")) @@ -4103,6 +4108,24 @@ int diff_result_code(struct diff_options *opt, int status) return result; } +/* + * Shall changes to this submodule be ignored? + * + * Submodule changes can be configured to be ignored separately for each path, + * but that configuration can be overridden from the command line. + */ +static int is_submodule_ignored(const char *path, struct diff_options *options) +{ + int ignored = 0; + unsigned orig_flags = options->flags; + if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG)) + set_diffopt_flags_from_submodule_config(options, path); + if (DIFF_OPT_TST(options, IGNORE_SUBMODULES)) + ignored = 1; + options->flags = orig_flags; + return ignored; +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, @@ -4110,7 +4133,7 @@ void diff_addremove(struct diff_options *options, { struct diff_filespec *one, *two; - if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode)) + if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options)) return; /* This may look odd, but it is a preparation for @@ -4157,8 +4180,8 @@ void diff_change(struct diff_options *options, { struct diff_filespec *one, *two; - if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode) - && S_ISGITLINK(new_mode)) + if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) && + is_submodule_ignored(concatpath, options)) return; if (DIFF_OPT_TST(options, REVERSE_DIFF)) { diff --git a/diff.h b/diff.h index 063d10ac22..53ad345469 100644 --- a/diff.h +++ b/diff.h @@ -77,6 +77,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) +#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/submodule.c b/submodule.c index 61cb6e21dd..ff2863074b 100644 --- a/submodule.c +++ b/submodule.c @@ -6,6 +6,10 @@ #include "revision.h" #include "run-command.h" #include "diffcore.h" +#include "string-list.h" + +struct string_list config_name_for_path; +struct string_list config_ignore_for_name; static int add_submodule_odb(const char *path) { @@ -46,6 +50,57 @@ static int add_submodule_odb(const char *path) return ret; } +void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, + const char *path) +{ + struct string_list_item *path_option, *ignore_option; + path_option = unsorted_string_list_lookup(&config_name_for_path, path); + if (path_option) { + ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); + if (ignore_option) + handle_ignore_submodules_arg(diffopt, ignore_option->util); + } +} + +int parse_submodule_config_option(const char *var, const char *value) +{ + int len; + struct string_list_item *config; + struct strbuf submodname = STRBUF_INIT; + + var += 10; /* Skip "submodule." */ + + len = strlen(var); + if ((len > 5) && !strcmp(var + len - 5, ".path")) { + strbuf_add(&submodname, var, len - 5); + config = unsorted_string_list_lookup(&config_name_for_path, value); + if (config) + free(config->util); + else + config = string_list_append(&config_name_for_path, xstrdup(value)); + config->util = strbuf_detach(&submodname, NULL); + strbuf_release(&submodname); + } else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) { + if (strcmp(value, "untracked") && strcmp(value, "dirty") && + strcmp(value, "all") && strcmp(value, "none")) { + warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); + return 0; + } + + strbuf_add(&submodname, var, len - 7); + config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf); + if (config) + free(config->util); + else + config = string_list_append(&config_ignore_for_name, + strbuf_detach(&submodname, NULL)); + strbuf_release(&submodname); + config->util = xstrdup(value); + return 0; + } + return 0; +} + void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -55,7 +110,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); else if (!strcmp(arg, "dirty")) DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES); - else + else if (strcmp(arg, "none")) die("bad --ignore-submodules argument: %s", arg); } diff --git a/submodule.h b/submodule.h index 6fd3bb4070..185a5ce6c6 100644 --- a/submodule.h +++ b/submodule.h @@ -3,6 +3,9 @@ struct diff_options; +void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, + const char *path); +int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 1bd8e5ee3a..6836728517 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -114,6 +114,31 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' ! test -s actual4 ' +test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' ' + git config submodule.subname.ignore none && + git config submodule.subname.path sub && + git diff HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config submodule.subname.ignore all && + git diff HEAD >actual2 && + ! test -s actual2 && + git config submodule.subname.ignore untracked && + git diff HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git config submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git diff HEAD --ignore-submodules=none >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname +' + test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' ( cd sub && @@ -146,6 +171,57 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' ! test -s actual4 ' +test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' ' + git config submodule.subname.ignore all && + git config submodule.subname.path sub && + git diff HEAD >actual2 && + ! test -s actual2 && + git config submodule.subname.ignore untracked && + git diff HEAD >actual3 && + ! test -s actual3 && + git config submodule.subname.ignore dirty && + git diff HEAD >actual4 && + ! test -s actual4 && + git diff --ignore-submodules=none HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual.body && + git config --remove-section submodule.subname +' + +test_expect_success 'git diff between submodule commits' ' + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git diff --ignore-submodules=dirty HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git diff --ignore-submodules HEAD^..HEAD >actual && + ! test -s actual +' + +test_expect_success 'git diff between submodule commits [.git/config]' ' + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config submodule.subname.ignore dirty && + git config submodule.subname.path sub && + git diff HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + test_cmp expect.body actual.body && + git config submodule.subname.ignore all && + git diff HEAD^..HEAD >actual && + ! test -s actual && + git diff --ignore-submodules=dirty HEAD^..HEAD >actual && + sed -e "1,/^@@/d" actual >actual.body && + expect_from_to >expect.body $subtip $subprev && + git config --remove-section submodule.subname +' + test_expect_success 'git diff (empty submodule dir)' ' : >empty && rm -rf sub/* sub/.git && diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a72fe3ae64..57bf2ee272 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -808,24 +808,38 @@ test_expect_success POSIXPERM 'status succeeds in a read-only repository' ' (exit $status) ' +(cd sm && echo > bar && git add bar && git commit -q -m 'Add bar' && cd .. && git add sm) +new_head=$(cd sm && git rev-parse --short=7 --verify HEAD) +touch .gitmodules + cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: dir1/modified # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Untracked files: # (use "git add ..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' @@ -834,19 +848,48 @@ test_expect_success '--ignore-submodules=untracked suppresses submodules with un test_cmp expect output ' +test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' ' + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' ' + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' ' echo modified > sm/foo && git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success '.git/config ignore=dirty suppresses submodules with modified content' ' + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) @@ -855,16 +898,21 @@ cat > expect << EOF # modified: dir1/modified # modified: sm (modified content) # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Untracked files: # (use "git add ..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' @@ -872,10 +920,23 @@ test_expect_success "--ignore-submodules=untracked doesn't suppress submodules w test_cmp expect output ' +test_expect_success ".git/config ignore=untracked doesn't suppress submodules with modified content" ' + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD) cat > expect << EOF # On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# modified: sm +# # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) @@ -883,21 +944,26 @@ cat > expect << EOF # modified: dir1/modified # modified: sm (new commits) # +# Submodule changes to be committed: +# +# * sm $head...$new_head (1): +# > Add bar +# # Submodules changed but not updated: # -# * sm $head...$head2 (1): +# * sm $new_head...$head2 (1): # > 2nd commit # # Untracked files: # (use "git add ..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked # expect # output # untracked -no changes added to commit (use "git add" and/or "git commit -a") EOF test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' @@ -905,11 +971,28 @@ test_expect_success "--ignore-submodules=untracked doesn't suppress submodule su test_cmp expect output ' +test_expect_success ".git/config ignore=untracked doesn't suppress submodule summary" ' + git config --add submodule.subname.ignore untracked && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' git status --ignore-submodules=dirty > output && test_cmp expect output ' +test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary" ' + git config --add submodule.subname.ignore dirty && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + + cat > expect << EOF # On branch master # Changed but not updated: @@ -921,6 +1004,7 @@ cat > expect << EOF # Untracked files: # (use "git add ..." to include in what will be committed) # +# .gitmodules # dir1/untracked # dir2/modified # dir2/untracked @@ -935,4 +1019,12 @@ test_expect_success "--ignore-submodules=all suppresses submodule summary" ' test_cmp expect output ' +test_expect_failure '.git/config ignore=all suppresses submodule summary' ' + git config --add submodule.subname.ignore all && + git config --add submodule.subname.path sm && + git status > output && + test_cmp expect output && + git config --remove-section submodule.subname +' + test_done diff --git a/wt-status.c b/wt-status.c index 2f9e33c8fa..54b6b03b9c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -313,8 +313,10 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); if (!s->show_untracked_files) DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); - if (s->ignore_submodule_arg) + if (s->ignore_submodule_arg) { + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; rev.prune_data = s->pathspec; @@ -331,8 +333,10 @@ static void wt_status_collect_changes_index(struct wt_status *s) opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; setup_revisions(0, NULL, &rev, &opt); - if (s->ignore_submodule_arg) + if (s->ignore_submodule_arg) { + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + } rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_updated_cb; -- cgit 1.2.3-korg From 66f136252fe1998e2c1381c913795b5f56b6dc8c Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Wed, 15 Dec 2010 22:02:38 +0700 Subject: Convert struct diff_options to use struct pathspec 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 --- builtin/diff-files.c | 2 +- builtin/diff.c | 4 ++-- builtin/log.c | 2 +- diff-lib.c | 2 +- diff-no-index.c | 4 ++-- diff.h | 4 +--- revision.c | 6 +----- tree-diff.c | 48 +++++++++++++----------------------------------- 8 files changed, 22 insertions(+), 50 deletions(-) (limited to 'diff-lib.c') diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 951c7c8994..46085f862f 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) rev.combine_merges = rev.dense_combined_merges = 1; - if (read_cache_preload(rev.diffopt.paths) < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff.c b/builtin/diff.c index 945e7583a8..a08c3240d5 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -135,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } @@ -237,7 +237,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->combine_merges = revs->dense_combined_merges = 1; setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/log.c b/builtin/log.c index d8c6c28d2f..f5ed690c43 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -89,7 +89,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->always_show_header = 0; if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) + if (rev->diffopt.pathspec.nr != 1) usage("git logs can only follow renames on one pathname at a time"); } for (i = 1; i < argc; i++) { diff --git a/diff-lib.c b/diff-lib.c index 392ce2bef0..3b809f2e0d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) active_nr = dst - active_cache; init_revisions(&revs, NULL); - revs.prune_data = opt->paths; + revs.prune_data = opt->pathspec.raw; tree = parse_tree_indirect(tree_sha1); if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); diff --git a/diff-no-index.c b/diff-no-index.c index e48ab92ba1..3a36144687 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -260,8 +260,8 @@ void diff_no_index(struct rev_info *revs, if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], - revs->diffopt.paths[1])) + if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0], + revs->diffopt.pathspec.raw[1])) exit(1); diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); diff --git a/diff.h b/diff.h index 0083d92438..310bd6b283 100644 --- a/diff.h +++ b/diff.h @@ -133,9 +133,7 @@ struct diff_options { FILE *file; int close_file; - int nr_paths; - const char **paths; - int *pathlens; + struct pathspec pathspec; change_fn_t change; add_remove_fn_t add_remove; diff_format_fn_t format_callback; diff --git a/revision.c b/revision.c index 7b9eaefae4..0c511aa44b 100644 --- a/revision.c +++ b/revision.c @@ -553,11 +553,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) left_first = left_count < right_count; init_patch_ids(&ids); - if (revs->diffopt.nr_paths) { - ids.diffopts.nr_paths = revs->diffopt.nr_paths; - ids.diffopts.paths = revs->diffopt.paths; - ids.diffopts.pathlens = revs->diffopt.pathlens; - } + ids.diffopts.pathspec = revs->diffopt.pathspec; /* Compute patch-ids for one side */ for (p = list; p; p = p->next) { diff --git a/tree-diff.c b/tree-diff.c index 12c9a88884..f6b4aa4caa 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -102,16 +102,17 @@ static int tree_entry_interesting(struct tree_desc *desc, const char *base, int int pathlen; int never_interesting = -1; - if (!opt->nr_paths) + if (!opt->pathspec.nr) return 2; sha1 = tree_entry_extract(desc, &path, &mode); pathlen = tree_entry_len(path, sha1); - for (i = 0; i < opt->nr_paths; i++) { - const char *match = opt->paths[i]; - int matchlen = opt->pathlens[i]; + for (i = 0; i < opt->pathspec.nr; i++) { + const struct pathspec_item *item = opt->pathspec.items+i; + const char *match = item->match; + int matchlen = item->len; int m = -1; /* signals that we haven't called strncmp() */ if (baselen >= matchlen) { @@ -286,7 +287,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru if (DIFF_OPT_TST(opt, QUICK) && DIFF_OPT_TST(opt, HAS_CHANGES)) break; - if (opt->nr_paths) { + if (opt->pathspec.nr) { if (!all_t1_interesting) skip_uninteresting(t1, base, baselen, opt, &all_t1_interesting); @@ -349,7 +350,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co DIFF_OPT_SET(&diff_opts, RECURSIVE); DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = opt->paths[0]; + diff_opts.single_follow = opt->pathspec.raw[0]; diff_opts.break_opt = opt->break_opt; paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); @@ -369,15 +370,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co * diff_queued_diff, we will also use that as the path in * the future! */ - if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) { + if ((p->status == 'R' || p->status == 'C') && + !strcmp(p->two->path, opt->pathspec.raw[0])) { /* Switch the file-pairs around */ q->queue[i] = choice; choice = p; /* Update the path we use from now on.. */ diff_tree_release_paths(opt); - opt->paths[0] = xstrdup(p->one->path); - diff_tree_setup_paths(opt->paths, opt); + opt->pathspec.raw[0] = xstrdup(p->one->path); + diff_tree_setup_paths(opt->pathspec.raw, opt); /* * The caller expects us to return a set of vanilla @@ -452,36 +454,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_ return retval; } -static int count_paths(const char **paths) -{ - int i = 0; - while (*paths++) - i++; - return i; -} - void diff_tree_release_paths(struct diff_options *opt) { - free(opt->pathlens); + free_pathspec(&opt->pathspec); } void diff_tree_setup_paths(const char **p, struct diff_options *opt) { - opt->nr_paths = 0; - opt->pathlens = NULL; - opt->paths = NULL; - - if (p) { - int i; - - opt->paths = p; - opt->nr_paths = count_paths(p); - if (opt->nr_paths == 0) { - opt->pathlens = NULL; - return; - } - opt->pathlens = xmalloc(opt->nr_paths * sizeof(int)); - for (i=0; i < opt->nr_paths; i++) - opt->pathlens[i] = strlen(p[i]); - } + init_pathspec(&opt->pathspec, p); } -- cgit 1.2.3-korg From afe069d16618190a6f7e84ef8451970e274aedb4 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Fri, 17 Dec 2010 19:43:06 +0700 Subject: struct rev_info: convert prune_data to struct pathspec 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 --- builtin/add.c | 2 +- builtin/diff.c | 12 ++++-------- builtin/fast-export.c | 2 +- diff-lib.c | 6 +++--- revision.c | 15 ++++++++------- revision.h | 2 +- wt-status.c | 4 ++-- 7 files changed, 20 insertions(+), 23 deletions(-) (limited to 'diff-lib.c') diff --git a/builtin/add.c b/builtin/add.c index 12b964e642..5f817ad77f 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -86,7 +86,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; + init_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; data.flags = flags; diff --git a/builtin/diff.c b/builtin/diff.c index a08c3240d5..d12de8f392 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -371,14 +371,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } die("unhandled object '%s' given.", name); } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } + if (rev.prune_data.nr) { + if (!path) + path = rev.prune_data.items[0].match; + paths += rev.prune_data.nr; } /* diff --git a/builtin/fast-export.c b/builtin/fast-export.c index c8fd46b872..ba57457cc5 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -651,7 +651,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename) import_marks(import_filename); - if (import_filename && revs.prune_data) + if (import_filename && revs.prune_data.nr) full_tree = 1; get_tags_and_duplicates(&revs.pending, &extra_refs); diff --git a/diff-lib.c b/diff-lib.c index 3b809f2e0d..2251f3df68 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -106,7 +106,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; - if (!ce_path_match(ce, revs->prune_data)) + if (!ce_path_match(ce, revs->prune_data.raw)) continue; if (ce_stage(ce)) { @@ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) if (tree == o->df_conflict_entry) tree = NULL; - if (ce_path_match(idx ? idx : tree, revs->prune_data)) + if (ce_path_match(idx ? idx : tree, revs->prune_data.raw)) do_oneway_diff(o, idx, tree); return 0; @@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) active_nr = dst - active_cache; init_revisions(&revs, NULL); - revs.prune_data = opt->pathspec.raw; + init_pathspec(&revs.prune_data, opt->pathspec.raw); tree = parse_tree_indirect(tree_sha1); if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); diff --git a/revision.c b/revision.c index 0c511aa44b..bf87e01e45 100644 --- a/revision.c +++ b/revision.c @@ -323,7 +323,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct * tagged commit by specifying both --simplify-by-decoration * and pathspec. */ - if (!revs->prune_data) + if (!revs->prune_data.nr) return REV_TREE_SAME; } @@ -969,7 +969,7 @@ static void prepare_show_merge(struct rev_info *revs) struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; - if (ce_path_match(ce, revs->prune_data)) { + if (ce_path_match(ce, revs->prune_data.raw)) { prune_num++; prune = xrealloc(prune, sizeof(*prune) * prune_num); prune[prune_num-2] = ce->name; @@ -979,7 +979,8 @@ static void prepare_show_merge(struct rev_info *revs) ce_same_name(ce, active_cache[i+1])) i++; } - revs->prune_data = prune; + free_pathspec(&revs->prune_data); + init_pathspec(&revs->prune_data, prune); revs->limited = 1; } @@ -1616,7 +1617,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s } if (prune_data) - revs->prune_data = get_pathspec(revs->prefix, prune_data); + init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data)); if (revs->def == NULL) revs->def = opt ? opt->def : NULL; @@ -1647,13 +1648,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->topo_order) revs->limited = 1; - if (revs->prune_data) { - diff_tree_setup_paths(revs->prune_data, &revs->pruning); + if (revs->prune_data.nr) { + diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning); /* Can't prune commits with rename following: the paths change.. */ if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES)) revs->prune = 1; if (!revs->full_diff) - diff_tree_setup_paths(revs->prune_data, &revs->diffopt); + diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt); } if (revs->combine_merges) revs->ignore_merges = 0; diff --git a/revision.h b/revision.h index 05659c64ac..82509dd1d9 100644 --- a/revision.h +++ b/revision.h @@ -34,7 +34,7 @@ struct rev_info { /* Basic information */ const char *prefix; const char *def; - void *prune_data; + struct pathspec prune_data; unsigned int early_output; /* Traversal flags */ diff --git a/wt-status.c b/wt-status.c index 123582b6cb..1ea330ed17 100644 --- a/wt-status.c +++ b/wt-status.c @@ -323,7 +323,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) } rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; - rev.prune_data = s->pathspec; + init_pathspec(&rev.prune_data, s->pathspec); run_diff_files(&rev, 0); } @@ -348,7 +348,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; rev.diffopt.break_opt = 0; - rev.prune_data = s->pathspec; + init_pathspec(&rev.prune_data, s->pathspec); run_diff_index(&rev, 1); } -- cgit 1.2.3-korg From eb9cb55b944796374402ab4e2639300dc9b0b409 Mon Sep 17 00:00:00 2001 From: Nguyễn Thái Ngọc Duy Date: Fri, 17 Dec 2010 19:43:07 +0700 Subject: Convert ce_path_match() to use struct pathspec 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 --- builtin/update-index.c | 8 ++++++-- cache.h | 2 +- diff-lib.c | 4 ++-- preload-index.c | 5 ++++- read-cache.c | 7 ++++--- revision.c | 2 +- wt-status.c | 5 ++++- 7 files changed, 22 insertions(+), 11 deletions(-) (limited to 'diff-lib.c') diff --git a/builtin/update-index.c b/builtin/update-index.c index 56baf27fb7..d7850c6309 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -546,7 +546,10 @@ static int do_reupdate(int ac, const char **av, */ int pos; int has_head = 1; - const char **pathspec = get_pathspec(prefix, av + 1); + const char **paths = get_pathspec(prefix, av + 1); + struct pathspec pathspec; + + init_pathspec(&pathspec, paths); if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial @@ -559,7 +562,7 @@ static int do_reupdate(int ac, const char **av, struct cache_entry *old = NULL; int save_nr; - if (ce_stage(ce) || !ce_path_match(ce, pathspec)) + if (ce_stage(ce) || !ce_path_match(ce, &pathspec)) continue; if (has_head) old = read_one_ent(NULL, head_sha1, @@ -578,6 +581,7 @@ static int do_reupdate(int ac, const char **av, if (save_nr != active_nr) goto redo; } + free_pathspec(&pathspec); return 0; } diff --git a/cache.h b/cache.h index 800efa2328..4beb2dc6ff 100644 --- a/cache.h +++ b/cache.h @@ -515,7 +515,7 @@ struct pathspec { extern int init_pathspec(struct pathspec *, const char **); extern void free_pathspec(struct pathspec *); -extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); +extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); diff --git a/diff-lib.c b/diff-lib.c index 2251f3df68..1e22992cb1 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -106,7 +106,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES)) break; - if (!ce_path_match(ce, revs->prune_data.raw)) + if (!ce_path_match(ce, &revs->prune_data)) continue; if (ce_stage(ce)) { @@ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) if (tree == o->df_conflict_entry) tree = NULL; - if (ce_path_match(idx ? idx : tree, revs->prune_data.raw)) + if (ce_path_match(idx ? idx : tree, &revs->prune_data)) do_oneway_diff(o, idx, tree); return 0; diff --git a/preload-index.c b/preload-index.c index e3d0bda31a..49cb08df96 100644 --- a/preload-index.c +++ b/preload-index.c @@ -35,7 +35,9 @@ static void *preload_thread(void *_data) struct index_state *index = p->index; struct cache_entry **cep = index->cache + p->offset; struct cache_def cache; + struct pathspec pathspec; + init_pathspec(&pathspec, p->pathspec); memset(&cache, 0, sizeof(cache)); nr = p->nr; if (nr + p->offset > index->cache_nr) @@ -51,7 +53,7 @@ static void *preload_thread(void *_data) continue; if (ce_uptodate(ce)) continue; - if (!ce_path_match(ce, p->pathspec)) + if (!ce_path_match(ce, &pathspec)) continue; if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce))) continue; @@ -61,6 +63,7 @@ static void *preload_thread(void *_data) continue; ce_mark_uptodate(ce); } while (--nr > 0); + free_pathspec(&pathspec); return NULL; } diff --git a/read-cache.c b/read-cache.c index 4f2e890b01..8b2d537f02 100644 --- a/read-cache.c +++ b/read-cache.c @@ -706,17 +706,18 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b) return ce_namelen(b) == len && !memcmp(a->name, b->name, len); } -int ce_path_match(const struct cache_entry *ce, const char **pathspec) +int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec) { const char *match, *name; + const char **ps = pathspec->raw; int len; - if (!pathspec) + if (!pathspec->nr) return 1; len = ce_namelen(ce); name = ce->name; - while ((match = *pathspec++) != NULL) { + while ((match = *ps++) != NULL) { int matchlen = strlen(match); if (matchlen > len) continue; diff --git a/revision.c b/revision.c index bf87e01e45..86d2470489 100644 --- a/revision.c +++ b/revision.c @@ -969,7 +969,7 @@ static void prepare_show_merge(struct rev_info *revs) struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; - if (ce_path_match(ce, revs->prune_data.raw)) { + if (ce_path_match(ce, &revs->prune_data)) { prune_num++; prune = xrealloc(prune, sizeof(*prune) * prune_num); prune[prune_num-2] = ce->name; diff --git a/wt-status.c b/wt-status.c index 1ea330ed17..a82b11d341 100644 --- a/wt-status.c +++ b/wt-status.c @@ -354,14 +354,16 @@ static void wt_status_collect_changes_index(struct wt_status *s) static void wt_status_collect_changes_initial(struct wt_status *s) { + struct pathspec pathspec; int i; + init_pathspec(&pathspec, s->pathspec); for (i = 0; i < active_nr; i++) { struct string_list_item *it; struct wt_status_change_data *d; struct cache_entry *ce = active_cache[i]; - if (!ce_path_match(ce, s->pathspec)) + if (!ce_path_match(ce, &pathspec)) continue; it = string_list_insert(&s->change, ce->name); d = it->util; @@ -376,6 +378,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) else d->index_status = DIFF_STATUS_ADDED; } + free_pathspec(&pathspec); } static void wt_status_collect_untracked(struct wt_status *s) -- cgit 1.2.3-korg From fa7b290895ba2c4407ae818911ba620eaca53bbd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Apr 2011 16:05:58 -0700 Subject: diff: remove often unused parameters from diff_unmerge() e9c8409 (diff-index --cached --raw: show tree entry on the LHS for unmerged entries., 2007-01-05) added a pair as parameters to this function, to store them in the pre-image side of an unmerged file pair. Now the function is fixed to return the filepair it queued, we can make the caller on the special case codepath to do so. Signed-off-by: Junio C Hamano --- diff-lib.c | 7 ++++--- diff.c | 5 +---- diff.h | 5 +---- 3 files changed, 6 insertions(+), 11 deletions(-) (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index 392ce2bef0..a98385538c 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -183,7 +183,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) * Show the diff for the 'ce' if we found the one * from the desired stage. */ - diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1); + diff_unmerge(&revs->diffopt, ce->name); if (ce_stage(ce) != diff_unmerged_stage) continue; } @@ -372,8 +372,9 @@ static void do_oneway_diff(struct unpack_trees_options *o, match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { - diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, - idx->sha1); + struct diff_filepair *pair; + pair = diff_unmerge(&revs->diffopt, idx->name); + fill_filespec(pair->one, idx->sha1, idx->ce_mode); return; } diff --git a/diff.c b/diff.c index 4c34c64bd7..d2c5c563bc 100644 --- a/diff.c +++ b/diff.c @@ -4308,9 +4308,7 @@ void diff_change(struct diff_options *options, DIFF_OPT_SET(options, HAS_CHANGES); } -struct diff_filepair *diff_unmerge(struct diff_options *options, - const char *path, - unsigned mode, const unsigned char *sha1) +struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) { struct diff_filepair *pair; struct diff_filespec *one, *two; @@ -4321,7 +4319,6 @@ struct diff_filepair *diff_unmerge(struct diff_options *options, one = alloc_filespec(path); two = alloc_filespec(path); - fill_filespec(one, sha1, mode); pair = diff_queue(&diff_queued_diff, one, two); pair->is_unmerged = 1; return pair; diff --git a/diff.h b/diff.h index f51a8ee1b2..3edb705b4d 100644 --- a/diff.h +++ b/diff.h @@ -209,10 +209,7 @@ extern void diff_change(struct diff_options *, const char *fullpath, unsigned dirty_submodule1, unsigned dirty_submodule2); -extern struct diff_filepair *diff_unmerge(struct diff_options *, - const char *path, - unsigned mode, - const unsigned char *sha1); +extern struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); #define DIFF_SETUP_REVERSE 1 #define DIFF_SETUP_USE_CACHE 2 -- cgit 1.2.3-korg From 095ce9538b738db28d5e9a6e05d94c7e3f55f39d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Apr 2011 16:19:27 -0700 Subject: diff-files: show unmerged entries correctly Earlier, e9c8409 (diff-index --cached --raw: show tree entry on the LHS for unmerged entries., 2007-01-05) taught the command to show the object name and the mode from the entry coming from the tree side when comparing a tree with an unmerged index. This is a belated companion patch that teaches diff-files to show the mode from the entry coming from the working tree side, when comparing an unmerged index and the working tree. Signed-off-by: Junio C Hamano --- diff-lib.c | 10 ++++-- t/t4046-diff-unmerged.sh | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100755 t/t4046-diff-unmerged.sh (limited to 'diff-lib.c') diff --git a/diff-lib.c b/diff-lib.c index a98385538c..b782476e4e 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -111,6 +111,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (ce_stage(ce)) { struct combine_diff_path *dpath; + struct diff_filepair *pair; + unsigned int wt_mode = 0; int num_compare_stages = 0; size_t path_len; @@ -129,7 +131,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) changed = check_removed(ce, &st); if (!changed) - dpath->mode = ce_mode_from_stat(ce, st.st_mode); + wt_mode = ce_mode_from_stat(ce, st.st_mode); else { if (changed < 0) { perror(ce->name); @@ -137,7 +139,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } if (silent_on_removed) continue; + wt_mode = 0; } + dpath->mode = wt_mode; while (i < entries) { struct cache_entry *nce = active_cache[i]; @@ -183,7 +187,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option) * Show the diff for the 'ce' if we found the one * from the desired stage. */ - diff_unmerge(&revs->diffopt, ce->name); + pair = diff_unmerge(&revs->diffopt, ce->name); + if (wt_mode) + pair->two->mode = wt_mode; if (ce_stage(ce) != diff_unmerged_stage) continue; } diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh new file mode 100755 index 0000000000..25d50a654a --- /dev/null +++ b/t/t4046-diff-unmerged.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='diff with unmerged index entries' +. ./test-lib.sh + +test_expect_success setup ' + for i in 0 1 2 3 + do + blob=$(echo $i | git hash-object --stdin) && + eval "blob$i=$blob" && + eval "m$i=\"100644 \$blob$i $i\"" || break + done && + paths= && + for b in o x + do + for o in o x + do + for t in o x + do + path="$b$o$t" && + case "$path" in ooo) continue ;; esac + paths="$paths$path " && + p=" $path" && + case "$b" in x) echo "$m1$p" ;; esac && + case "$o" in x) echo "$m2$p" ;; esac && + case "$t" in x) echo "$m3$p" ;; esac || + break + done || break + done || break + done >ls-files-s.expect && + git update-index --index-info ls-files-s.actual && + test_cmp ls-files-s.expect ls-files-s.actual +' + +test_expect_success 'diff-files -0' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" + done >diff-files-0.expect && + git diff-files -0 >diff-files-0.actual && + test_cmp diff-files-0.expect diff-files-0.actual +' + +test_expect_success 'diff-files -1' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + x??) echo ":100644 100644 $blob1 $_z40 M $path" + esac + done >diff-files-1.expect && + git diff-files -1 >diff-files-1.actual && + test_cmp diff-files-1.expect diff-files-1.actual +' + +test_expect_success 'diff-files -2' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + ?x?) echo ":100644 100644 $blob2 $_z40 M $path" + esac + done >diff-files-2.expect && + git diff-files -2 >diff-files-2.actual && + test_cmp diff-files-2.expect diff-files-2.actual && + git diff-files >diff-files-default-2.actual && + test_cmp diff-files-2.expect diff-files-default-2.actual +' + +test_expect_success 'diff-files -3' ' + for path in $paths + do + >"$path" && + echo ":000000 100644 $_z40 $_z40 U $path" && + case "$path" in + ??x) echo ":100644 100644 $blob3 $_z40 M $path" + esac + done >diff-files-3.expect && + git diff-files -3 >diff-files-3.actual && + test_cmp diff-files-3.expect diff-files-3.actual +' + +test_done -- cgit 1.2.3-korg