Skip to content

Commit aee9c7d

Browse files
jlehmanngitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 64fdc08 commit aee9c7d

File tree

11 files changed

+297
-20
lines changed

11 files changed

+297
-20
lines changed

Documentation/config.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,16 @@ submodule.<name>.update::
17431743
URL and other values found in the `.gitmodules` file. See
17441744
linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
17451745

1746+
submodule.<name>.ignore::
1747+
Defines under what circumstances "git status" and the diff family show
1748+
a submodule as modified. When set to "all", it will never be considered
1749+
modified, "dirty" will ignore all changes to the submodules work tree and
1750+
takes only differences between the HEAD of the submodule and the commit
1751+
recorded in the superproject into account. "untracked" will additionally
1752+
let submodules with modified tracked files in their work tree show up.
1753+
Using "none" (the default when this option is not set) also shows
1754+
submodules that have untracked files in their work tree as changed.
1755+
17461756
tar.umask::
17471757
This variable can be used to restrict the permission bits of
17481758
tar archive entries. The default is 0002, which turns off the

Documentation/diff-options.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,11 @@ endif::git-format-patch[]
330330

331331
--ignore-submodules[=<when>]::
332332
Ignore changes to submodules in the diff generation. <when> can be
333-
either "untracked", "dirty" or "all", which is the default. When
333+
either "none", "untracked", "dirty" or "all", which is the default
334+
Using "none" will consider the submodule modified when it either contains
335+
untracked or modified files or its HEAD differs from the commit recorded
336+
in the superproject and can be used to override any settings of the
337+
'ignore' option in linkgit:git-config[1]. When
334338
"untracked" is used submodules are not considered dirty when they only
335339
contain untracked content (but they are still scanned for modified
336340
content). Using "dirty" ignores all changes to the work tree of submodules,

Documentation/git-status.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ specified.
5555

5656
--ignore-submodules[=<when>]::
5757
Ignore changes to submodules when looking for changes. <when> can be
58-
either "untracked", "dirty" or "all", which is the default. When
58+
either "none", "untracked", "dirty" or "all", which is the default.
59+
Using "none" will consider the submodule modified when it either contains
60+
untracked or modified files or its HEAD differs from the commit recorded
61+
in the superproject and can be used to override any settings of the
62+
'ignore' option in linkgit:git-config[1]. When
5963
"untracked" is used submodules are not considered dirty when they only
6064
contain untracked content (but they are still scanned for modified
6165
content). Using "dirty" ignores all changes to the work tree of submodules,

diff-lib.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,16 @@ static int match_stat_with_submodule(struct diff_options *diffopt,
6868
unsigned ce_option, unsigned *dirty_submodule)
6969
{
7070
int changed = ce_match_stat(ce, st, ce_option);
71-
if (S_ISGITLINK(ce->ce_mode)
72-
&& !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)
73-
&& !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
74-
&& (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) {
75-
*dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
71+
if (S_ISGITLINK(ce->ce_mode)) {
72+
unsigned orig_flags = diffopt->flags;
73+
if (!DIFF_OPT_TST(diffopt, OVERRIDE_SUBMODULE_CONFIG))
74+
set_diffopt_flags_from_submodule_config(diffopt, ce->name);
75+
if (DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES))
76+
changed = 0;
77+
else if (!DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES)
78+
&& (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES)))
79+
*dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES));
80+
diffopt->flags = orig_flags;
7681
}
7782
return changed;
7883
}

diff.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
141141
return 0;
142142
}
143143

144+
if (!prefixcmp(var, "submodule."))
145+
return parse_submodule_config_option(var, value);
146+
144147
return git_color_default_config(var, value, cb);
145148
}
146149

@@ -3166,11 +3169,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
31663169
DIFF_OPT_SET(options, ALLOW_TEXTCONV);
31673170
else if (!strcmp(arg, "--no-textconv"))
31683171
DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
3169-
else if (!strcmp(arg, "--ignore-submodules"))
3172+
else if (!strcmp(arg, "--ignore-submodules")) {
3173+
DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
31703174
handle_ignore_submodules_arg(options, "all");
3171-
else if (!prefixcmp(arg, "--ignore-submodules="))
3175+
} else if (!prefixcmp(arg, "--ignore-submodules=")) {
3176+
DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
31723177
handle_ignore_submodules_arg(options, arg + 20);
3173-
else if (!strcmp(arg, "--submodule"))
3178+
} else if (!strcmp(arg, "--submodule"))
31743179
DIFF_OPT_SET(options, SUBMODULE_LOG);
31753180
else if (!prefixcmp(arg, "--submodule=")) {
31763181
if (!strcmp(arg + 12, "log"))
@@ -4103,14 +4108,32 @@ int diff_result_code(struct diff_options *opt, int status)
41034108
return result;
41044109
}
41054110

