Skip to content

Commit 2aa9100

Browse files
committed
Merge branch 'dotgit-case-maint-1.8.5' into maint-1.8.5
* dotgit-case-maint-1.8.5: fsck: complain about NTFS ".git" aliases in trees read-cache: optionally disallow NTFS .git variants path: add is_ntfs_dotgit() helper fsck: complain about HFS+ ".git" aliases in trees read-cache: optionally disallow HFS+ .git variants utf8: add is_hfs_dotgit() helper fsck: notice .git case-insensitively t1450: refactor ".", "..", and ".git" fsck tests verify_dotfile(): reject .git case-insensitively read-tree: add tests for confusing paths like ".." and ".git" unpack-trees: propagate errors adding entries to the index
2 parents eeff891 + d08c13b commit 2aa9100

File tree

14 files changed

+261
-38
lines changed

14 files changed

+261
-38
lines changed

Documentation/config.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ core.precomposeunicode::
234234
When false, file names are handled fully transparent by Git,
235235
which is backward compatible with older versions of Git.
236236

237+
core.protectHFS::
238+
If set to true, do not allow checkout of paths that would
239+
be considered equivalent to `.git` on an HFS+ filesystem.
240+
Defaults to `true` on Mac OS, and `false` elsewhere.
241+
242+
core.protectNTFS::
243+
If set to true, do not allow checkout of paths that would
244+
cause problems with the NTFS filesystem, e.g. conflict with
245+
8.3 "short" names.
246+
Defaults to `true` on Windows, and `false` elsewhere.
247+
237248
core.trustctime::
238249
If false, the ctime differences between the index and the
239250
working tree are ignored; useful when the inode change time

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ extern int fsync_object_files;
584584
extern int core_preload_index;
585585
extern int core_apply_sparse_checkout;
586586
extern int precomposed_unicode;
587+
extern int protect_hfs;
588+
extern int protect_ntfs;
587589

588590
/*
589591
* The character that begins a commented line in user-editable file
@@ -758,6 +760,7 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes);
758760
char *strip_path_suffix(const char *path, const char *suffix);
759761
int daemon_avoid_alias(const char *path);
760762
int offset_1st_component(const char *path);
763+
extern int is_ntfs_dotgit(const char *name);
761764

762765
/* object replacement */
763766
#define READ_SHA1_FILE_REPLACE 1

config.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,16 @@ static int git_default_core_config(const char *var, const char *value)
881881
return 0;
882882
}
883883

884+
if (!strcmp(var, "core.protecthfs")) {
885+
protect_hfs = git_config_bool(var, value);
886+
return 0;
887+
}
888+
889+
if (!strcmp(var, "core.protectntfs")) {
890+
protect_ntfs = git_config_bool(var, value);
891+
return 0;
892+
}
893+
884894
/* Add other config variables here and to Documentation/config.txt. */
885895
return 0;
886896
}

config.mak.uname

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ ifeq ($(uname_S),Darwin)
9797
HAVE_DEV_TTY = YesPlease
9898
COMPAT_OBJS += compat/precompose_utf8.o
9999
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
100+
BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1
100101
endif
101102
ifeq ($(uname_S),SunOS)
102103
NEEDS_SOCKET = YesPlease
@@ -361,6 +362,7 @@ ifeq ($(uname_S),Windows)
361362
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
362363
PTHREAD_LIBS =
363364
lib =
365+
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
364366
ifndef DEBUG
365367
BASIC_CFLAGS += -GL -Os -MT
366368
BASIC_LDFLAGS += -LTCG
@@ -505,6 +507,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
505507
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
506508
compat/win32/pthread.o compat/win32/syslog.o \
507509
compat/win32/dirent.o
510+
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
508511
BASIC_LDFLAGS += -Wl,--large-address-aware
509512
EXTLIBS += -lws2_32
510513
GITLIBS += git.res

environment.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
6363
struct startup_info *startup_info;
6464
unsigned long pack_size_limit_cfg;
6565

