mirror of
https://github.com/git/git
synced 2024-08-24 18:26:02 +00:00
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
This commit is contained in:
commit
2aa9100846
|
@ -234,6 +234,17 @@ core.precomposeunicode::
|
|||
When false, file names are handled fully transparent by Git,
|
||||
which is backward compatible with older versions of Git.
|
||||
|
||||
core.protectHFS::
|
||||
If set to true, do not allow checkout of paths that would
|
||||
be considered equivalent to `.git` on an HFS+ filesystem.
|
||||
Defaults to `true` on Mac OS, and `false` elsewhere.
|
||||
|
||||
core.protectNTFS::
|
||||
If set to true, do not allow checkout of paths that would
|
||||
cause problems with the NTFS filesystem, e.g. conflict with
|
||||
8.3 "short" names.
|
||||
Defaults to `true` on Windows, and `false` elsewhere.
|
||||
|
||||
core.trustctime::
|
||||
If false, the ctime differences between the index and the
|
||||
working tree are ignored; useful when the inode change time
|
||||
|
|
3
cache.h
3
cache.h
|
@ -584,6 +584,8 @@ extern int fsync_object_files;
|
|||
extern int core_preload_index;
|
||||
extern int core_apply_sparse_checkout;
|
||||
extern int precomposed_unicode;
|
||||
extern int protect_hfs;
|
||||
extern int protect_ntfs;
|
||||
|
||||
/*
|
||||
* 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);
|
|||
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 */
|
||||
#define READ_SHA1_FILE_REPLACE 1
|
||||
|
|
10
config.c
10
config.c
|
@ -881,6 +881,16 @@ static int git_default_core_config(const char *var, const char *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.protecthfs")) {
|
||||
protect_hfs = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.protectntfs")) {
|
||||
protect_ntfs = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add other config variables here and to Documentation/config.txt. */
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ ifeq ($(uname_S),Darwin)
|
|||
HAVE_DEV_TTY = YesPlease
|
||||
COMPAT_OBJS += compat/precompose_utf8.o
|
||||
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
|
||||
BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1
|
||||
endif
|
||||
ifeq ($(uname_S),SunOS)
|
||||
NEEDS_SOCKET = YesPlease
|
||||
|
@ -361,6 +362,7 @@ ifeq ($(uname_S),Windows)
|
|||
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
|
||||
PTHREAD_LIBS =
|
||||
lib =
|
||||
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
|
||||
ifndef DEBUG
|
||||
BASIC_CFLAGS += -GL -Os -MT
|
||||
BASIC_LDFLAGS += -LTCG
|
||||
|
@ -505,6 +507,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
|
|||
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
|
||||
compat/win32/pthread.o compat/win32/syslog.o \
|
||||
compat/win32/dirent.o
|
||||
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
|
||||
BASIC_LDFLAGS += -Wl,--large-address-aware
|
||||
EXTLIBS += -lws2_32
|
||||
GITLIBS += git.res
|
||||
|
|
|
@ -63,6 +63,16 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
|
|||
struct startup_info *startup_info;
|
||||
unsigned long pack_size_limit_cfg;
|
||||
|
||||
#ifndef PROTECT_HFS_DEFAULT
|
||||
#define PROTECT_HFS_DEFAULT 0
|
||||
#endif
|
||||
int protect_hfs = PROTECT_HFS_DEFAULT;
|
||||
|
||||
#ifndef PROTECT_NTFS_DEFAULT
|
||||
#define PROTECT_NTFS_DEFAULT 0
|
||||
#endif
|
||||
int protect_ntfs = PROTECT_NTFS_DEFAULT;
|
||||
|
||||
/*
|
||||
* The character that begins a commented line in user-editable file
|
||||
* that is subject to stripspace.
|
||||
|
|
4
fsck.c
4
fsck.c
|
@ -6,6 +6,7 @@
|
|||
#include "commit.h"
|
||||
#include "tag.h"
|
||||
#include "fsck.h"
|
||||
#include "utf8.h"
|
||||
|
||||
static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
|
||||
{
|
||||
|
@ -175,7 +176,8 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
|
|||
has_dot = 1;
|
||||
if (!strcmp(name, ".."))
|
||||
has_dotdot = 1;
|
||||
if (!strcmp(name, ".git"))
|
||||
if (!strcasecmp(name, ".git") || is_hfs_dotgit(name) ||
|
||||
is_ntfs_dotgit(name))
|
||||
has_dotgit = 1;
|
||||
has_zero_pad |= *(char *)desc.buffer == '0';
|
||||
update_tree_entry(&desc);
|
||||
|
|
33
path.c
33
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;
|
||||
}
|
||||
}
|
||||
|
|
10
read-cache.c
10
read-cache.c
|
@ -14,6 +14,7 @@
|
|||
#include "resolve-undo.h"
|
||||
#include "strbuf.h"
|
||||
#include "varint.h"
|
||||
#include "utf8.h"
|
||||
|
||||
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
|
||||
|
||||
|
@ -759,9 +760,10 @@ static int verify_dotfile(const char *rest)
|
|||
* shares the path end test with the ".." case.
|
||||
*/
|
||||
case 'g':
|
||||
if (rest[1] != 'i')
|
||||
case 'G':
|
||||
if (rest[1] != 'i' && rest[1] != 'I')
|
||||
break;
|
||||
if (rest[2] != 't')
|
||||
if (rest[2] != 't' && rest[2] != 'T')
|
||||
break;
|
||||
rest += 2;
|
||||
/* fallthrough */
|
||||
|
@ -785,6 +787,10 @@ int verify_path(const char *path)
|
|||
return 1;
|
||||
if (is_dir_sep(c)) {
|
||||
inside:
|
||||
if (protect_hfs && is_hfs_dotgit(path))
|
||||
return 0;
|
||||
if (protect_ntfs && is_ntfs_dotgit(path))
|
||||
return 0;
|
||||
c = *path++;
|
||||
if ((c == '.' && !verify_dotfile(path)) ||
|
||||
is_dir_sep(c) || c == '\0')
|
||||
|
|
62
t/t1014-read-tree-confusing.sh
Executable file
62
t/t1014-read-tree-confusing.sh
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='check that read-tree rejects confusing paths'
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'create base tree' '
|
||||
echo content >file &&
|
||||
git add file &&
|
||||
git commit -m base &&
|
||||
blob=$(git rev-parse HEAD:file) &&
|
||||
tree=$(git rev-parse HEAD^{tree})
|
||||
'
|
||||
|
||||
test_expect_success 'enable core.protectHFS for rejection tests' '
|
||||
git config core.protectHFS true
|
||||
'
|
||||
|
||||
test_expect_success 'enable core.protectNTFS for rejection tests' '
|
||||
git config core.protectNTFS true
|
||||
'
|
||||
|
||||
while read path pretty; do
|
||||
: ${pretty:=$path}
|
||||
case "$path" in
|
||||
*SPACE)
|
||||
path="${path%SPACE} "
|
||||
;;
|
||||
esac
|
||||
test_expect_success "reject $pretty at end of path" '
|
||||
printf "100644 blob %s\t%s" "$blob" "$path" >tree &&
|
||||
bogus=$(git mktree <tree) &&
|
||||
test_must_fail git read-tree $bogus
|
||||
'
|
||||
|
||||
test_expect_success "reject $pretty as subtree" '
|
||||
printf "040000 tree %s\t%s" "$tree" "$path" >tree &&
|
||||
bogus=$(git mktree <tree) &&
|
||||
test_must_fail git read-tree $bogus
|
||||
'
|
||||
done <<-EOF
|
||||
.
|
||||
..
|
||||
.git
|
||||
.GIT
|
||||
${u200c}.Git {u200c}.Git
|
||||
.gI${u200c}T .gI{u200c}T
|
||||
.GiT${u200c} .GiT{u200c}
|
||||
git~1
|
||||
.git.SPACE .git.{space}
|
||||
.\\\\.GIT\\\\foobar backslashes
|
||||
.git\\\\foobar backslashes2
|
||||
EOF
|
||||
|
||||
test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
|
||||
test_when_finished "git read-tree HEAD" &&
|
||||
test_config core.protectHFS false &&
|
||||
printf "100644 blob %s\t%s" "$blob" ".gi${u200c}t" >tree &&
|
||||
ok=$(git mktree <tree) &&
|
||||
git read-tree $ok
|
||||
'
|
||||
|
||||
test_done
|
|
@ -237,35 +237,40 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success 'fsck notices "." and ".." in trees' '
|
||||
(
|
||||
git init dots &&
|
||||
cd dots &&
|
||||
blob=$(echo foo | git hash-object -w --stdin) &&
|
||||
tab=$(printf "\\t") &&
|
||||
git mktree <<-EOF &&
|
||||
100644 blob $blob$tab.
|
||||
100644 blob $blob$tab..
|
||||
EOF
|
||||
git fsck 2>out &&
|
||||
cat out &&
|
||||
grep "warning.*\\." out
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'fsck notices ".git" in trees' '
|
||||
(
|
||||
git init dotgit &&
|
||||
cd dotgit &&
|
||||
blob=$(echo foo | git hash-object -w --stdin) &&
|
||||
tab=$(printf "\\t") &&
|
||||
git mktree <<-EOF &&
|
||||
100644 blob $blob$tab.git
|
||||
EOF
|
||||
git fsck 2>out &&
|
||||
cat out &&
|
||||
grep "warning.*\\.git" out
|
||||
)
|
||||
'
|
||||
while read name path pretty; do
|
||||
while read mode type; do
|
||||
: ${pretty:=$path}
|
||||
test_expect_success "fsck notices $pretty as $type" '
|
||||
(
|
||||
git init $name-$type &&
|
||||
cd $name-$type &&
|
||||
echo content >file &&
|
||||
git add file &&
|
||||
git commit -m base &&
|
||||
blob=$(git rev-parse :file) &&
|
||||
tree=$(git rev-parse HEAD^{tree}) &&
|
||||
value=$(eval "echo \$$type") &&
|
||||
printf "$mode $type %s\t%s" "$value" "$path" >bad &&
|
||||
bad_tree=$(git mktree <bad) &&
|
||||
git fsck 2>out &&
|
||||
cat out &&
|
||||
grep "warning.*tree $bad_tree" out
|
||||
)'
|
||||
done <<-\EOF
|
||||
100644 blob
|
||||
040000 tree
|
||||
EOF
|
||||
done <<-EOF
|
||||
dot .
|
||||
dotdot ..
|
||||
dotgit .git
|
||||
dotgit-case .GIT
|
||||
dotgit-unicode .gI${u200c}T .gI{u200c}T
|
||||
dotgit-case2 .Git
|
||||
git-tilde1 git~1
|
||||
dotgitdot .git.
|
||||
dot-backslash-case .\\\\.GIT\\\\foobar
|
||||
dotgit-case-backslash .git\\\\foobar
|
||||
EOF
|
||||
|
||||
test_done
|
||||
|
|
|
@ -154,7 +154,11 @@ _z40=0000000000000000000000000000000000000000
|
|||
LF='
|
||||
'
|
||||
|
||||
export _x05 _x40 _z40 LF
|
||||
# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores
|
||||
# when case-folding filenames
|
||||
u200c=$(printf '\342\200\214')
|
||||
|
||||
export _x05 _x40 _z40 LF u200c
|
||||
|
||||
# Each test should start with something like this, after copyright notices:
|
||||
#
|
||||
|
|
|
@ -102,7 +102,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
|
|||
opts->unpack_rejects[i].strdup_strings = 1;
|
||||
}
|
||||
|
||||
static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
||||
static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
||||
unsigned int set, unsigned int clear)
|
||||
{
|
||||
clear |= CE_HASHED | CE_UNHASHED;
|
||||
|
@ -112,8 +112,8 @@ static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
|
|||
|
||||
ce->next = NULL;
|
||||
ce->ce_flags = (ce->ce_flags & ~clear) | set;
|
||||
add_index_entry(&o->result, ce,
|
||||
ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
|
||||
return add_index_entry(&o->result, ce,
|
||||
ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
|
||||
}
|
||||
|
||||
static struct cache_entry *dup_entry(const struct cache_entry *ce)
|
||||
|
@ -608,7 +608,9 @@ static int unpack_nondirectories(int n, unsigned long mask,
|
|||
|
||||
for (i = 0; i < n; i++)
|
||||
if (src[i] && src[i] != o->df_conflict_entry)
|
||||
do_add_entry(o, src[i], 0, 0);
|
||||
if (do_add_entry(o, src[i], 0, 0))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
64
utf8.c
64
utf8.c
|
@ -628,3 +628,67 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
|
|||
|
||||
return chrlen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pick the next char from the stream, folding as an HFS+ filename comparison
|
||||
* would. Note that this is _not_ complete by any means. It's just enough
|
||||
* to make is_hfs_dotgit() work, and should not be used otherwise.
|
||||
*/
|
||||
static ucs_char_t next_hfs_char(const char **in)
|
||||
{
|
||||
while (1) {
|
||||
ucs_char_t out = pick_one_utf8_char(in, NULL);
|
||||
/*
|
||||
* check for malformed utf8. Technically this
|
||||
* gets converted to a percent-sequence, but
|
||||
* returning 0 is good enough for is_hfs_dotgit
|
||||
* to realize it cannot be .git
|
||||
*/
|
||||
if (!*in)
|
||||
return 0;
|
||||
|
||||
/* these code points are ignored completely */
|
||||
switch (out) {
|
||||
case 0x200c: /* ZERO WIDTH NON-JOINER */
|
||||
case 0x200d: /* ZERO WIDTH JOINER */
|
||||
case 0x200e: /* LEFT-TO-RIGHT MARK */
|
||||
case 0x200f: /* RIGHT-TO-LEFT MARK */
|
||||
case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
|
||||
case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
|
||||
case 0x202c: /* POP DIRECTIONAL FORMATTING */
|
||||
case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
|
||||
case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
|
||||
case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
|
||||
case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
|
||||
case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
|
||||
case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
|
||||
case 0x206e: /* NATIONAL DIGIT SHAPES */
|
||||
case 0x206f: /* NOMINAL DIGIT SHAPES */
|
||||
case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* there's a great deal of other case-folding that occurs,
|
||||
* but this is enough to catch anything that will convert
|
||||
* to ".git"
|
||||
*/
|
||||
return tolower(out);
|
||||
}
|
||||
}
|
||||
|
||||
int is_hfs_dotgit(const char *path)
|
||||
{
|
||||
ucs_char_t c;
|
||||
|
||||
if (next_hfs_char(&path) != '.' ||
|
||||
next_hfs_char(&path) != 'g' ||
|
||||
next_hfs_char(&path) != 'i' ||
|
||||
next_hfs_char(&path) != 't')
|
||||
return 0;
|
||||
c = next_hfs_char(&path);
|
||||
if (c && !is_dir_sep(c))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
8
utf8.h
8
utf8.h
|
@ -42,4 +42,12 @@ static inline char *reencode_string(const char *in,
|
|||
|
||||
int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
|
||||
|
||||
/*
|
||||
* Returns true if the the path would match ".git" after HFS case-folding.
|
||||
* The path should be NUL-terminated, but we will match variants of both ".git\0"
|
||||
* and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
|
||||
* and verify_path().
|
||||
*/
|
||||
int is_hfs_dotgit(const char *path);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue