Skip to content

Commit 4d06f8a

Browse files
author
Junio C Hamano
committed
Fix 'git add' with .gitignore
When '*.ig' is ignored, and you have two files f.ig and d.ig/foo in the working tree, $ git add . correctly ignored f.ig but failed to ignore d.ig/foo. This was caused by a thinko in an earlier commit 4888c53, when we tried to allow adding otherwise ignored files. After reverting that commit, this takes a much simpler approach. When we have an unmatched pathspec that talks about an existing pathname, we know it is an ignored path the user tried to add, so we include it in the set of paths directory walker returned. This does not let you say "git add -f D" on an ignored directory D and add everything under D. People can submit a patch to further allow it if they want to, but I think it is a saner behaviour to require explicit paths to be spelled out in such a case. Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent c889763 commit 4d06f8a

File tree

4 files changed

+69
-29
lines changed

4 files changed

+69
-29
lines changed

builtin-add.c

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,9 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
2626
i = dir->nr;
2727
while (--i >= 0) {
2828
struct dir_entry *entry = *src++;
29-
int how = match_pathspec(pathspec, entry->name, entry->len,
30-
prefix, seen);
31-
/*
32-
* ignored entries can be added with exact match,
33-
* but not with glob nor recursive.
34-
*/
35-
if (!how ||
36-
(entry->ignored_entry && how != MATCHED_EXACTLY)) {
37-
free(entry);
38-
continue;
39-
}
40-
*dst++ = entry;
29+
if (match_pathspec(pathspec, entry->name, entry->len,
30+
prefix, seen))
31+
*dst++ = entry;
4132
}
4233
dir->nr = dst - dir->entries;
4334

@@ -47,10 +38,20 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
4738
if (seen[i])
4839
continue;
4940

50-
/* Existing file? We must have ignored it */
5141
match = pathspec[i];
52-
if (!match[0] || !lstat(match, &st))
42+
if (!match[0])
5343
continue;
44+
45+
/* Existing file? We must have ignored it */
46+
if (!lstat(match, &st)) {
47+
struct dir_entry *ent;
48+
49+
ent = dir_add_name(dir, match, strlen(match));
50+
ent->ignored = 1;
51+
if (S_ISDIR(st.st_mode))
52+
ent->ignored_dir = 1;
53+
continue;
54+
}
5455
die("pathspec '%s' did not match any files", match);
5556
}
5657
}
@@ -62,8 +63,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
6263

6364
/* Set up the default git porcelain excludes */
6465
memset(dir, 0, sizeof(*dir));
65-
if (pathspec)
66-
dir->show_both = 1;
6766
dir->exclude_per_dir = ".gitignore";
6867
path = git_path("info/exclude");
6968
if (!access(path, R_OK))
@@ -154,7 +153,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
154153
if (show_only) {
155154
const char *sep = "", *eof = "";
156155
for (i = 0; i < dir.nr; i++) {
157-
if (!ignored_too && dir.entries[i]->ignored_entry)
156+
if (!ignored_too && dir.entries[i]->ignored)
158157
continue;
159158
printf("%s%s", sep, dir.entries[i]->name);
160159
sep = " ";
@@ -168,16 +167,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)
168167
die("index file corrupt");
169168

170169
if (!ignored_too) {
171-
int has_ignored = -1;
172-
for (i = 0; has_ignored < 0 && i < dir.nr; i++)
173-
if (dir.entries[i]->ignored_entry)
174-
has_ignored = i;
175-
if (0 <= has_ignored) {
170+
int has_ignored = 0;
171+
for (i = 0; i < dir.nr; i++)
172+
if (dir.entries[i]->ignored)
173+
has_ignored = 1;
174+
if (has_ignored) {
176175
fprintf(stderr, ignore_warning);
177-
for (i = has_ignored; i < dir.nr; i++) {
178-
if (!dir.entries[i]->ignored_entry)
176+
for (i = 0; i < dir.nr; i++) {
177+
if (!dir.entries[i]->ignored)
179178
continue;
180-
fprintf(stderr, "%s\n", dir.entries[i]->name);
179+
fprintf(stderr, "%s", dir.entries[i]->name);
180+
if (dir.entries[i]->ignored_dir)
181+
fprintf(stderr, " (directory)");
182+
fputc('\n', stderr);
181183
}
182184
fprintf(stderr,
183185
"Use -f if you really want to add them.\n");

dir.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,23 +260,25 @@ int excluded(struct dir_struct *dir, const char *pathname)
260260
return 0;
261261
}
262262

263-
static void add_name(struct dir_struct *dir, const char *pathname, int len)
263+
struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
264264
{
265265
struct dir_entry *ent;
266266

267267
if (cache_name_pos(pathname, len) >= 0)
268-
return;
268+
return NULL;
269269

270270
if (dir->nr == dir->alloc) {
271271
int alloc = alloc_nr(dir->alloc);
272272
dir->alloc = alloc;
273273
dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
274274
}
275275
ent = xmalloc(sizeof(*ent) + len + 1);
276+
ent->ignored = ent->ignored_dir = 0;
276277
ent->len = len;
277278
memcpy(ent->name, pathname, len);
278279
ent->name[len] = 0;
279280
dir->entries[dir->nr++] = ent;
281+
return ent;
280282
}
281283

282284
static int dir_exists(const char *dirname, int len)
@@ -364,7 +366,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
364366
if (check_only)
365367
goto exit_early;
366368
else
367-
add_name(dir, fullname, baselen + len);
369+
dir_add_name(dir, fullname, baselen + len);
368370
}
369371
exit_early:
370372
closedir(fdir);

dir.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414

1515
struct dir_entry {
16-
int len;
16+
unsigned int ignored : 1;
17+
unsigned int ignored_dir : 1;
18+
unsigned int len : 30;
1719
char name[FLEX_ARRAY]; /* more */
1820
};
1921

@@ -55,5 +57,6 @@ extern void add_excludes_from_file(struct dir_struct *, const char *fname);
5557
extern void add_exclude(const char *string, const char *base,
5658
int baselen, struct exclude_list *which);
5759
extern int file_exists(const char *);
60+
extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
5861

5962
#endif

t/t3700-add.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,37 @@ test_expect_success \
5151
*) echo fail; git-ls-files --stage xfoo3; (exit 1);;
5252
esac'
5353

54+
test_expect_success '.gitignore test setup' '
55+
echo "*.ig" >.gitignore &&
56+
mkdir c.if d.ig &&
57+
>a.ig && >b.if &&
58+
>c.if/c.if && >c.if/c.ig &&
59+
>d.ig/d.if && >d.ig/d.ig
60+
'
61+
62+
test_expect_success '.gitignore is honored' '
63+
git-add . &&
64+
! git-ls-files | grep "\\.ig"
65+
'
66+
67+
test_expect_success 'error out when attempting to add ignored ones without -f' '
68+
! git-add a.?? &&
69+
! git-ls-files | grep "\\.ig"
70+
'
71+
72+
test_expect_success 'error out when attempting to add ignored ones without -f' '
73+
! git-add d.?? &&
74+
! git-ls-files | grep "\\.ig"
75+
'
76+
77+
test_expect_success 'add ignored ones with -f' '
78+
git-add -f a.?? &&
79+
git-ls-files --error-unmatch a.ig
80+
'
81+
82+
test_expect_success 'add ignored ones with -f' '
83+
git-add -f d.??/* &&
84+
git-ls-files --error-unmatch d.ig/d.if d.ig/d.ig
85+
'
86+
5487
test_done

0 commit comments

Comments
 (0)