66+
#ifndef PROTECT_HFS_DEFAULT
67+
#define PROTECT_HFS_DEFAULT 0
68+
#endif
69+
int protect_hfs = PROTECT_HFS_DEFAULT;
70+
71+
#ifndef PROTECT_NTFS_DEFAULT
72+
#define PROTECT_NTFS_DEFAULT 0
73+
#endif
74+
int protect_ntfs = PROTECT_NTFS_DEFAULT;
75+
6676
/*
6777
* The character that begins a commented line in user-editable file
6878
* that is subject to stripspace.

fsck.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "commit.h"
77
#include "tag.h"
88
#include "fsck.h"
9+
#include "utf8.h"
910

1011
static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
1112
{
@@ -175,7 +176,8 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
175176
has_dot = 1;
176177
if (!strcmp(name, ".."))
177178
has_dotdot = 1;
178-
if (!strcmp(name, ".git"))
179+
if (!strcasecmp(name, ".git") || is_hfs_dotgit(name) ||
180+
is_ntfs_dotgit(name))
179181
has_dotgit = 1;
180182
has_zero_pad |= *(char *)desc.buffer == '0';
181183
update_tree_entry(&desc);

path.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,3 +830,36 @@ int offset_1st_component(const char *path)
830830
return 2 + is_dir_sep(path[2]);
831831
return is_dir_sep(path[0]);
832832
}
833+
834+
static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
835+
{
836+
if (len < skip)
837+
return 0;
838+
len -= skip;
839+
path += skip;
840+
while (len-- > 0) {
841+
char c = *(path++);
842+
if (c != ' ' && c != '.')
843+
return 0;
844+
}
845+
return 1;
846+
}
847+
848+
int is_ntfs_dotgit(const char *name)
849+
{
850+
int len;
851+
852+
for (len = 0; ; len++)
853+
if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
854+
if (only_spaces_and_periods(name, len, 4) &&
855+
!strncasecmp(name, ".git", 4))
856+
return 1;
857+
if (only_spaces_and_periods(name, len, 5) &&
858+
!strncasecmp(name, "git~1", 5))
859+
return 1;
860+
if (name[len] != '\\')
861+
return 0;
862+
name += len + 1;
863+
len = -1;
864+
}
865+
}

read-cache.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "resolve-undo.h"
1515
#include "strbuf.h"
1616
#include "varint.h"
17+
#include "utf8.h"
1718

1819
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
1920

@@ -759,9 +760,10 @@ static int verify_dotfile(const char *rest)
759760
* shares the path end test with the ".." case.
760761
*/
761762
case 'g':
762-
if (rest[1] != 'i')
763+
case 'G':
764+
if (rest[1] != 'i' && rest[1] != 'I')
763765
break;
764-
if (rest[2] != 't')
766+
if (rest[2] != 't' && rest[2] != 'T')
765767
break;
766768
rest += 2;
767769
/* fallthrough */
@@ -785,6 +787,10 @@ int verify_path(const char *path)
785787
return 1;
786788
if (is_dir_sep(c)) {
787789
inside:
790+
if (protect_hfs && is_hfs_dotgit(path))
791+
return 0;
792+
if (protect_ntfs && is_ntfs_dotgit(path))
793+
return 0;
788794
c = *path++;
789795
if ((c == '.' && !verify_dotfile(path)) ||
790796
is_dir_sep(c) || c == '\0')

t/t1014-read-tree-confusing.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/bin/sh
2+
3+
test_description='check that read-tree rejects confusing paths'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'create base tree' '
7+
echo content >file &&
8+
git add file &&
9+
git commit -m base &&
10+
blob=$(git rev-parse HEAD:file) &&
11+
tree=$(git rev-parse HEAD^{tree})
12+
'
13+
14+
test_expect_success 'enable core.protectHFS for rejection tests' '
15+
git config core.protectHFS true
16+
'
17+
18+
test_expect_success 'enable core.protectNTFS for rejection tests' '
19+
git config core.protectNTFS true
20+
'
21+
22+
while read path pretty; do
23+
: ${pretty:=$path}
24+
case "$path" in
25+
*SPACE)
26+
path="${path%SPACE} "
27+
;;
28+
esac
29+
test_expect_success "reject $pretty at end of path" '
30+
printf "100644 blob %s\t%s" "$blob" "$path" >tree &&
31+
bogus=$(git mktree <tree) &&
32+
test_must_fail git read-tree $bogus
33+
'
34+
35+
test_expect_success "reject $pretty as subtree" '
36+
printf "040000 tree %s\t%s" "$tree" "$path" >tree &&
37+
bogus=$(git mktree <tree) &&
38+
test_must_fail git read-tree $bogus
39+
'
40+
done <<-EOF
41+
.
42+
..
43+
.git
44+
.GIT
45+
${u200c}.Git {u200c}.Git
46+
.gI${u200c}T .gI{u200c}T
47+
.GiT${u200c} .GiT{u200c}
48+
git~1
49+
.git.SPACE .git.{space}
50+
.\\\\.GIT\\\\foobar backslashes
51+
.git\\\\foobar backslashes2
52+
EOF
53+
54+
test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
55+
test_when_finished "git read-tree HEAD" &&
56+
test_config core.protectHFS false &&
57+
printf "100644 blob %s\t%s" "$blob" ".gi${u200c}t" >tree &&
58+
ok=$(git mktree <tree) &&
59+
git read-tree $ok
60+
'
61+
62+
test_done

