Skip to content

Commit

Permalink
add always_exclude file support
Browse files Browse the repository at this point in the history
Added new always_exclude per-repo exclude file.  It has the lowest
priority among exclude and ignore files and the special property that
commands like "git status --ignored" still respect it.

Users with very large repos were previously unable to run certain
commands without them taking ages to complete, even if the user could
reasonbly ignore large parts of the tree, since those commands still
traverse every file.  This allows the user to run certain commands that
interact with ignored files, while still always excluding others.

Signed-off-by: Eric Mecklenburg <ermeckle@microsoft.com>
  • Loading branch information
ermeckle authored and dscho committed Apr 14, 2017
1 parent 4aaf917 commit e553a6b
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 3 deletions.
39 changes: 37 additions & 2 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,29 @@ int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
return 0;
}

static GIT_PATH_FUNC(git_path_info_always_exclude, "info/always_exclude")

int is_always_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
{
struct exclude *exclude = NULL;
struct exclude_list_group *group;
const char *path = git_path_info_always_exclude();
int pathlen = strlen(pathname);
const char *basename = strrchr(pathname, '/');
basename = (basename) ? basename + 1 : pathname;

group = &dir->exclude_list_group[EXC_FILE];
if (group->nr && group->el && !strcmp(group->el->src, path)) {
exclude = last_exclude_matching_from_list(
pathname, pathlen, basename, dtype_p,
group->el);
}

if (exclude)
return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
return 0;
}

static struct dir_entry *dir_entry_new(const char *pathname, int len)
{
struct dir_entry *ent;
Expand Down Expand Up @@ -1524,7 +1547,8 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
* Excluded? If we don't explicitly want to show
* ignored files, ignore it
*/
if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
if (exclude && (!(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)) ||
is_always_excluded(dir, path->buf, &dtype)))
return path_excluded;

switch (dtype) {
Expand Down Expand Up @@ -1729,6 +1753,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
struct strbuf path = STRBUF_INIT;
int dtype;

strbuf_add(&path, base, baselen);

Expand Down Expand Up @@ -1774,6 +1799,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
/* add the path to the appropriate result list */
switch (state) {
case path_excluded:
dtype = DTYPE(cdir.de);
if (is_always_excluded(dir, path.buf, &dtype))
break;
if (dir->flags & DIR_SHOW_IGNORED)
dir_add_name(dir, path.buf, path.len);
else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
Expand Down Expand Up @@ -2215,7 +2243,14 @@ void setup_standard_excludes(struct dir_struct *dir)
{
dir->exclude_per_dir = ".gitignore";

/* core.excludefile defaulting to $XDG_HOME/git/ignore */
/* always_exclude */
if (startup_info->have_repository) {
const char *path = git_path_info_always_exclude();
if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file_1(dir, path, NULL);
}

/* core.excludesfile defaulting to $XDG_HOME/git/ignore */
if (!excludes_file)
excludes_file = xdg_config_home("ignore");
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
Expand Down
33 changes: 32 additions & 1 deletion t/t0008-ignores.sh
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,12 @@ test_expect_success 'setup' '
!globaltwo
globalthree
EOF
cat <<-\EOF >>.git/info/exclude
cat <<-\EOF >>.git/info/exclude &&
per-repo
EOF
cat <<-\EOF >>.git/info/always_exclude
always_exclude
EOF
'

############################################################################
Expand Down Expand Up @@ -561,6 +564,24 @@ test_expect_success 'global ignore with -v' '
test_check_ignore "-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
'

test_expect_success 'always_exclude' '
enable_global_excludes &&
expect_from_stdin <<-\EOF &&
always_exclude
a/always_exclude
EOF
test_check_ignore "always_exclude a/always_exclude"
'

test_expect_success 'always_exclude with -v' '
enable_global_excludes &&
expect_from_stdin <<-EOF &&
.git/info/always_exclude:1:always_exclude always_exclude
.git/info/always_exclude:1:always_exclude a/always_exclude
EOF
test_check_ignore "-v always_exclude a/always_exclude"
'

############################################################################
#
# test --stdin
Expand Down Expand Up @@ -841,4 +862,14 @@ test_expect_success 'info/exclude trumps core.excludesfile' '
test_cmp expect actual
'

test_expect_success 'core.excludesfile trumps info/always_exclude' '
echo >>.git/info/always_exclude usually-ignored &&
echo >>global-excludes "!usually-ignored" &&
>usually-ignored &&
echo "?? usually-ignored" >expect &&
git status --porcelain usually-ignored >actual &&
test_cmp expect actual
'

test_done
36 changes: 36 additions & 0 deletions t/t2204-add-ignored.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,40 @@ do
'
done

test_expect_success always_exclude_setup '
rm -rf sub dir .git/index ign file &&
mkdir sub &&
echo always_excluded >.git/info/always_exclude &&
>always_excluded &&
>sub/always_excluded &&
>not_excluded
'

test_expect_success "silent failure for always_excluded file" '
git add always_excluded >actual_out 2>actual_err &&
: >expect_out &&
: >expect_err &&
test_cmp expect_out actual_out &&
test_cmp expect_err actual_err &&
test_path_is_missing .git/index
'

test_expect_success "silent failure for always_excluded file in sub" '
git add sub/always_excluded >actual_out 2>actual_err &&
: >expect_out &&
: >expect_err &&
test_cmp expect_out actual_out &&
test_cmp expect_err actual_err &&
test_path_is_missing .git/index
'

test_expect_success "success for file not excluded" '
git add not_excluded >actual_out 2>actual_err &&
: >expect_out &&
: >expect_err &&
test_cmp expect_out actual_out &&
test_cmp expect_err actual_err &&
test_path_is_file .git/index
'

test_done
49 changes: 49 additions & 0 deletions t/t3001-ls-files-others-exclude.sh
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,56 @@ test_expect_success 'negated directory doesn'\''t affect content patterns' '
fi
'

test_expect_success 'always_exclude (setup)' '
mkdir -p top &&
(
cd top &&
git init &&
echo ignored >.gitignore &&
echo excluded >.git/info/exclude &&
echo always_excluded >.git/info/always_exclude &&
>not_ignored &&
>ignored &&
>excluded &&
>always_excluded
)
'

test_expect_success 'always_exclude (plain)' '
(
cd top &&
git ls-files -o --exclude-standard
) >actual &&
cat >expect <<\EOF &&
.gitignore
not_ignored
EOF
test_cmp expect actual
'

test_expect_success 'always_exclude (-i)' '
(
cd top &&
git ls-files -o -i --exclude-standard
) >actual &&
cat >expect <<\EOF &&
excluded
ignored
EOF
test_cmp expect actual
'

test_expect_success 'always_exclude (exclude-from)' '
(
cd top &&
git ls-files -o -i --exclude-from=.git/info/always_exclude
) >actual &&
>expect &&
test_cmp expect actual
'

test_expect_success 'subdirectory ignore (setup)' '
rm -rf top &&
mkdir -p top/l1/l2 &&
(
cd top &&
Expand Down
14 changes: 14 additions & 0 deletions t/t7061-wtstatus-ignore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,18 @@ test_expect_success 'status ignored tracked directory with uncommitted file in t
test_cmp expected actual
'

cat >expected <<\EOF
?? .gitignore
?? actual
?? expected
!! tracked/ignored/uncommitted
EOF

test_expect_success 'status ignored with always_exclude' '
echo "always_excluded" >.git/info/always_exclude &&
: >always_excluded &&
git status --porcelain --ignored >actual &&
test_cmp expected actual
'

test_done

0 comments on commit e553a6b

Please sign in to comment.