4111+
/*
4112+
* Shall changes to this submodule be ignored?
4113+
*
4114+
* Submodule changes can be configured to be ignored separately for each path,
4115+
* but that configuration can be overridden from the command line.
4116+
*/
4117+
static int is_submodule_ignored(const char *path, struct diff_options *options)
4118+
{
4119+
int ignored = 0;
4120+
unsigned orig_flags = options->flags;
4121+
if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
4122+
set_diffopt_flags_from_submodule_config(options, path);
4123+
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
4124+
ignored = 1;
4125+
options->flags = orig_flags;
4126+
return ignored;
4127+
}
4128+
41064129
void diff_addremove(struct diff_options *options,
41074130
int addremove, unsigned mode,
41084131
const unsigned char *sha1,
41094132
const char *concatpath, unsigned dirty_submodule)
41104133
{
41114134
struct diff_filespec *one, *two;
41124135

4113-
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
4136+
if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
41144137
return;
41154138

41164139
/* This may look odd, but it is a preparation for
@@ -4157,8 +4180,8 @@ void diff_change(struct diff_options *options,
41574180
{
41584181
struct diff_filespec *one, *two;
41594182

4160-
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
4161-
&& S_ISGITLINK(new_mode))
4183+
if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
4184+
is_submodule_ignored(concatpath, options))
41624185
return;
41634186

41644187
if (DIFF_OPT_TST(options, REVERSE_DIFF)) {

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
7777
#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24)
7878
#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
7979
#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
80+
#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
8081

8182
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
8283
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)

submodule.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
#include "revision.h"
77
#include "run-command.h"
88
#include "diffcore.h"
9+
#include "string-list.h"
10+
11+
struct string_list config_name_for_path;
12+
struct string_list config_ignore_for_name;
913

1014
static int add_submodule_odb(const char *path)
1115
{
@@ -46,6 +50,57 @@ static int add_submodule_odb(const char *path)
4650
return ret;
4751
}
4852

53+
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
54+
const char *path)
55+
{
56+
struct string_list_item *path_option, *ignore_option;
57+
path_option = unsorted_string_list_lookup(&config_name_for_path, path);
58+
if (path_option) {
59+
ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util);
60+
if (ignore_option)
61+
handle_ignore_submodules_arg(diffopt, ignore_option->util);
62+
}
63+
}
64+
65+
int parse_submodule_config_option(const char *var, const char *value)
66+
{
67+
int len;
68+
struct string_list_item *config;
69+
struct strbuf submodname = STRBUF_INIT;
70+
71+
var += 10; /* Skip "submodule." */
72+
73+
len = strlen(var);
74+
if ((len > 5) && !strcmp(var + len - 5, ".path")) {
75+
strbuf_add(&submodname, var, len - 5);
76+
config = unsorted_string_list_lookup(&config_name_for_path, value);
77+
if (config)
78+
free(config->util);
79+
else
80+
config = string_list_append(&config_name_for_path, xstrdup(value));
81+
config->util = strbuf_detach(&submodname, NULL);
82+
strbuf_release(&submodname);
83+
} else if ((len > 7) && !strcmp(var + len - 7, ".ignore")) {
84+
if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
85+
strcmp(value, "all") && strcmp(value, "none")) {
86+
warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
87+
return 0;
88+
}
89+
90+
strbuf_add(&submodname, var, len - 7);
91+
config = unsorted_string_list_lookup(&config_ignore_for_name, submodname.buf);
92+
if (config)
93+
free(config->util);
94+
else
95+
config = string_list_append(&config_ignore_for_name,
96+
strbuf_detach(&submodname, NULL));
97+
strbuf_release(&submodname);
98+
config->util = xstrdup(value);
99+
return 0;
100+
}
101+
return 0;
102+
}
103+
49104
void handle_ignore_submodules_arg(struct diff_options *diffopt,
50105
const char *arg)
51106
{
@@ -55,7 +110,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
55110
DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
56111
else if (!strcmp(arg, "dirty"))
57112
DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
58-
else
113+
else if (strcmp(arg, "none"))
59114
die("bad --ignore-submodules argument: %s", arg);
60115
}
61116

