diff options
authorJohannes Schindelin <>2014-12-16 22:31:03 (GMT)
committerJunio C Hamano <>2014-12-17 19:04:45 (GMT)
commit1d1d69bc52dcc7def5b2edbd165cc0a4e3911c8e (patch)
parenta18fcc9ff22b714e7df30c400c05542f52830eb0 (diff)
path: add is_ntfs_dotgit() helper
We do not allow paths with a ".git" component to be added to the index, as that would mean repository contents could overwrite our repository files. However, asking "is this path the same as .git" is not as simple as strcmp() on some filesystems. On NTFS (and FAT32), there exist so-called "short names" for backwards-compatibility: 8.3 compliant names that refer to the same files as their long names. As ".git" is not an 8.3 compliant name, a short name is generated automatically, typically "git~1". Depending on the Windows version, any combination of trailing spaces and periods are ignored, too, so that both "git~1." and ".git." still refer to the Git directory. The reason is that 8.3 stores file names shorter than 8 characters with trailing spaces. So literally, it does not matter for the short name whether it is padded with spaces or whether it is shorter than 8 characters, it is considered to be the exact same. The period is the separator between file name and file extension, and again, an empty extension consists just of spaces in 8.3 format. So technically, we would need only take care of the equivalent of this regex: (\.git {0,4}|git~1 {0,3})\. {0,3} However, there are indications that at least some Windows versions might be more lenient and accept arbitrary combinations of trailing spaces and periods and strip them out. So we're playing it real safe here. Besides, there can be little doubt about the intention behind using file names matching even the more lenient pattern specified above, therefore we should be fine with disallowing such patterns. Extra care is taken to catch names such as '.\\.git\\booh' because the backslash is marked as a directory separator only on Windows, and we want to use this new helper function also in fsck on other platforms. A big thank you goes to Ed Thomson and an unnamed Microsoft engineer for the detailed analysis performed to come up with the corresponding fixes for libgit2. This commit adds a function to detect whether a given file name can refer to the Git directory by mistake. Signed-off-by: Johannes Schindelin <> Signed-off-by: Junio C Hamano <>
2 files changed, 34 insertions, 0 deletions
diff --git a/cache.h b/cache.h
index b600a0c..d17b1d6 100644
--- a/cache.h
+++ b/cache.h
@@ -759,6 +759,7 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
int offset_1st_component(const char *path);
+extern int is_ntfs_dotgit(const char *name);
/* object replacement */
diff --git a/path.c b/path.c
index 24594c4..4ef1b01 100644
--- a/path.c
+++ b/path.c
@@ -830,3 +830,36 @@ int offset_1st_component(const char *path)
return 2 + is_dir_sep(path[2]);
return is_dir_sep(path[0]);
+static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ if (len < skip)
+ return 0;
+ len -= skip;
+ path += skip;
+ while (len-- > 0) {
+ char c = *(path++);
+ if (c != ' ' && c != '.')
+ return 0;
+ }
+ return 1;
+int is_ntfs_dotgit(const char *name)
+ int len;
+ for (len = 0; ; len++)
+ if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
+ if (only_spaces_and_periods(name, len, 4) &&
+ !strncasecmp(name, ".git", 4))
+ return 1;
+ if (only_spaces_and_periods(name, len, 5) &&
+ !strncasecmp(name, "git~1", 5))
+ return 1;
+ if (name[len] != '\\')
+ return 0;
+ name += len + 1;
+ len = -1;
+ }