|
 |
e5a527 |
diff --git a/Documentation/config.txt b/Documentation/config.txt
|
|
 |
e5a527 |
index c26a7c8..7076aa9 100644
|
|
 |
e5a527 |
--- a/Documentation/config.txt
|
|
 |
e5a527 |
+++ b/Documentation/config.txt
|
|
 |
e5a527 |
@@ -234,6 +234,17 @@ core.precomposeunicode::
|
|
 |
e5a527 |
When false, file names are handled fully transparent by Git,
|
|
 |
e5a527 |
which is backward compatible with older versions of Git.
|
|
 |
e5a527 |
|
|
 |
e5a527 |
+core.protectHFS::
|
|
 |
e5a527 |
+ If set to true, do not allow checkout of paths that would
|
|
 |
e5a527 |
+ be considered equivalent to `.git` on an HFS+ filesystem.
|
|
 |
e5a527 |
+ Defaults to `true` on Mac OS, and `false` elsewhere.
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+core.protectNTFS::
|
|
 |
e5a527 |
+ If set to true, do not allow checkout of paths that would
|
|
 |
e5a527 |
+ cause problems with the NTFS filesystem, e.g. conflict with
|
|
 |
e5a527 |
+ 8.3 "short" names.
|
|
 |
e5a527 |
+ Defaults to `true` on Windows, and `false` elsewhere.
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
core.trustctime::
|
|
 |
e5a527 |
If false, the ctime differences between the index and the
|
|
 |
e5a527 |
working tree are ignored; useful when the inode change time
|
|
 |
e5a527 |
--- a/builtin/annotate.c
|
|
 |
e5a527 |
+++ b/builtin/annotate.c
|
|
 |
e5a527 |
@@ -5,20 +5,18 @@
|
|
 |
e5a527 |
*/
|
|
 |
e5a527 |
#include "git-compat-util.h"
|
|
 |
e5a527 |
#include "builtin.h"
|
|
 |
e5a527 |
+#include "argv-array.h"
|
|
 |
e5a527 |
|
|
 |
e5a527 |
int cmd_annotate(int argc, const char **argv, const char *prefix)
|
|
 |