submodule.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
struct diff_options;
55

6+
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
7+
const char *path);
8+
int parse_submodule_config_option(const char *var, const char *value);
69
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
710
void show_submodule_summary(FILE *f, const char *path,
811
unsigned char one[20], unsigned char two[20],

t/t4027-diff-submodule.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)'
114114
! test -s actual4
115115
'
116116

117+
test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' '
118+
git config submodule.subname.ignore none &&
119+
git config submodule.subname.path sub &&
120+
git diff HEAD >actual &&
121+
sed -e "1,/^@@/d" actual >actual.body &&
122+
expect_from_to >expect.body $subprev $subprev-dirty &&
123+
test_cmp expect.body actual.body &&
124+
git config submodule.subname.ignore all &&
125+
git diff HEAD >actual2 &&
126+
! test -s actual2 &&
127+
git config submodule.subname.ignore untracked &&
128+
git diff HEAD >actual3 &&
129+
sed -e "1,/^@@/d" actual3 >actual3.body &&
130+
expect_from_to >expect.body $subprev $subprev-dirty &&
131+
test_cmp expect.body actual3.body &&
132+
git config submodule.subname.ignore dirty &&
133+
git diff HEAD >actual4 &&
134+
! test -s actual4 &&
135+
git diff HEAD --ignore-submodules=none >actual &&
136+
sed -e "1,/^@@/d" actual >actual.body &&
137+
expect_from_to >expect.body $subprev $subprev-dirty &&
138+
test_cmp expect.body actual.body &&
139+
git config --remove-section submodule.subname
140+
'
141+
117142
test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '
118143
(
119144
cd sub &&
@@ -146,6 +171,57 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)'
146171
! test -s actual4
147172
'
148173

174+
test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' '
175+
git config submodule.subname.ignore all &&
176+
git config submodule.subname.path sub &&
177+
git diff HEAD >actual2 &&
178+
! test -s actual2 &&
179+
git config submodule.subname.ignore untracked &&
180+
git diff HEAD >actual3 &&
181+
! test -s actual3 &&
182+
git config submodule.subname.ignore dirty &&
183+
git diff HEAD >actual4 &&
184+
! test -s actual4 &&
185+
git diff --ignore-submodules=none HEAD >actual &&
186+
sed -e "1,/^@@/d" actual >actual.body &&
187+
expect_from_to >expect.body $subprev $subprev-dirty &&
188+
test_cmp expect.body actual.body &&
189+
git config --remove-section submodule.subname
190+
'
191+
192+
test_expect_success 'git diff between submodule commits' '
193+
git diff HEAD^..HEAD >actual &&
194+
sed -e "1,/^@@/d" actual >actual.body &&
195+
expect_from_to >expect.body $subtip $subprev &&
196+
test_cmp expect.body actual.body &&
197+
git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
198+
sed -e "1,/^@@/d" actual >actual.body &&
199+
expect_from_to >expect.body $subtip $subprev &&
200+
test_cmp expect.body actual.body &&
201+
git diff --ignore-submodules HEAD^..HEAD >actual &&
202+
! test -s actual
203+
'
204+
205+
test_expect_success 'git diff between submodule commits [.git/config]' '
206+
git diff HEAD^..HEAD >actual &&
207+
sed -e "1,/^@@/d" actual >actual.body &&
208+
expect_from_to >expect.body $subtip $subprev &&
209+
test_cmp expect.body actual.body &&
210+
git config submodule.subname.ignore dirty &&
211+
git config submodule.subname.path sub &&
212+
git diff HEAD^..HEAD >actual &&
213+
sed -e "1,/^@@/d" actual >actual.body &&
214+
expect_from_to >expect.body $subtip $subprev &&
215+
test_cmp expect.body actual.body &&
216+
git config submodule.subname.ignore all &&
217+
git diff HEAD^..HEAD >actual &&
218+
! test -s actual &&
219+
git diff --ignore-submodules=dirty HEAD^..HEAD >actual &&
220+
sed -e "1,/^@@/d" actual >actual.body &&
221+
expect_from_to >expect.body $subtip $subprev &&
222+
git config --remove-section submodule.subname
223+
'
224+
149225
test_expect_success 'git diff (empty submodule dir)' '
150226
: >empty &&
151227
rm -rf sub/* sub/.git &&

0 commit comments

Comments
 (0)