Skip to content

Commit

Permalink
Submodules: Add the new "ignore" config option for diff and status
Browse files Browse the repository at this point in the history
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 <Jens.Lehmann@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Jens Lehmann authored and Junio C Hamano committed Aug 9, 2010
1 parent 64fdc08 commit aee9c7d
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 20 deletions.
10 changes: 10 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,16 @@ submodule.<name>.update::
URL and other values found in the `.gitmodules` file. See
linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.

submodule.<name>.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
Expand Down
6 changes: 5 additions & 1 deletion Documentation/diff-options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,11 @@ endif::git-format-patch[]

--ignore-submodules[=<when>]::
Ignore changes to submodules in the diff generation. <when> 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,
Expand Down
6 changes: 5 additions & 1 deletion Documentation/git-status.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ specified.

--ignore-submodules[=<when>]::
Ignore changes to submodules when looking for changes. <when> 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,
Expand Down
15 changes: 10 additions & 5 deletions diff-lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
35 changes: 29 additions & 6 deletions diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -4103,14 +4108,32 @@ 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,
const char *concatpath, unsigned dirty_submodule)
{
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
Expand Down Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions diff.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
57 changes: 56 additions & 1 deletion submodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
}

Expand Down
3 changes: 3 additions & 0 deletions submodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
76 changes: 76 additions & 0 deletions t/t4027-diff-submodule.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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 &&
Expand Down
Loading

0 comments on commit aee9c7d

Please sign in to comment.