e5a527 |
{
|
|
 |
e5a527 |
- const char **nargv;
|
|
 |
e5a527 |
+ struct argv_array args = ARGV_ARRAY_INIT;
|
|
 |
e5a527 |
int i;
|
|
 |
e5a527 |
- nargv = xmalloc(sizeof(char *) * (argc + 2));
|
|
 |
e5a527 |
|
|
 |
e5a527 |
- nargv[0] = "annotate";
|
|
 |
e5a527 |
- nargv[1] = "-c";
|
|
 |
e5a527 |
+ argv_array_pushl(&args, "annotate", "-c", NULL);
|
|
 |
e5a527 |
|
|
 |
e5a527 |
for (i = 1; i < argc; i++) {
|
|
 |
e5a527 |
- nargv[i+1] = argv[i];
|
|
 |
e5a527 |
+ argv_array_push(&args, argv[i]);
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
- nargv[argc + 1] = NULL;
|
|
 |
e5a527 |
|
|
 |
e5a527 |
- return cmd_blame(argc + 1, nargv, prefix);
|
|
 |
e5a527 |
+ return cmd_blame(args.argc, args.argv, prefix);
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
diff --git a/builtin/clean.c b/builtin/clean.c
|
|
 |
e5a527 |
index 977a068..8926ca8 100644
|
|
 |
e5a527 |
--- a/builtin/clean.c
|
|
 |
e5a527 |
+++ b/builtin/clean.c
|
|
 |
e5a527 |
@@ -48,7 +48,7 @@ enum color_clean {
|
|
 |
e5a527 |
CLEAN_COLOR_PROMPT = 2,
|
|
 |
e5a527 |
CLEAN_COLOR_HEADER = 3,
|
|
 |
e5a527 |
CLEAN_COLOR_HELP = 4,
|
|
 |
e5a527 |
- CLEAN_COLOR_ERROR = 5,
|
|
 |
e5a527 |
+ CLEAN_COLOR_ERROR = 5
|
|
 |
e5a527 |
};
|
|
 |
e5a527 |
|
|
 |
e5a527 |
#define MENU_OPTS_SINGLETON 01
|
|
 |
e5a527 |
diff --git a/cache.h b/cache.h
|
|
 |
e5a527 |
index ebe9a40..017c487 100644
|
|
 |
e5a527 |
--- a/cache.h
|
|
 |
e5a527 |
+++ b/cache.h
|
|
 |
e5a527 |
@@ -587,6 +587,8 @@ extern int fsync_object_files;
|
|
 |
e5a527 |
extern int core_preload_index;
|
|
 |
e5a527 |
extern int core_apply_sparse_checkout;
|
|
 |
e5a527 |
extern int precomposed_unicode;
|
|
 |
e5a527 |
+extern int protect_hfs;
|
|
 |
e5a527 |
+extern int protect_ntfs;
|
|
 |
e5a527 |
|
|
 |
e5a527 |
/*
|
|
 |
e5a527 |
* The character that begins a commented line in user-editable file
|
|
 |
e5a527 |
@@ -782,6 +784,7 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes);
|
|
 |
e5a527 |
char *strip_path_suffix(const char *path, const char *suffix);
|
|
 |
e5a527 |
int daemon_avoid_alias(const char *path);
|
|
 |
e5a527 |
int offset_1st_component(const char *path);
|
|
 |
e5a527 |
+extern int is_ntfs_dotgit(const char *name);
|
|
 |
e5a527 |
|
|
 |
e5a527 |
/* object replacement */
|
|
 |
e5a527 |
#define LOOKUP_REPLACE_OBJECT 1
|
|
 |
e5a527 |
diff --git a/config.c b/config.c
|
|
 |
e5a527 |
index 314d8ee..8984ad2 100644
|
|
 |
e5a527 |
--- a/config.c
|
|
 |
e5a527 |
+++ b/config.c
|
|
 |
e5a527 |
@@ -885,6 +885,16 @@ static int git_default_core_config(const char *var, const char *value)
|
|
 |
e5a527 |
return 0;
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
|
|
 |
e5a527 |
+ if (!strcmp(var, "core.protecthfs")) {
|
|
 |
e5a527 |
+ protect_hfs = git_config_bool(var, value);
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ if (!strcmp(var, "core.protectntfs")) {
|
|
 |
e5a527 |
+ protect_ntfs = git_config_bool(var, value);
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
/* Add other config variables here and to Documentation/config.txt. */
|
|
 |
e5a527 |
return 0;
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
diff --git a/config.mak.uname b/config.mak.uname
|
|
 |
e5a527 |
index efaed94..f3cdcbc 100644
|
|
 |
e5a527 |
--- a/config.mak.uname
|
|
 |
e5a527 |
+++ b/config.mak.uname
|
|
 |
e5a527 |
@@ -97,6 +97,7 @@ ifeq ($(uname_S),Darwin)
|
|
 |
e5a527 |
HAVE_DEV_TTY = YesPlease
|
|
 |
e5a527 |
COMPAT_OBJS += compat/precompose_utf8.o
|
|
 |
e5a527 |
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
|
|
 |
e5a527 |
+ BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1
|
|
 |
e5a527 |
endif
|
|
 |
e5a527 |
ifeq ($(uname_S),SunOS)
|
|
 |
e5a527 |
NEEDS_SOCKET = YesPlease
|
|
 |
e5a527 |
@@ -369,6 +370,7 @@ ifeq ($(uname_S),Windows)
|
|
 |
e5a527 |
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
|
|
 |
e5a527 |
PTHREAD_LIBS =
|
|
 |
e5a527 |
lib =
|
|
 |
e5a527 |
+ BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
|
|
 |
e5a527 |
ifndef DEBUG
|
|
 |
e5a527 |
BASIC_CFLAGS += -GL -Os -MT
|
|
 |
e5a527 |
BASIC_LDFLAGS += -LTCG
|
|
 |
e5a527 |
@@ -513,6 +515,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
|
|
 |
e5a527 |
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
|
|
 |
e5a527 |
compat/win32/pthread.o compat/win32/syslog.o \
|
|
 |
e5a527 |
compat/win32/dirent.o
|
|
 |
e5a527 |
+ BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
|
|
 |
e5a527 |
BASIC_LDFLAGS += -Wl,--large-address-aware
|
|
 |
e5a527 |
EXTLIBS += -lws2_32
|
|
 |
e5a527 |
GITLIBS += git.res
|
|
 |
e5a527 |
diff --git a/environment.c b/environment.c
|
|
 |
e5a527 |
index 4a3437d..39a8c6c 100644
|
|
 |
e5a527 |
--- a/environment.c
|
|
 |
e5a527 |
+++ b/environment.c
|
|
 |
e5a527 |
@@ -64,6 +64,16 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
|
|
 |
e5a527 |
struct startup_info *startup_info;
|
|
 |
e5a527 |
unsigned long pack_size_limit_cfg;
|
|
 |
e5a527 |
|
|
 |
e5a527 |
+#ifndef PROTECT_HFS_DEFAULT
|
|
 |
e5a527 |
+#define PROTECT_HFS_DEFAULT 0
|
|
 |
e5a527 |
+#endif
|
|
 |
e5a527 |
+int protect_hfs = PROTECT_HFS_DEFAULT;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+#ifndef PROTECT_NTFS_DEFAULT
|
|
 |
e5a527 |
+#define PROTECT_NTFS_DEFAULT 0
|
|
 |
e5a527 |
+#endif
|
|
 |
e5a527 |
+int protect_ntfs = PROTECT_NTFS_DEFAULT;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
/*
|
|
 |
e5a527 |
* The character that begins a commented line in user-editable file
|
|
 |
e5a527 |
* that is subject to stripspace.
|
|
 |
e5a527 |
diff --git a/fsck.c b/fsck.c
|
|
 |
e5a527 |
index 64bf279..ee7f531 100644
|
|
 |
e5a527 |
--- a/fsck.c
|
|
 |
e5a527 |
+++ b/fsck.c
|
|
 |
e5a527 |
@@ -6,6 +6,7 @@
|
|
 |
e5a527 |
#include "commit.h"
|
|
 |
e5a527 |
#include "tag.h"
|
|
 |
e5a527 |
#include "fsck.h"
|
|
 |
e5a527 |
+#include "utf8.h"
|
|
 |
e5a527 |
|
|
 |
e5a527 |
static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
|
|
 |
e5a527 |
{
|
|
 |
e5a527 |
@@ -175,7 +176,8 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
|
|
 |
e5a527 |
has_dot = 1;
|
|
 |
e5a527 |
if (!strcmp(name, ".."))
|
|
 |
e5a527 |
has_dotdot = 1;
|
|
 |
e5a527 |
- if (!strcmp(name, ".git"))
|
|
 |
e5a527 |
+ if (!strcasecmp(name, ".git") || is_hfs_dotgit(name) ||
|
|
 |
e5a527 |
+ is_ntfs_dotgit(name))
|
|
 |
e5a527 |
has_dotgit = 1;
|
|
 |
e5a527 |
has_zero_pad |= *(char *)desc.buffer == '0';
|
|
 |
e5a527 |
update_tree_entry(&desc);
|
|
 |
e5a527 |
diff --git a/path.c b/path.c
|
|
 |
e5a527 |
index f9c5062..dfd58f4 100644
|
|
 |
e5a527 |
--- a/path.c
|
|
 |
e5a527 |
+++ b/path.c
|
|
 |
e5a527 |
@@ -830,3 +830,36 @@ int offset_1st_component(const char *path)
|
|
 |
e5a527 |
return 2 + is_dir_sep(path[2]);
|
|
 |
e5a527 |
return is_dir_sep(path[0]);
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
|
|
 |
e5a527 |
+{
|
|
 |
e5a527 |
+ if (len < skip)
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ len -= skip;
|
|
 |
e5a527 |
+ path += skip;
|
|
 |
e5a527 |
+ while (len-- > 0) {
|
|
 |
e5a527 |
+ char c = *(path++);
|
|
 |
e5a527 |
+ if (c != ' ' && c != '.')
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+ return 1;
|
|
 |
e5a527 |
+}
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+int is_ntfs_dotgit(const char *name)
|
|
 |
e5a527 |
+{
|
|
 |
e5a527 |
+ int len;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ for (len = 0; ; len++)
|
|
 |
e5a527 |
+ if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
|
|
 |
e5a527 |
+ if (only_spaces_and_periods(name, len, 4) &&
|
|
 |
e5a527 |
+ !strncasecmp(name, ".git", 4))
|
|
 |
e5a527 |
+ return 1;
|
|
 |
e5a527 |
+ if (only_spaces_and_periods(name, len, 5) &&
|
|
 |
e5a527 |
+ !strncasecmp(name, "git~1", 5))
|
|
 |
e5a527 |
+ return 1;
|
|
 |
e5a527 |
+ if (name[len] != '\\')
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ name += len + 1;
|
|
 |
e5a527 |
+ len = -1;
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+}
|
|
 |
e5a527 |
diff --git a/pretty.c b/pretty.c
|
|
 |
e5a527 |
index 6e266dd..f64ff9a 100644
|
|
 |
e5a527 |
--- a/pretty.c
|
|
 |
e5a527 |
+++ b/pretty.c
|
|
 |
e5a527 |
@@ -274,7 +274,7 @@ static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
|
|
 |
e5a527 |
|
|
 |
e5a527 |
enum rfc2047_type {
|
|
 |
e5a527 |
RFC2047_SUBJECT,
|
|
 |
e5a527 |
- RFC2047_ADDRESS,
|
|
 |
e5a527 |
+ RFC2047_ADDRESS
|
|
 |
e5a527 |
};
|
|
 |
e5a527 |
|
|
 |
e5a527 |
static int is_rfc2047_special(char ch, enum rfc2047_type type)
|
|
 |
e5a527 |
diff --git a/read-cache.c b/read-cache.c
|
|
 |
e5a527 |
index 4b4effd..ee07cd6 100644
|
|
 |
e5a527 |
--- a/read-cache.c
|
|
 |
e5a527 |
+++ b/read-cache.c
|
|
 |
e5a527 |
@@ -14,6 +14,7 @@
|
|
 |
e5a527 |
#include "resolve-undo.h"
|
|
 |
e5a527 |
#include "strbuf.h"
|
|
 |
e5a527 |
#include "varint.h"
|
|
 |
e5a527 |
+#include "utf8.h"
|
|
 |
e5a527 |
|
|
 |
e5a527 |
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
|
|
 |
e5a527 |
unsigned int options);
|
|
 |
e5a527 |
@@ -752,9 +753,10 @@ static int verify_dotfile(const char *rest)
|
|
 |
e5a527 |
* shares the path end test with the ".." case.
|
|
 |
e5a527 |
*/
|
|
 |
e5a527 |
case 'g':
|
|
 |
e5a527 |
- if (rest[1] != 'i')
|
|
 |
e5a527 |
+ case 'G':
|
|
 |
e5a527 |
+ if (rest[1] != 'i' && rest[1] != 'I')
|
|
 |
e5a527 |
break;
|
|
 |
e5a527 |
- if (rest[2] != 't')
|
|
 |
e5a527 |
+ if (rest[2] != 't' && rest[2] != 'T')
|
|
 |
e5a527 |
break;
|
|
 |
e5a527 |
rest += 2;
|
|
 |
e5a527 |
/* fallthrough */
|
|
 |
e5a527 |
@@ -778,6 +780,10 @@ int verify_path(const char *path)
|
|
 |
e5a527 |
return 1;
|
|
 |
e5a527 |
if (is_dir_sep(c)) {
|
|
 |
e5a527 |
inside:
|
|
 |
e5a527 |
+ if (protect_hfs && is_hfs_dotgit(path))
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ if (protect_ntfs && is_ntfs_dotgit(path))
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
c = *path++;
|
|
 |
e5a527 |
if ((c == '.' && !verify_dotfile(path)) ||
|
|
 |
e5a527 |
is_dir_sep(c) || c == '\0')
|
|
 |
e5a527 |
diff --git a/t/t1014-read-tree-confusing.sh b/t/t1014-read-tree-confusing.sh
|
|
 |
e5a527 |
new file mode 100755
|
|
 |
e5a527 |
index 0000000..2f5a25d
|
|
 |
e5a527 |
--- /dev/null
|
|
 |
e5a527 |
+++ b/t/t1014-read-tree-confusing.sh
|
|
 |
e5a527 |
@@ -0,0 +1,62 @@
|
|
 |
e5a527 |
+#!/bin/sh
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_description='check that read-tree rejects confusing paths'
|
|
 |
e5a527 |
+. ./test-lib.sh
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_expect_success 'create base tree' '
|
|
 |
e5a527 |
+ echo content >file &&
|
|
 |
e5a527 |
+ git add file &&
|
|
 |
e5a527 |
+ git commit -m base &&
|
|
 |
e5a527 |
+ blob=$(git rev-parse HEAD:file) &&
|
|
 |
e5a527 |
+ tree=$(git rev-parse HEAD^{tree})
|
|
 |
e5a527 |
+'
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_expect_success 'enable core.protectHFS for rejection tests' '
|
|
 |
e5a527 |
+ git config core.protectHFS true
|
|
 |
e5a527 |
+'
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_expect_success 'enable core.protectNTFS for rejection tests' '
|
|
 |
e5a527 |
+ git config core.protectNTFS true
|
|
 |
e5a527 |
+'
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+while read path pretty; do
|
|
 |
e5a527 |
+ : ${pretty:=$path}
|
|
 |
e5a527 |
+ case "$path" in
|
|
 |
e5a527 |
+ *SPACE)
|
|
 |
e5a527 |
+ path="${path%SPACE} "
|
|
 |
e5a527 |
+ ;;
|
|
 |
e5a527 |
+ esac
|
|
 |
e5a527 |
+ test_expect_success "reject $pretty at end of path" '
|
|
 |
e5a527 |
+ printf "100644 blob %s\t%s" "$blob" "$path" >tree &&
|
|
 |
e5a527 |
+ bogus=$(git mktree
|
|
 |
e5a527 |
+ test_must_fail git read-tree $bogus
|
|
 |
e5a527 |
+ '
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ test_expect_success "reject $pretty as subtree" '
|
|
 |
e5a527 |
+ printf "040000 tree %s\t%s" "$tree" "$path" >tree &&
|
|
 |
e5a527 |
+ bogus=$(git mktree
|
|
 |
e5a527 |
+ test_must_fail git read-tree $bogus
|
|
 |
e5a527 |
+ '
|
|
 |
e5a527 |
+done <<-EOF
|
|
 |
e5a527 |
+.
|
|
 |
e5a527 |
+..
|
|
 |
e5a527 |
+.git
|
|
 |
e5a527 |
+.GIT
|
|
 |
e5a527 |
+${u200c}.Git {u200c}.Git
|
|
 |
e5a527 |
+.gI${u200c}T .gI{u200c}T
|
|
 |
e5a527 |
+.GiT${u200c} .GiT{u200c}
|
|
 |
e5a527 |
+git~1
|
|
 |
e5a527 |
+.git.SPACE .git.{space}
|
|
 |
e5a527 |
+.\\\\.GIT\\\\foobar backslashes
|
|
 |
e5a527 |
+.git\\\\foobar backslashes2
|
|
 |
e5a527 |
+EOF
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
|
|
 |
e5a527 |
+ test_when_finished "git read-tree HEAD" &&
|
|
 |
e5a527 |
+ test_config core.protectHFS false &&
|
|
 |
e5a527 |
+ printf "100644 blob %s\t%s" "$blob" ".gi${u200c}t" >tree &&
|
|
 |
e5a527 |
+ ok=$(git mktree
|
|
 |
e5a527 |
+ git read-tree $ok
|
|
 |
e5a527 |
+'
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+test_done
|
|
 |
e5a527 |
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
|
|
 |
e5a527 |
index 8c739c9..983568a 100755
|
|
 |
e5a527 |
--- a/t/t1450-fsck.sh
|
|
 |
e5a527 |
+++ b/t/t1450-fsck.sh
|
|
 |
e5a527 |
@@ -251,35 +251,40 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
|
|
 |
e5a527 |
)
|
|
 |
e5a527 |
'
|
|
 |
e5a527 |
|
|
 |
e5a527 |
-test_expect_success 'fsck notices "." and ".." in trees' '
|
|
 |
e5a527 |
- (
|
|
 |
e5a527 |
- git init dots &&
|
|
 |
e5a527 |
- cd dots &&
|
|
 |
e5a527 |
- blob=$(echo foo | git hash-object -w --stdin) &&
|
|
 |
e5a527 |
- tab=$(printf "\\t") &&
|
|
 |
e5a527 |
- git mktree <<-EOF &&
|
|
 |
e5a527 |
- 100644 blob $blob$tab.
|
|
 |
e5a527 |
- 100644 blob $blob$tab..
|
|
 |
e5a527 |
- EOF
|
|
 |
e5a527 |
- git fsck 2>out &&
|
|
 |
e5a527 |
- cat out &&
|
|
 |
e5a527 |
- grep "warning.*\\." out
|
|
 |
e5a527 |
- )
|
|
 |
e5a527 |
-'
|
|
 |
e5a527 |
-
|
|
 |
e5a527 |
-test_expect_success 'fsck notices ".git" in trees' '
|
|
 |
e5a527 |
- (
|
|
 |
e5a527 |
- git init dotgit &&
|
|
 |
e5a527 |
- cd dotgit &&
|
|
 |
e5a527 |
- blob=$(echo foo | git hash-object -w --stdin) &&
|
|
 |
e5a527 |
- tab=$(printf "\\t") &&
|
|
 |
e5a527 |
- git mktree <<-EOF &&
|
|
 |
e5a527 |
- 100644 blob $blob$tab.git
|
|
 |
e5a527 |
- EOF
|
|
 |
e5a527 |
- git fsck 2>out &&
|
|
 |
e5a527 |
- cat out &&
|
|
 |
e5a527 |
- grep "warning.*\\.git" out
|
|
 |
e5a527 |
- )
|
|
 |
e5a527 |
-'
|
|
 |
e5a527 |
+while read name path pretty; do
|
|
 |
e5a527 |
+ while read mode type; do
|
|
 |
e5a527 |
+ : ${pretty:=$path}
|
|
 |
e5a527 |
+ test_expect_success "fsck notices $pretty as $type" '
|
|
 |
e5a527 |
+ (
|
|
 |
e5a527 |
+ git init $name-$type &&
|
|
 |
e5a527 |
+ cd $name-$type &&
|
|
 |
e5a527 |
+ echo content >file &&
|
|
 |
e5a527 |
+ git add file &&
|
|
 |
e5a527 |
+ git commit -m base &&
|
|
 |
e5a527 |
+ blob=$(git rev-parse :file) &&
|
|
 |
e5a527 |
+ tree=$(git rev-parse HEAD^{tree}) &&
|
|
 |
e5a527 |
+ value=$(eval "echo \$$type") &&
|
|
 |
e5a527 |
+ printf "$mode $type %s\t%s" "$value" "$path" >bad &&
|
|
 |
e5a527 |
+ bad_tree=$(git mktree
|
|
 |
e5a527 |
+ git fsck 2>out &&
|
|
 |
e5a527 |
+ cat out &&
|
|
 |
e5a527 |
+ grep "warning.*tree $bad_tree" out
|
|
 |
e5a527 |
+ )'
|
|
 |
e5a527 |
+ done <<-\EOF
|
|
 |
e5a527 |
+ 100644 blob
|
|
 |
e5a527 |
+ 040000 tree
|
|
 |
e5a527 |
+ EOF
|
|
 |
e5a527 |
+done <<-EOF
|
|
 |
e5a527 |
+dot .
|
|
 |
e5a527 |
+dotdot ..
|
|
 |
e5a527 |
+dotgit .git
|
|
 |
e5a527 |
+dotgit-case .GIT
|
|
 |
e5a527 |
+dotgit-unicode .gI${u200c}T .gI{u200c}T
|
|
 |
e5a527 |
+dotgit-case2 .Git
|
|
 |
e5a527 |
+git-tilde1 git~1
|
|
 |
e5a527 |
+dotgitdot .git.
|
|
 |
e5a527 |
+dot-backslash-case .\\\\.GIT\\\\foobar
|
|
 |
e5a527 |
+dotgit-case-backslash .git\\\\foobar
|
|
 |
e5a527 |
+EOF
|
|
 |
e5a527 |
|
|
 |
e5a527 |
test_done
|
|
 |
e5a527 |
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
|
|
 |
e5a527 |
index 74de814..04118ad 100755
|
|
 |
e5a527 |
--- a/t/t7300-clean.sh
|
|
 |
e5a527 |
+++ b/t/t7300-clean.sh
|
|
 |
e5a527 |
@@ -426,10 +426,10 @@ test_expect_success SANITY 'removal failure' '
|
|
 |
e5a527 |
|
|
 |
e5a527 |
mkdir foo &&
|
|
 |
e5a527 |
touch foo/bar &&
|
|
 |
e5a527 |
+ test_when_finished "chmod 755 foo" &&
|
|
 |
e5a527 |
(exec
|
|
 |
e5a527 |
chmod 0 foo &&
|
|
 |
e5a527 |
- test_must_fail git clean -f -d &&
|
|
 |
e5a527 |
- chmod 755 foo)
|
|
 |
e5a527 |
+ test_must_fail git clean -f -d)
|
|
 |
e5a527 |
'
|
|
 |
e5a527 |
|
|
 |
e5a527 |
test_expect_success 'nested git work tree' '
|
|
 |
e5a527 |
diff --git a/t/test-lib.sh b/t/test-lib.sh
|
|
 |
e5a527 |
index 3c7cb1d..afa411e 100644
|
|
 |
e5a527 |
--- a/t/test-lib.sh
|
|
 |
e5a527 |
+++ b/t/test-lib.sh
|
|
 |
e5a527 |
@@ -158,7 +158,11 @@ _z40=0000000000000000000000000000000000000000
|
|
 |
e5a527 |
LF='
|
|
 |
e5a527 |
'
|
|
 |
e5a527 |
|
|
 |
e5a527 |
-export _x05 _x40 _z40 LF
|
|
 |
e5a527 |
+# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores
|
|
 |
e5a527 |
+# when case-folding filenames
|
|
 |
e5a527 |
+u200c=$(printf '\342\200\214')
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+export _x05 _x40 _z40 LF u200c
|
|
 |
e5a527 |
|
|
 |
e5a527 |
# Each test should start with something like this, after copyright notices:
|
|
 |
e5a527 |
#
|
|
 |
e5a527 |
diff --git a/unpack-trees.c b/unpack-trees.c
|
|
 |
e5a527 |
index 164354d..ca7dd0f 100644
|
|
 |
e5a527 |
--- a/unpack-trees.c
|
|
 |
e5a527 |
+++ b/unpack-trees.c
|
|
 |
e5a527 |
@@ -102,7 +102,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
|
|
 |
e5a527 |
opts->unpack_rejects[i].strdup_strings = 1;
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
|
|
 |
e5a527 |
-static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
|
 |
e5a527 |
+static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
|
 |
e5a527 |
unsigned int set, unsigned int clear)
|
|
 |
e5a527 |
{
|
|
 |
e5a527 |
clear |= CE_HASHED | CE_UNHASHED;
|
|
 |
e5a527 |
@@ -112,8 +112,8 @@ static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
|
 |
e5a527 |
|
|
 |
e5a527 |
ce->next = NULL;
|
|
 |
e5a527 |
ce->ce_flags = (ce->ce_flags & ~clear) | set;
|
|
 |
e5a527 |
- add_index_entry(&o->result, ce,
|
|
 |
e5a527 |
- ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
|
|
 |
e5a527 |
+ return add_index_entry(&o->result, ce,
|
|
 |
e5a527 |
+ ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
|
|
 |
e5a527 |
static struct cache_entry *dup_entry(const struct cache_entry *ce)
|
|
 |
e5a527 |
@@ -608,7 +608,9 @@ static int unpack_nondirectories(int n, unsigned long mask,
|
|
 |
e5a527 |
|
|
 |
e5a527 |
for (i = 0; i < n; i++)
|
|
 |
e5a527 |
if (src[i] && src[i] != o->df_conflict_entry)
|
|
 |
e5a527 |
- do_add_entry(o, src[i], 0, 0);
|
|
 |
e5a527 |
+ if (do_add_entry(o, src[i], 0, 0))
|
|
 |
e5a527 |
+ return -1;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
return 0;
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
|
|
 |
e5a527 |
diff --git a/utf8.c b/utf8.c
|
|
 |
e5a527 |
index 536a9c8..015c815 100644
|
|
 |
e5a527 |
--- a/utf8.c
|
|
 |
e5a527 |
+++ b/utf8.c
|
|
 |
e5a527 |
@@ -627,3 +627,67 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
|
|
 |
e5a527 |
|
|
 |
e5a527 |
return chrlen;
|
|
 |
e5a527 |
}
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+/*
|
|
 |
e5a527 |
+ * Pick the next char from the stream, folding as an HFS+ filename comparison
|
|
 |
e5a527 |
+ * would. Note that this is _not_ complete by any means. It's just enough
|
|
 |
e5a527 |
+ * to make is_hfs_dotgit() work, and should not be used otherwise.
|
|
 |
e5a527 |
+ */
|
|
 |
e5a527 |
+static ucs_char_t next_hfs_char(const char **in)
|
|
 |
e5a527 |
+{
|
|
 |
e5a527 |
+ while (1) {
|
|
 |
e5a527 |
+ ucs_char_t out = pick_one_utf8_char(in, NULL);
|
|
 |
e5a527 |
+ /*
|
|
 |
e5a527 |
+ * check for malformed utf8. Technically this
|
|
 |
e5a527 |
+ * gets converted to a percent-sequence, but
|
|
 |
e5a527 |
+ * returning 0 is good enough for is_hfs_dotgit
|
|
 |
e5a527 |
+ * to realize it cannot be .git
|
|
 |
e5a527 |
+ */
|
|
 |
e5a527 |
+ if (!*in)
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ /* these code points are ignored completely */
|
|
 |
e5a527 |
+ switch (out) {
|
|
 |
e5a527 |
+ case 0x200c: /* ZERO WIDTH NON-JOINER */
|
|
 |
e5a527 |
+ case 0x200d: /* ZERO WIDTH JOINER */
|
|
 |
e5a527 |
+ case 0x200e: /* LEFT-TO-RIGHT MARK */
|
|
 |
e5a527 |
+ case 0x200f: /* RIGHT-TO-LEFT MARK */
|
|
 |
e5a527 |
+ case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
|
|
 |
e5a527 |
+ case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
|
|
 |
e5a527 |
+ case 0x202c: /* POP DIRECTIONAL FORMATTING */
|
|
 |
e5a527 |
+ case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
|
|
 |
e5a527 |
+ case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
|
|
 |
e5a527 |
+ case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
|
|
 |
e5a527 |
+ case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
|
|
 |
e5a527 |
+ case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
|
|
 |
e5a527 |
+ case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
|
|
 |
e5a527 |
+ case 0x206e: /* NATIONAL DIGIT SHAPES */
|
|
 |
e5a527 |
+ case 0x206f: /* NOMINAL DIGIT SHAPES */
|
|
 |
e5a527 |
+ case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
|
|
 |
e5a527 |
+ continue;
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ /*
|
|
 |
e5a527 |
+ * there's a great deal of other case-folding that occurs,
|
|
 |
e5a527 |
+ * but this is enough to catch anything that will convert
|
|
 |
e5a527 |
+ * to ".git"
|
|
 |
e5a527 |
+ */
|
|
 |
e5a527 |
+ return tolower(out);
|
|
 |
e5a527 |
+ }
|
|
 |
e5a527 |
+}
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+int is_hfs_dotgit(const char *path)
|
|
 |
e5a527 |
+{
|
|
 |
e5a527 |
+ ucs_char_t c;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ if (next_hfs_char(&path) != '.' ||
|
|
 |
e5a527 |
+ next_hfs_char(&path) != 'g' ||
|
|
 |
e5a527 |
+ next_hfs_char(&path) != 'i' ||
|
|
 |
e5a527 |
+ next_hfs_char(&path) != 't')
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+ c = next_hfs_char(&path);
|
|
 |
e5a527 |
+ if (c && !is_dir_sep(c))
|
|
 |
e5a527 |
+ return 0;
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
+ return 1;
|
|
 |
e5a527 |
+}
|
|
 |
e5a527 |
diff --git a/utf8.h b/utf8.h
|
|
 |
e5a527 |
index 65d0e42..e4d9183 100644
|
|
 |
e5a527 |
--- a/utf8.h
|
|
 |
e5a527 |
+++ b/utf8.h
|
|
 |
e5a527 |
@@ -42,4 +42,12 @@ static inline char *reencode_string(const char *in,
|
|
 |
e5a527 |
|
|
 |
e5a527 |
int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
|
|
 |
e5a527 |
|
|
 |
e5a527 |
+/*
|
|
 |
e5a527 |
+ * Returns true if the the path would match ".git" after HFS case-folding.
|
|
 |
e5a527 |
+ * The path should be NUL-terminated, but we will match variants of both ".git\0"
|
|
 |
e5a527 |
+ * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
|
|
 |
e5a527 |
+ * and verify_path().
|
|
 |
e5a527 |
+ */
|
|
 |
e5a527 |
+int is_hfs_dotgit(const char *path);
|
|
 |
e5a527 |
+
|
|
 |
e5a527 |
#endif
|
|
 |
e5a527 |
|