t/t1450-fsck.sh

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -237,35 +237,40 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
237237
)
238238
'
239239

240-
test_expect_success 'fsck notices "." and ".." in trees' '
241-
(
242-
git init dots &&
243-
cd dots &&
244-
blob=$(echo foo | git hash-object -w --stdin) &&
245-
tab=$(printf "\\t") &&
246-
git mktree <<-EOF &&
247-
100644 blob $blob$tab.
248-
100644 blob $blob$tab..
249-
EOF
250-
git fsck 2>out &&
251-
cat out &&
252-
grep "warning.*\\." out
253-
)
254-
'
255-
256-
test_expect_success 'fsck notices ".git" in trees' '
257-
(
258-
git init dotgit &&
259-
cd dotgit &&
260-
blob=$(echo foo | git hash-object -w --stdin) &&
261-
tab=$(printf "\\t") &&
262-
git mktree <<-EOF &&
263-
100644 blob $blob$tab.git
264-
EOF
265-
git fsck 2>out &&
266-
cat out &&
267-
grep "warning.*\\.git" out
268-
)
269-
'
240+
while read name path pretty; do
241+
while read mode type; do
242+
: ${pretty:=$path}
243+
test_expect_success "fsck notices $pretty as $type" '
244+
(
245+
git init $name-$type &&
246+
cd $name-$type &&
247+
echo content >file &&
248+
git add file &&
249+
git commit -m base &&
250+
blob=$(git rev-parse :file) &&
251+
tree=$(git rev-parse HEAD^{tree}) &&
252+
value=$(eval "echo \$$type") &&
253+
printf "$mode $type %s\t%s" "$value" "$path" >bad &&
254+
bad_tree=$(git mktree <bad) &&
255+
git fsck 2>out &&
256+
cat out &&
257+
grep "warning.*tree $bad_tree" out
258+
)'
259+
done <<-\EOF
260+
100644 blob
261+
040000 tree
262+
EOF
263+
done <<-EOF
264+
dot .
265+
dotdot ..
266+
dotgit .git
267+
dotgit-case .GIT
268+
dotgit-unicode .gI${u200c}T .gI{u200c}T
269+
dotgit-case2 .Git
270+
git-tilde1 git~1
271+
dotgitdot .git.
272+
dot-backslash-case .\\\\.GIT\\\\foobar
273+
dotgit-case-backslash .git\\\\foobar
274+
EOF
270275

271276
test_done

0 commit comments

Comments
 (0)