зеркало из https://github.com/microsoft/git.git
Sync with 2.14.6
* maint-2.14: (28 commits) Git 2.14.6 mingw: handle `subst`-ed "DOS drives" mingw: refuse to access paths with trailing spaces or periods mingw: refuse to access paths with illegal characters unpack-trees: let merged_entry() pass through do_add_entry()'s errors quote-stress-test: offer to test quoting arguments for MSYS2 sh t6130/t9350: prepare for stringent Win32 path validation quote-stress-test: allow skipping some trials quote-stress-test: accept arguments to test via the command-line tests: add a helper to stress test argument quoting mingw: fix quoting of arguments Disallow dubiously-nested submodule git directories protect_ntfs: turn on NTFS protection by default path: also guard `.gitmodules` against NTFS Alternate Data Streams is_ntfs_dotgit(): speed it up mingw: disallow backslash characters in tree objects' file names path: safeguard `.git` against NTFS Alternate Streams Accesses clone --recurse-submodules: prevent name squatting on Windows is_ntfs_dotgit(): only verify the leading segment test-path-utils: offer to run a protectNTFS/protectHFS benchmark ...
This commit is contained in:
Коммит
d3ac8c3f27
|
@ -0,0 +1,54 @@
|
|||
Git v2.14.6 Release Notes
|
||||
=========================
|
||||
|
||||
This release addresses the security issues CVE-2019-1348,
|
||||
CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352,
|
||||
CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387.
|
||||
|
||||
Fixes since v2.14.5
|
||||
-------------------
|
||||
|
||||
* CVE-2019-1348:
|
||||
The --export-marks option of git fast-import is exposed also via
|
||||
the in-stream command feature export-marks=... and it allows
|
||||
overwriting arbitrary paths.
|
||||
|
||||
* CVE-2019-1349:
|
||||
When submodules are cloned recursively, under certain circumstances
|
||||
Git could be fooled into using the same Git directory twice. We now
|
||||
require the directory to be empty.
|
||||
|
||||
* CVE-2019-1350:
|
||||
Incorrect quoting of command-line arguments allowed remote code
|
||||
execution during a recursive clone in conjunction with SSH URLs.
|
||||
|
||||
* CVE-2019-1351:
|
||||
While the only permitted drive letters for physical drives on
|
||||
Windows are letters of the US-English alphabet, this restriction
|
||||
does not apply to virtual drives assigned via subst <letter>:
|
||||
<path>. Git mistook such paths for relative paths, allowing writing
|
||||
outside of the worktree while cloning.
|
||||
|
||||
* CVE-2019-1352:
|
||||
Git was unaware of NTFS Alternate Data Streams, allowing files
|
||||
inside the .git/ directory to be overwritten during a clone.
|
||||
|
||||
* CVE-2019-1353:
|
||||
When running Git in the Windows Subsystem for Linux (also known as
|
||||
"WSL") while accessing a working directory on a regular Windows
|
||||
drive, none of the NTFS protections were active.
|
||||
|
||||
* CVE-2019-1354:
|
||||
Filenames on Linux/Unix can contain backslashes. On Windows,
|
||||
backslashes are directory separators. Git did not use to refuse to
|
||||
write out tracked files with such filenames.
|
||||
|
||||
* CVE-2019-1387:
|
||||
Recursive clones are currently affected by a vulnerability that is
|
||||
caused by too-lax validation of submodule names, allowing very
|
||||
targeted attacks via remote code execution in recursive clones.
|
||||
|
||||
Credit for finding these vulnerabilities goes to Microsoft Security
|
||||
Response Center, in particular to Nicolas Joly. The `fast-import`
|
||||
fixes were provided by Jeff King, the other fixes by Johannes
|
||||
Schindelin with help from Garima Singh.
|
|
@ -50,6 +50,21 @@ OPTIONS
|
|||
memory used by fast-import during this run. Showing this output
|
||||
is currently the default, but can be disabled with --quiet.
|
||||
|
||||
--allow-unsafe-features::
|
||||
Many command-line options can be provided as part of the
|
||||
fast-import stream itself by using the `feature` or `option`
|
||||
commands. However, some of these options are unsafe (e.g.,
|
||||
allowing fast-import to access the filesystem outside of the
|
||||
repository). These options are disabled by default, but can be
|
||||
allowed by providing this option on the command line. This
|
||||
currently impacts only the `export-marks`, `import-marks`, and
|
||||
`import-marks-if-exists` feature commands.
|
||||
+
|
||||
Only enable this option if you trust the program generating the
|
||||
fast-import stream! This option is enabled automatically for
|
||||
remote-helpers that use the `import` capability, as they are
|
||||
already trusted to run their own code.
|
||||
|
||||
Options for Frontends
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -758,7 +758,7 @@ static int checkout(int submodule_progress)
|
|||
|
||||
if (!err && (option_recurse_submodules.nr > 0)) {
|
||||
struct argv_array args = ARGV_ARRAY_INIT;
|
||||
argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
|
||||
argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
|
||||
|
||||
if (option_shallow_submodules == 1)
|
||||
argv_array_push(&args, "--depth=1");
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "remote.h"
|
||||
#include "refs.h"
|
||||
#include "connect.h"
|
||||
#include "dir.h"
|
||||
|
||||
static char *get_default_remote(void)
|
||||
{
|
||||
|
@ -616,6 +617,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
char *p, *path = NULL, *sm_gitdir;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
struct string_list reference = STRING_LIST_INIT_NODUP;
|
||||
int require_init = 0;
|
||||
char *sm_alternate = NULL, *error_strategy = NULL;
|
||||
|
||||
struct option module_clone_options[] = {
|
||||
|
@ -640,6 +642,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
|
||||
OPT_BOOL(0, "progress", &progress,
|
||||
N_("force cloning progress")),
|
||||
OPT_BOOL(0, "require-init", &require_init,
|
||||
N_("disallow cloning into non-empty directory")),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
@ -667,6 +671,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
} else
|
||||
path = xstrdup(path);
|
||||
|
||||
if (validate_submodule_git_dir(sm_gitdir, name) < 0)
|
||||
die(_("refusing to create/use '%s' in another submodule's "
|
||||
"git dir"), sm_gitdir);
|
||||
|
||||
if (!file_exists(sm_gitdir)) {
|
||||
if (safe_create_leading_directories_const(sm_gitdir) < 0)
|
||||
die(_("could not create directory '%s'"), sm_gitdir);
|
||||
|
@ -678,6 +686,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
die(_("clone of '%s' into submodule path '%s' failed"),
|
||||
url, path);
|
||||
} else {
|
||||
if (require_init && !access(path, X_OK) && !is_empty_dir(path))
|
||||
die(_("directory not empty: '%s'"), path);
|
||||
if (safe_create_leading_directories_const(path) < 0)
|
||||
die(_("could not create directory '%s'"), path);
|
||||
strbuf_addf(&sb, "%s/index", sm_gitdir);
|
||||
|
@ -726,6 +736,7 @@ struct submodule_update_clone {
|
|||
int quiet;
|
||||
int recommend_shallow;
|
||||
struct string_list references;
|
||||
unsigned require_init;
|
||||
const char *depth;
|
||||
const char *recursive_prefix;
|
||||
const char *prefix;
|
||||
|
@ -741,7 +752,7 @@ struct submodule_update_clone {
|
|||
int failed_clones_nr, failed_clones_alloc;
|
||||
};
|
||||
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
|
||||
SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
|
||||
SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
|
||||
NULL, NULL, NULL, \
|
||||
STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
|
||||
|
||||
|
@ -860,6 +871,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
|
|||
argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
|
||||
if (suc->recommend_shallow && sub->recommend_shallow == 1)
|
||||
argv_array_push(&child->args, "--depth=1");
|
||||
if (suc->require_init)
|
||||
argv_array_push(&child->args, "--require-init");
|
||||
argv_array_pushl(&child->args, "--path", sub->path, NULL);
|
||||
argv_array_pushl(&child->args, "--name", sub->name, NULL);
|
||||
argv_array_pushl(&child->args, "--url", url, NULL);
|
||||
|
@ -1011,6 +1024,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
|
|||
OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
|
||||
OPT_BOOL(0, "progress", &suc.progress,
|
||||
N_("force cloning progress")),
|
||||
OPT_BOOL(0, "require-init", &suc.require_init,
|
||||
N_("disallow cloning into non-empty directory")),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
|
100
compat/mingw.c
100
compat/mingw.c
|
@ -333,6 +333,12 @@ int mingw_mkdir(const char *path, int mode)
|
|||
{
|
||||
int ret;
|
||||
wchar_t wpath[MAX_PATH];
|
||||
|
||||
if (!is_valid_win32_path(path)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (xutftowcs_path(wpath, path) < 0)
|
||||
return -1;
|
||||
ret = _wmkdir(wpath);
|
||||
|
@ -345,13 +351,18 @@ int mingw_open (const char *filename, int oflags, ...)
|
|||
{
|
||||
va_list args;
|
||||
unsigned mode;
|
||||
int fd;
|
||||
int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
|
||||
va_start(args, oflags);
|
||||
mode = va_arg(args, int);
|
||||
va_end(args);
|
||||
|
||||
if (!is_valid_win32_path(filename)) {
|
||||
errno = create ? EINVAL : ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
|
||||
|
@ -413,6 +424,11 @@ FILE *mingw_fopen (const char *filename, const char *otype)
|
|||
int hide = needs_hiding(filename);
|
||||
FILE *file;
|
||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||
if (!is_valid_win32_path(filename)) {
|
||||
int create = otype && strchr(otype, 'w');
|
||||
errno = create ? EINVAL : ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
if (xutftowcs_path(wfilename, filename) < 0 ||
|
||||
|
@ -435,6 +451,11 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
|
|||
int hide = needs_hiding(filename);
|
||||
FILE *file;
|
||||
wchar_t wfilename[MAX_PATH], wotype[4];
|
||||
if (!is_valid_win32_path(filename)) {
|
||||
int create = otype && strchr(otype, 'w');
|
||||
errno = create ? EINVAL : ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
if (filename && !strcmp(filename, "/dev/null"))
|
||||
filename = "nul";
|
||||
if (xutftowcs_path(wfilename, filename) < 0 ||
|
||||
|
@ -872,7 +893,7 @@ static const char *quote_arg(const char *arg)
|
|||
p++;
|
||||
len++;
|
||||
}
|
||||
if (*p == '"')
|
||||
if (*p == '"' || !*p)
|
||||
n += count*2 + 1;
|
||||
continue;
|
||||
}
|
||||
|
@ -894,16 +915,19 @@ static const char *quote_arg(const char *arg)
|
|||
count++;
|
||||
*d++ = *arg++;
|
||||
}
|
||||
if (*arg == '"') {
|
||||
if (*arg == '"' || !*arg) {
|
||||
while (count-- > 0)
|
||||
*d++ = '\\';
|
||||
/* don't escape the surrounding end quote */
|
||||
if (!*arg)
|
||||
break;
|
||||
*d++ = '\\';
|
||||
}
|
||||
}
|
||||
*d++ = *arg++;
|
||||
}
|
||||
*d++ = '"';
|
||||
*d++ = 0;
|
||||
*d++ = '\0';
|
||||
return q;
|
||||
}
|
||||
|
||||
|
@ -1965,6 +1989,30 @@ pid_t waitpid(pid_t pid, int *status, int options)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int mingw_has_dos_drive_prefix(const char *path)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Does it start with an ASCII letter (i.e. highest bit not set),
|
||||
* followed by a colon?
|
||||
*/
|
||||
if (!(0x80 & (unsigned char)*path))
|
||||
return *path && path[1] == ':' ? 2 : 0;
|
||||
|
||||
/*
|
||||
* While drive letters must be letters of the English alphabet, it is
|
||||
* possible to assign virtually _any_ Unicode character via `subst` as
|
||||
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
|
||||
* like this:
|
||||
*
|
||||
* subst ֍: %USERPROFILE%\Desktop
|
||||
*/
|
||||
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
|
||||
; /* skip first UTF-8 character */
|
||||
return path[i] == ':' ? i + 1 : 0;
|
||||
}
|
||||
|
||||
int mingw_skip_dos_drive_prefix(char **path)
|
||||
{
|
||||
int ret = has_dos_drive_prefix(*path);
|
||||
|
@ -2106,6 +2154,50 @@ static void setup_windows_environment(void)
|
|||
setenv("TERM", "cygwin", 1);
|
||||
}
|
||||
|
||||
int is_valid_win32_path(const char *path)
|
||||
{
|
||||
int preceding_space_or_period = 0, i = 0, periods = 0;
|
||||
|
||||
if (!protect_ntfs)
|
||||
return 1;
|
||||
|
||||
skip_dos_drive_prefix((char **)&path);
|
||||
|
||||
for (;;) {
|
||||
char c = *(path++);
|
||||
switch (c) {
|
||||
case '\0':
|
||||
case '/': case '\\':
|
||||
/* cannot end in ` ` or `.`, except for `.` and `..` */
|
||||
if (preceding_space_or_period &&
|
||||
(i != periods || periods > 2))
|
||||
return 0;
|
||||
if (!c)
|
||||
return 1;
|
||||
|
||||
i = periods = preceding_space_or_period = 0;
|
||||
continue;
|
||||
case '.':
|
||||
periods++;
|
||||
/* fallthru */
|
||||
case ' ':
|
||||
preceding_space_or_period = 1;
|
||||
i++;
|
||||
continue;
|
||||
case ':': /* DOS drive prefix was already skipped */
|
||||
case '<': case '>': case '"': case '|': case '?': case '*':
|
||||
/* illegal character */
|
||||
return 0;
|
||||
default:
|
||||
if (c > '\0' && c < '\x20')
|
||||
/* illegal character */
|
||||
return 0;
|
||||
}
|
||||
preceding_space_or_period = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
|
||||
* mingw startup code, see init.c in mingw runtime).
|
||||
|
|
|
@ -394,8 +394,8 @@ HANDLE winansi_get_osfhandle(int fd);
|
|||
* git specific compatibility
|
||||
*/
|
||||
|
||||
#define has_dos_drive_prefix(path) \
|
||||
(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
|
||||
int mingw_has_dos_drive_prefix(const char *path);
|
||||
#define has_dos_drive_prefix mingw_has_dos_drive_prefix
|
||||
int mingw_skip_dos_drive_prefix(char **path);
|
||||
#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
|
||||
static inline int mingw_is_dir_sep(int c)
|
||||
|
@ -428,6 +428,20 @@ int mingw_offset_1st_component(const char *path);
|
|||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Verifies that the given path is a valid one on Windows.
|
||||
*
|
||||
* In particular, path segments are disallowed which
|
||||
*
|
||||
* - end in a period or a space (except the special directories `.` and `..`).
|
||||
*
|
||||
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
|
||||
*
|
||||
* Returns 1 upon success, otherwise 0.
|
||||
*/
|
||||
int is_valid_win32_path(const char *path);
|
||||
#define is_valid_path(path) is_valid_win32_path(path)
|
||||
|
||||
/**
|
||||
* Converts UTF-8 encoded string to UTF-16LE.
|
||||
*
|
||||
|
|
|
@ -381,7 +381,6 @@ ifeq ($(uname_S),Windows)
|
|||
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj
|
||||
PTHREAD_LIBS =
|
||||
lib =
|
||||
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
|
||||
ifndef DEBUG
|
||||
BASIC_CFLAGS += -GL -Os -MD
|
||||
BASIC_LDFLAGS += -LTCG
|
||||
|
@ -519,7 +518,6 @@ 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
|
||||
EXTLIBS += -lws2_32
|
||||
GITLIBS += git.res
|
||||
PTHREAD_LIBS =
|
||||
|
|
|
@ -264,7 +264,7 @@ int url_is_local_not_ssh(const char *url)
|
|||
const char *colon = strchr(url, ':');
|
||||
const char *slash = strchr(url, '/');
|
||||
return !colon || (slash && slash < colon) ||
|
||||
has_dos_drive_prefix(url);
|
||||
(has_dos_drive_prefix(url) && is_valid_path(url));
|
||||
}
|
||||
|
||||
static const char *prot_name(enum protocol protocol)
|
||||
|
|
|
@ -73,7 +73,7 @@ enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET;
|
|||
int protect_hfs = PROTECT_HFS_DEFAULT;
|
||||
|
||||
#ifndef PROTECT_NTFS_DEFAULT
|
||||
#define PROTECT_NTFS_DEFAULT 0
|
||||
#define PROTECT_NTFS_DEFAULT 1
|
||||
#endif
|
||||
int protect_ntfs = PROTECT_NTFS_DEFAULT;
|
||||
|
||||
|
|
|
@ -367,6 +367,7 @@ static uintmax_t next_mark;
|
|||
static struct strbuf new_data = STRBUF_INIT;
|
||||
static int seen_data_command;
|
||||
static int require_explicit_termination;
|
||||
static int allow_unsafe_features;
|
||||
|
||||
/* Signal handling */
|
||||
static volatile sig_atomic_t checkpoint_requested;
|
||||
|
@ -1862,6 +1863,12 @@ static void dump_marks(void)
|
|||
if (!export_marks_file || (import_marks_file && !import_marks_file_done))
|
||||
return;
|
||||
|
||||
if (safe_create_leading_directories_const(export_marks_file)) {
|
||||
failure |= error_errno("unable to create leading directories of %s",
|
||||
export_marks_file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
|
||||
failure |= error_errno("Unable to write marks file %s",
|
||||
export_marks_file);
|
||||
|
@ -3229,7 +3236,6 @@ static void option_import_marks(const char *marks,
|
|||
}
|
||||
|
||||
import_marks_file = make_fast_import_path(marks);
|
||||
safe_create_leading_directories_const(import_marks_file);
|
||||
import_marks_file_from_stream = from_stream;
|
||||
import_marks_file_ignore_missing = ignore_missing;
|
||||
}
|
||||
|
@ -3270,7 +3276,6 @@ static void option_active_branches(const char *branches)
|
|||
static void option_export_marks(const char *marks)
|
||||
{
|
||||
export_marks_file = make_fast_import_path(marks);
|
||||
safe_create_leading_directories_const(export_marks_file);
|
||||
}
|
||||
|
||||
static void option_cat_blob_fd(const char *fd)
|
||||
|
@ -3313,10 +3318,12 @@ static int parse_one_option(const char *option)
|
|||
option_active_branches(option);
|
||||
} else if (skip_prefix(option, "export-pack-edges=", &option)) {
|
||||
option_export_pack_edges(option);
|
||||
} else if (starts_with(option, "quiet")) {
|
||||
} else if (!strcmp(option, "quiet")) {
|
||||
show_stats = 0;
|
||||
} else if (starts_with(option, "stats")) {
|
||||
} else if (!strcmp(option, "stats")) {
|
||||
show_stats = 1;
|
||||
} else if (!strcmp(option, "allow-unsafe-features")) {
|
||||
; /* already handled during early option parsing */
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
@ -3324,6 +3331,13 @@ static int parse_one_option(const char *option)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void check_unsafe_feature(const char *feature, int from_stream)
|
||||
{
|
||||
if (from_stream && !allow_unsafe_features)
|
||||
die(_("feature '%s' forbidden in input without --allow-unsafe-features"),
|
||||
feature);
|
||||
}
|
||||
|
||||
static int parse_one_feature(const char *feature, int from_stream)
|
||||
{
|
||||
const char *arg;
|
||||
|
@ -3331,10 +3345,13 @@ static int parse_one_feature(const char *feature, int from_stream)
|
|||
if (skip_prefix(feature, "date-format=", &arg)) {
|
||||
option_date_format(arg);
|
||||
} else if (skip_prefix(feature, "import-marks=", &arg)) {
|
||||
check_unsafe_feature("import-marks", from_stream);
|
||||
option_import_marks(arg, from_stream, 0);
|
||||
} else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
|
||||
check_unsafe_feature("import-marks-if-exists", from_stream);
|
||||
option_import_marks(arg, from_stream, 1);
|
||||
} else if (skip_prefix(feature, "export-marks=", &arg)) {
|
||||
check_unsafe_feature(feature, from_stream);
|
||||
option_export_marks(arg);
|
||||
} else if (!strcmp(feature, "get-mark")) {
|
||||
; /* Don't die - this feature is supported */
|
||||
|
@ -3461,6 +3478,20 @@ int cmd_main(int argc, const char **argv)
|
|||
avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
|
||||
marks = pool_calloc(1, sizeof(struct mark_set));
|
||||
|
||||
/*
|
||||
* We don't parse most options until after we've seen the set of
|
||||
* "feature" lines at the start of the stream (which allows the command
|
||||
* line to override stream data). But we must do an early parse of any
|
||||
* command-line options that impact how we interpret the feature lines.
|
||||
*/
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (*arg != '-' || !strcmp(arg, "--"))
|
||||
break;
|
||||
if (!strcmp(arg, "--allow-unsafe-features"))
|
||||
allow_unsafe_features = 1;
|
||||
}
|
||||
|
||||
global_argc = argc;
|
||||
global_argv = argv;
|
||||
|
||||
|
|
11
fsck.c
11
fsck.c
|
@ -551,7 +551,7 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
|
|||
|
||||
while (desc.size) {
|
||||
unsigned mode;
|
||||
const char *name;
|
||||
const char *name, *backslash;
|
||||
const struct object_id *oid;
|
||||
|
||||
oid = tree_entry_extract(&desc, &name, &mode);
|
||||
|
@ -565,6 +565,15 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
|
|||
is_hfs_dotgit(name) ||
|
||||
is_ntfs_dotgit(name));
|
||||
has_zero_pad |= *(char *)desc.buffer == '0';
|
||||
|
||||
if ((backslash = strchr(name, '\\'))) {
|
||||
while (backslash) {
|
||||
backslash++;
|
||||
has_dotgit |= is_ntfs_dotgit(backslash);
|
||||
backslash = strchr(backslash, '\\');
|
||||
}
|
||||
}
|
||||
|
||||
if (update_tree_entry_gently(&desc)) {
|
||||
retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
|
||||
break;
|
||||
|
|
|
@ -370,6 +370,10 @@ static inline int git_offset_1st_component(const char *path)
|
|||
#define offset_1st_component git_offset_1st_component
|
||||
#endif
|
||||
|
||||
#ifndef is_valid_path
|
||||
#define is_valid_path(path) 1
|
||||
#endif
|
||||
|
||||
#ifndef find_last_dir_sep
|
||||
static inline char *git_find_last_dir_sep(const char *path)
|
||||
{
|
||||
|
|
|
@ -34,6 +34,7 @@ reference=
|
|||
cached=
|
||||
recursive=
|
||||
init=
|
||||
require_init=
|
||||
files=
|
||||
remote=
|
||||
nofetch=
|
||||
|
@ -528,6 +529,10 @@ cmd_update()
|
|||
-i|--init)
|
||||
init=1
|
||||
;;
|
||||
--require-init)
|
||||
init=1
|
||||
require_init=1
|
||||
;;
|
||||
--remote)
|
||||
remote=1
|
||||
;;
|
||||
|
@ -606,6 +611,7 @@ cmd_update()
|
|||
${update:+--update "$update"} \
|
||||
${reference:+"$reference"} \
|
||||
${depth:+--depth "$depth"} \
|
||||
${require_init:+--require-init} \
|
||||
${recommend_shallow:+"$recommend_shallow"} \
|
||||
${jobs:+$jobs} \
|
||||
"$@" || echo "#unmatched" $?
|
||||
|
|
98
path.c
98
path.c
|
@ -1289,37 +1289,77 @@ int daemon_avoid_alias(const char *p)
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* On NTFS, we need to be careful to disallow certain synonyms of the `.git/`
|
||||
* directory:
|
||||
*
|
||||
* - For historical reasons, file names that end in spaces or periods are
|
||||
* automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
|
||||
* to `.git/`.
|
||||
*
|
||||
* - For other historical reasons, file names that do not conform to the 8.3
|
||||
* format (up to eight characters for the basename, three for the file
|
||||
* extension, certain characters not allowed such as `+`, etc) are associated
|
||||
* with a so-called "short name", at least on the `C:` drive by default.
|
||||
* Which means that `git~1/` is a valid way to refer to `.git/`.
|
||||
*
|
||||
* Note: Technically, `.git/` could receive the short name `git~2` if the
|
||||
* short name `git~1` were already used. In Git, however, we guarantee that
|
||||
* `.git` is the first item in a directory, therefore it will be associated
|
||||
* with the short name `git~1` (unless short names are disabled).
|
||||
*
|
||||
* - For yet other historical reasons, NTFS supports so-called "Alternate Data
|
||||
* Streams", i.e. metadata associated with a given file, referred to via
|
||||
* `<filename>:<stream-name>:<stream-type>`. There exists a default stream
|
||||
* type for directories, allowing `.git/` to be accessed via
|
||||
* `.git::$INDEX_ALLOCATION/`.
|
||||
*
|
||||
* When this function returns 1, it indicates that the specified file/directory
|
||||
* name refers to a `.git` file or directory, or to any of these synonyms, and
|
||||
* Git should therefore not track it.
|
||||
*
|
||||
* For performance reasons, _all_ Alternate Data Streams of `.git/` are
|
||||
* forbidden, not just `::$INDEX_ALLOCATION`.
|
||||
*
|
||||
* This function is intended to be used by `git fsck` even on platforms where
|
||||
* the backslash is a regular filename character, therefore it needs to handle
|
||||
* backlash characters in the provided `name` specially: they are interpreted
|
||||
* as directory separators.
|
||||
*/
|
||||
int is_ntfs_dotgit(const char *name)
|
||||
{
|
||||
size_t len;
|
||||
char c;
|
||||
|
||||
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;
|
||||
}
|
||||
/*
|
||||
* Note that when we don't find `.git` or `git~1` we end up with `name`
|
||||
* advanced partway through the string. That's okay, though, as we
|
||||
* return immediately in those cases, without looking at `name` any
|
||||
* further.
|
||||
*/
|
||||
c = *(name++);
|
||||
if (c == '.') {
|
||||
/* .git */
|
||||
if (((c = *(name++)) != 'g' && c != 'G') ||
|
||||
((c = *(name++)) != 'i' && c != 'I') ||
|
||||
((c = *(name++)) != 't' && c != 'T'))
|
||||
return 0;
|
||||
} else if (c == 'g' || c == 'G') {
|
||||
/* git ~1 */
|
||||
if (((c = *(name++)) != 'i' && c != 'I') ||
|
||||
((c = *(name++)) != 't' && c != 'T') ||
|
||||
*(name++) != '~' ||
|
||||
*(name++) != '1')
|
||||
return 0;
|
||||
} else
|
||||
return 0;
|
||||
|
||||
for (;;) {
|
||||
c = *(name++);
|
||||
if (!c || c == '\\' || c == '/' || c == ':')
|
||||
return 1;
|
||||
if (c != '.' && c != ' ')
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_ntfs_dot_generic(const char *name,
|
||||
|
@ -1335,7 +1375,7 @@ static int is_ntfs_dot_generic(const char *name,
|
|||
only_spaces_and_periods:
|
||||
for (;;) {
|
||||
char c = name[i++];
|
||||
if (!c)
|
||||
if (!c || c == ':')
|
||||
return 1;
|
||||
if (c != ' ' && c != '.')
|
||||
return 0;
|
||||
|
|
11
read-cache.c
11
read-cache.c
|
@ -848,6 +848,9 @@ int verify_path(const char *path, unsigned mode)
|
|||
if (has_dos_drive_prefix(path))
|
||||
return 0;
|
||||
|
||||
if (!is_valid_path(path))
|
||||
return 0;
|
||||
|
||||
goto inside;
|
||||
for (;;) {
|
||||
if (!c)
|
||||
|
@ -875,7 +878,15 @@ inside:
|
|||
if ((c == '.' && !verify_dotfile(path, mode)) ||
|
||||
is_dir_sep(c) || c == '\0')
|
||||
return 0;
|
||||
} else if (c == '\\' && protect_ntfs) {
|
||||
if (is_ntfs_dotgit(path))
|
||||
return 0;
|
||||
if (S_ISLNK(mode)) {
|
||||
if (is_ntfs_dotgitmodules(path))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
c = *path++;
|
||||
}
|
||||
}
|
||||
|
|
49
submodule.c
49
submodule.c
|
@ -1794,6 +1794,47 @@ int merge_submodule(struct object_id *result, const char *path,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
|
||||
{
|
||||
size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
|
||||
char *p;
|
||||
int ret = 0;
|
||||
|
||||
if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
|
||||
strcmp(p, submodule_name))
|
||||
BUG("submodule name '%s' not a suffix of git dir '%s'",
|
||||
submodule_name, git_dir);
|
||||
|
||||
/*
|
||||
* We prevent the contents of sibling submodules' git directories to
|
||||
* clash.
|
||||
*
|
||||
* Example: having a submodule named `hippo` and another one named
|
||||
* `hippo/hooks` would result in the git directories
|
||||
* `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively,
|
||||
* but the latter directory is already designated to contain the hooks
|
||||
* of the former.
|
||||
*/
|
||||
for (; *p; p++) {
|
||||
if (is_dir_sep(*p)) {
|
||||
char c = *p;
|
||||
|
||||
*p = '\0';
|
||||
if (is_git_directory(git_dir))
|
||||
ret = -1;
|
||||
*p = c;
|
||||
|
||||
if (ret < 0)
|
||||
return error(_("submodule git dir '%s' is "
|
||||
"inside git dir '%.*s'"),
|
||||
git_dir,
|
||||
(int)(p - git_dir), git_dir);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Embeds a single submodules git directory into the superprojects git dir,
|
||||
* non recursively.
|
||||
|
@ -1802,7 +1843,7 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
|
|||
const char *path)
|
||||
{
|
||||
char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
|
||||
const char *new_git_dir;
|
||||
char *new_git_dir;
|
||||
const struct submodule *sub;
|
||||
|
||||
if (submodule_uses_worktrees(path))
|
||||
|
@ -1820,10 +1861,14 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
|
|||
if (!sub)
|
||||
die(_("could not lookup name for submodule '%s'"), path);
|
||||
|
||||
new_git_dir = git_path("modules/%s", sub->name);
|
||||
new_git_dir = git_pathdup("modules/%s", sub->name);
|
||||
if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
|
||||
die(_("refusing to move '%s' into an existing git dir"),
|
||||
real_old_git_dir);
|
||||
if (safe_create_leading_directories_const(new_git_dir) < 0)
|
||||
die(_("could not create directory '%s'"), new_git_dir);
|
||||
real_new_git_dir = real_pathdup(new_git_dir, 1);
|
||||
free(new_git_dir);
|
||||
|
||||
fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
|
||||
get_super_prefix_or_empty(), path,
|
||||
|
|
|
@ -111,6 +111,11 @@ extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git
|
|||
*/
|
||||
int submodule_to_gitdir(struct strbuf *buf, const char *submodule);
|
||||
|
||||
/*
|
||||
* Make sure that no submodule's git dir is nested in a sibling submodule's.
|
||||
*/
|
||||
int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
|
||||
|
||||
#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)
|
||||
#define SUBMODULE_MOVE_HEAD_FORCE (1<<1)
|
||||
extern int submodule_move_head(const char *path,
|
||||
|
|
|
@ -176,6 +176,99 @@ static int is_dotgitmodules(const char *path)
|
|||
return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* A very simple, reproducible pseudo-random generator. Copied from
|
||||
* `test-genrandom.c`.
|
||||
*/
|
||||
static uint64_t my_random_value = 1234;
|
||||
|
||||
static uint64_t my_random(void)
|
||||
{
|
||||
my_random_value = my_random_value * 1103515245 + 12345;
|
||||
return my_random_value;
|
||||
}
|
||||
|
||||
/*
|
||||
* A fast approximation of the square root, without requiring math.h.
|
||||
*
|
||||
* It uses Newton's method to approximate the solution of 0 = x^2 - value.
|
||||
*/
|
||||
static double my_sqrt(double value)
|
||||
{
|
||||
const double epsilon = 1e-6;
|
||||
double x = value;
|
||||
|
||||
if (value == 0)
|
||||
return 0;
|
||||
|
||||
for (;;) {
|
||||
double delta = (value / x - x) / 2;
|
||||
if (delta < epsilon && delta > -epsilon)
|
||||
return x + delta;
|
||||
x += delta;
|
||||
}
|
||||
}
|
||||
|
||||
static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
|
||||
{
|
||||
size_t i, j, nr, min_len = 3, max_len = 20;
|
||||
char **names;
|
||||
int repetitions = 15, file_mode = 0100644;
|
||||
uint64_t begin, end;
|
||||
double m[3][2], v[3][2];
|
||||
uint64_t cumul;
|
||||
double cumul2;
|
||||
|
||||
if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) {
|
||||
file_mode = 0120000;
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
|
||||
nr = argc > 1 ? strtoul(argv[1], NULL, 0) : 1000000;
|
||||
ALLOC_ARRAY(names, nr);
|
||||
|
||||
if (argc > 2) {
|
||||
min_len = strtoul(argv[2], NULL, 0);
|
||||
if (argc > 3)
|
||||
max_len = strtoul(argv[3], NULL, 0);
|
||||
if (min_len > max_len)
|
||||
die("min_len > max_len");
|
||||
}
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
size_t len = min_len + (my_random() % (max_len + 1 - min_len));
|
||||
|
||||
names[i] = xmallocz(len);
|
||||
while (len > 0)
|
||||
names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' ')));
|
||||
}
|
||||
|
||||
for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
|
||||
for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) {
|
||||
cumul = 0;
|
||||
cumul2 = 0;
|
||||
for (i = 0; i < repetitions; i++) {
|
||||
begin = getnanotime();
|
||||
for (j = 0; j < nr; j++)
|
||||
verify_path(names[j], file_mode);
|
||||
end = getnanotime();
|
||||
printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6);
|
||||
cumul += end - begin;
|
||||
cumul2 += (end - begin) * (end - begin);
|
||||
}
|
||||
m[protect_ntfs][protect_hfs] = cumul / (double)repetitions;
|
||||
v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]);
|
||||
printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6);
|
||||
}
|
||||
|
||||
for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
|
||||
for (protect_hfs = 0; protect_hfs < 2; protect_hfs++)
|
||||
printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_main(int argc, const char **argv)
|
||||
{
|
||||
if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
|
||||
|
@ -290,6 +383,26 @@ int cmd_main(int argc, const char **argv)
|
|||
return !!res;
|
||||
}
|
||||
|
||||
if (argc > 1 && !strcmp(argv[1], "protect_ntfs_hfs"))
|
||||
return !!protect_ntfs_hfs_benchmark(argc - 1, argv + 1);
|
||||
|
||||
if (argc > 1 && !strcmp(argv[1], "is_valid_path")) {
|
||||
int res = 0, expect = 1, i;
|
||||
|
||||
for (i = 2; i < argc; i++)
|
||||
if (!strcmp("--not", argv[i]))
|
||||
expect = 0;
|
||||
else if (expect != is_valid_path(argv[i]))
|
||||
res = error("'%s' is%s a valid path",
|
||||
argv[i], expect ? " not" : "");
|
||||
else
|
||||
fprintf(stderr,
|
||||
"'%s' is%s a valid path\n",
|
||||
argv[i], expect ? "" : " not");
|
||||
|
||||
return !!res;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
|
||||
argv[1] ? argv[1] : "(there was none)");
|
||||
return 1;
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#include "run-command.h"
|
||||
#include "argv-array.h"
|
||||
#include "strbuf.h"
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "gettext.h"
|
||||
#include "parse-options.h"
|
||||
|
||||
static int number_callbacks;
|
||||
static int parallel_next(struct child_process *cp,
|
||||
|
@ -49,11 +49,145 @@ static int task_finished(int result,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static uint64_t my_random_next = 1234;
|
||||
|
||||
static uint64_t my_random(void)
|
||||
{
|
||||
uint64_t res = my_random_next;
|
||||
my_random_next = my_random_next * 1103515245 + 12345;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int quote_stress_test(int argc, const char **argv)
|
||||
{
|
||||
/*
|
||||
* We are running a quote-stress test.
|
||||
* spawn a subprocess that runs quote-stress with a
|
||||
* special option that echoes back the arguments that
|
||||
* were passed in.
|
||||
*/
|
||||
char special[] = ".?*\\^_\"'`{}()[]<>@~&+:;$%"; // \t\r\n\a";
|
||||
int i, j, k, trials = 100, skip = 0, msys2 = 0;
|
||||
struct strbuf out = STRBUF_INIT;
|
||||
struct argv_array args = ARGV_ARRAY_INIT;
|
||||
struct option options[] = {
|
||||
OPT_INTEGER('n', "trials", &trials, "Number of trials"),
|
||||
OPT_INTEGER('s', "skip", &skip, "Skip <n> trials"),
|
||||
OPT_BOOL('m', "msys2", &msys2, "Test quoting for MSYS2's sh"),
|
||||
OPT_END()
|
||||
};
|
||||
const char * const usage[] = {
|
||||
"test-run-command quote-stress-test <options>",
|
||||
NULL
|
||||
};
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options, usage, 0);
|
||||
|
||||
setenv("MSYS_NO_PATHCONV", "1", 0);
|
||||
|
||||
for (i = 0; i < trials; i++) {
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
size_t arg_count, arg_offset;
|
||||
int ret = 0;
|
||||
|
||||
argv_array_clear(&args);
|
||||
if (msys2)
|
||||
argv_array_pushl(&args, "sh", "-c",
|
||||
"printf %s\\\\0 \"$@\"", "skip", NULL);
|
||||
else
|
||||
argv_array_pushl(&args, "test-run-command",
|
||||
"quote-echo", NULL);
|
||||
arg_offset = args.argc;
|
||||
|
||||
if (argc > 0) {
|
||||
trials = 1;
|
||||
arg_count = argc;
|
||||
for (j = 0; j < arg_count; j++)
|
||||
argv_array_push(&args, argv[j]);
|
||||
} else {
|
||||
arg_count = 1 + (my_random() % 5);
|
||||
for (j = 0; j < arg_count; j++) {
|
||||
char buf[20];
|
||||
size_t min_len = 1;
|
||||
size_t arg_len = min_len +
|
||||
(my_random() % (ARRAY_SIZE(buf) - min_len));
|
||||
|
||||
for (k = 0; k < arg_len; k++)
|
||||
buf[k] = special[my_random() %
|
||||
ARRAY_SIZE(special)];
|
||||
buf[arg_len] = '\0';
|
||||
|
||||
argv_array_push(&args, buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < skip)
|
||||
continue;
|
||||
|
||||
cp.argv = args.argv;
|
||||
strbuf_reset(&out);
|
||||
if (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0)
|
||||
return error("Failed to spawn child process");
|
||||
|
||||
for (j = 0, k = 0; j < arg_count; j++) {
|
||||
const char *arg = args.argv[j + arg_offset];
|
||||
|
||||
if (strcmp(arg, out.buf + k))
|
||||
ret = error("incorrectly quoted arg: '%s', "
|
||||
"echoed back as '%s'",
|
||||
arg, out.buf + k);
|
||||
k += strlen(out.buf + k) + 1;
|
||||
}
|
||||
|
||||
if (k != out.len)
|
||||
ret = error("got %d bytes, but consumed only %d",
|
||||
(int)out.len, (int)k);
|
||||
|
||||
if (ret) {
|
||||
fprintf(stderr, "Trial #%d failed. Arguments:\n", i);
|
||||
for (j = 0; j < arg_count; j++)
|
||||
fprintf(stderr, "arg #%d: '%s'\n",
|
||||
(int)j, args.argv[j + arg_offset]);
|
||||
|
||||
strbuf_release(&out);
|
||||
argv_array_clear(&args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (i && (i % 100) == 0)
|
||||
fprintf(stderr, "Trials completed: %d\n", (int)i);
|
||||
}
|
||||
|
||||
strbuf_release(&out);
|
||||
argv_array_clear(&args);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int quote_echo(int argc, const char **argv)
|
||||
{
|
||||
while (argc > 1) {
|
||||
fwrite(argv[1], strlen(argv[1]), 1, stdout);
|
||||
fputc('\0', stdout);
|
||||
argv++;
|
||||
argc--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_main(int argc, const char **argv)
|
||||
{
|
||||
struct child_process proc = CHILD_PROCESS_INIT;
|
||||
int jobs;
|
||||
|
||||
if (argc >= 2 && !strcmp(argv[1], "quote-stress-test"))
|
||||
return !!quote_stress_test(argc - 1, argv + 1);
|
||||
|
||||
if (argc >= 2 && !strcmp(argv[1], "quote-echo"))
|
||||
return !!quote_echo(argc - 1, argv + 1);
|
||||
|
||||
if (argc < 3)
|
||||
return 1;
|
||||
proc.argv = (const char **)argv + 2;
|
||||
|
|
|
@ -165,6 +165,15 @@ test_expect_success 'absolute path rejects the empty string' '
|
|||
test_must_fail test-path-utils absolute_path ""
|
||||
'
|
||||
|
||||
test_expect_success MINGW '<drive-letter>:\\abc is an absolute path' '
|
||||
for letter in : \" C Z 1 ä
|
||||
do
|
||||
path=$letter:\\abc &&
|
||||
absolute="$(test-path-utils absolute_path "$path")" &&
|
||||
test "$path" = "$absolute" || return 1
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'real path rejects the empty string' '
|
||||
test_must_fail test-path-utils real_path ""
|
||||
'
|
||||
|
@ -408,6 +417,9 @@ test_expect_success 'match .gitmodules' '
|
|||
~1000000 \
|
||||
~9999999 \
|
||||
\
|
||||
.gitmodules:\$DATA \
|
||||
"gitmod~4 . :\$DATA" \
|
||||
\
|
||||
--not \
|
||||
".gitmodules x" \
|
||||
".gitmodules .x" \
|
||||
|
@ -432,7 +444,25 @@ test_expect_success 'match .gitmodules' '
|
|||
\
|
||||
GI7EB~1 \
|
||||
GI7EB~01 \
|
||||
GI7EB~1X
|
||||
GI7EB~1X \
|
||||
\
|
||||
.gitmodules,:\$DATA
|
||||
'
|
||||
|
||||
test_expect_success MINGW 'is_valid_path() on Windows' '
|
||||
test-path-utils is_valid_path \
|
||||
win32 \
|
||||
"win32 x" \
|
||||
../hello.txt \
|
||||
C:\\git \
|
||||
\
|
||||
--not \
|
||||
"win32 " \
|
||||
"win32 /x " \
|
||||
"win32." \
|
||||
"win32 . ." \
|
||||
.../hello.txt \
|
||||
colon:test
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -49,6 +49,7 @@ git~1
|
|||
.git.SPACE .git.{space}
|
||||
.\\\\.GIT\\\\foobar backslashes
|
||||
.git\\\\foobar backslashes2
|
||||
.git...:alternate-stream
|
||||
EOF
|
||||
|
||||
test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
|
||||
|
|
|
@ -419,6 +419,7 @@ while read name path pretty; do
|
|||
(
|
||||
git init $name-$type &&
|
||||
cd $name-$type &&
|
||||
git config core.protectNTFS false &&
|
||||
echo content >file &&
|
||||
git add file &&
|
||||
git commit -m base &&
|
||||
|
|
|
@ -10,6 +10,7 @@ test_expect_success 'create commits with glob characters' '
|
|||
# the name "f*" in the worktree, because it is not allowed
|
||||
# on Windows (the tests below do not depend on the presence
|
||||
# of the file in the worktree)
|
||||
git config core.protectNTFS false &&
|
||||
git update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:foo)" "f*" &&
|
||||
test_tick &&
|
||||
git commit -m star &&
|
||||
|
|
|
@ -73,4 +73,60 @@ test_expect_success 'clone evil superproject' '
|
|||
! grep "RUNNING POST CHECKOUT" output
|
||||
'
|
||||
|
||||
test_expect_success MINGW 'prevent git~1 squatting on Windows' '
|
||||
git init squatting &&
|
||||
(
|
||||
cd squatting &&
|
||||
mkdir a &&
|
||||
touch a/..git &&
|
||||
git add a/..git &&
|
||||
test_tick &&
|
||||
git commit -m initial &&
|
||||
|
||||
modules="$(test_write_lines \
|
||||
"[submodule \"b.\"]" "url = ." "path = c" \
|
||||
"[submodule \"b\"]" "url = ." "path = d\\\\a" |
|
||||
git hash-object -w --stdin)" &&
|
||||
rev="$(git rev-parse --verify HEAD)" &&
|
||||
hash="$(echo x | git hash-object -w --stdin)" &&
|
||||
git -c core.protectNTFS=false update-index --add \
|
||||
--cacheinfo 100644,$modules,.gitmodules \
|
||||
--cacheinfo 160000,$rev,c \
|
||||
--cacheinfo 160000,$rev,d\\a \
|
||||
--cacheinfo 100644,$hash,d./a/x \
|
||||
--cacheinfo 100644,$hash,d./a/..git &&
|
||||
test_tick &&
|
||||
git -c core.protectNTFS=false commit -m "module" &&
|
||||
test_must_fail git show HEAD: 2>err &&
|
||||
test_i18ngrep backslash err
|
||||
) &&
|
||||
test_must_fail git -c core.protectNTFS=false \
|
||||
clone --recurse-submodules squatting squatting-clone 2>err &&
|
||||
test_i18ngrep -e "directory not empty" -e "not an empty directory" err &&
|
||||
! grep gitdir squatting-clone/d/a/git~2
|
||||
'
|
||||
|
||||
test_expect_success 'git dirs of sibling submodules must not be nested' '
|
||||
git init nested &&
|
||||
test_commit -C nested nested &&
|
||||
(
|
||||
cd nested &&
|
||||
cat >.gitmodules <<-EOF &&
|
||||
[submodule "hippo"]
|
||||
url = .
|
||||
path = thing1
|
||||
[submodule "hippo/hooks"]
|
||||
url = .
|
||||
path = thing2
|
||||
EOF
|
||||
git clone . thing1 &&
|
||||
git clone . thing2 &&
|
||||
git add .gitmodules thing1 thing2 &&
|
||||
test_tick &&
|
||||
git commit -m nested
|
||||
) &&
|
||||
test_must_fail git clone --recurse-submodules nested clone 2>err &&
|
||||
test_i18ngrep "is inside git dir" err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -31,4 +31,18 @@ test_expect_success 'clone rejects unprotected dash' '
|
|||
test_i18ngrep ignoring err
|
||||
'
|
||||
|
||||
test_expect_success 'trailing backslash is handled correctly' '
|
||||
git init testmodule &&
|
||||
test_commit -C testmodule c &&
|
||||
git submodule add ./testmodule &&
|
||||
: ensure that the name ends in a double backslash &&
|
||||
sed -e "s|\\(submodule \"testmodule\\)\"|\\1\\\\\\\\\"|" \
|
||||
-e "s|url = .*|url = \" --should-not-be-an-option\"|" \
|
||||
<.gitmodules >.new &&
|
||||
mv .new .gitmodules &&
|
||||
git commit -am "Add testmodule" &&
|
||||
test_must_fail git clone --verbose --recurse-submodules . dolly 2>err &&
|
||||
test_i18ngrep ! "unknown option" err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -17,4 +17,21 @@ test_expect_success 'clone rejects unprotected dash' '
|
|||
test_i18ngrep ignoring err
|
||||
'
|
||||
|
||||
test_expect_success MINGW 'submodule paths disallows trailing spaces' '
|
||||
git init super &&
|
||||
test_must_fail git -C super submodule add ../upstream "sub " &&
|
||||
|
||||
: add "sub", then rename "sub" to "sub ", the hard way &&
|
||||
git -C super submodule add ../upstream sub &&
|
||||
tree=$(git -C super write-tree) &&
|
||||
git -C super ls-tree $tree >tree &&
|
||||
sed "s/sub/sub /" <tree >tree.new &&
|
||||
tree=$(git -C super mktree <tree.new) &&
|
||||
commit=$(echo with space | git -C super commit-tree $tree) &&
|
||||
git -C super update-ref refs/heads/master $commit &&
|
||||
|
||||
test_must_fail git clone --recurse-submodules super dst 2>err &&
|
||||
test_i18ngrep "sub " err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -2106,12 +2106,27 @@ test_expect_success 'R: abort on receiving feature after data command' '
|
|||
test_must_fail git fast-import <input
|
||||
'
|
||||
|
||||
test_expect_success 'R: import-marks features forbidden by default' '
|
||||
>git.marks &&
|
||||
echo "feature import-marks=git.marks" >input &&
|
||||
test_must_fail git fast-import <input &&
|
||||
echo "feature import-marks-if-exists=git.marks" >input &&
|
||||
test_must_fail git fast-import <input
|
||||
'
|
||||
|
||||
test_expect_success 'R: only one import-marks feature allowed per stream' '
|
||||
>git.marks &&
|
||||
>git2.marks &&
|
||||
cat >input <<-EOF &&
|
||||
feature import-marks=git.marks
|
||||
feature import-marks=git2.marks
|
||||
EOF
|
||||
|
||||
test_must_fail git fast-import --allow-unsafe-features <input
|
||||
'
|
||||
|
||||
test_expect_success 'R: export-marks feature forbidden by default' '
|
||||
echo "feature export-marks=git.marks" >input &&
|
||||
test_must_fail git fast-import <input
|
||||
'
|
||||
|
||||
|
@ -2125,19 +2140,29 @@ test_expect_success 'R: export-marks feature results in a marks file being creat
|
|||
|
||||
EOF
|
||||
|
||||
cat input | git fast-import &&
|
||||
git fast-import --allow-unsafe-features <input &&
|
||||
grep :1 git.marks
|
||||
'
|
||||
|
||||
test_expect_success 'R: export-marks options can be overridden by commandline options' '
|
||||
cat input | git fast-import --export-marks=other.marks &&
|
||||
grep :1 other.marks
|
||||
cat >input <<-\EOF &&
|
||||
feature export-marks=feature-sub/git.marks
|
||||
blob
|
||||
mark :1
|
||||
data 3
|
||||
hi
|
||||
|
||||
EOF
|
||||
git fast-import --allow-unsafe-features \
|
||||
--export-marks=cmdline-sub/other.marks <input &&
|
||||
grep :1 cmdline-sub/other.marks &&
|
||||
test_path_is_missing feature-sub
|
||||
'
|
||||
|
||||
test_expect_success 'R: catch typo in marks file name' '
|
||||
test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
|
||||
echo "feature import-marks=nonexistent.marks" |
|
||||
test_must_fail git fast-import
|
||||
test_must_fail git fast-import --allow-unsafe-features
|
||||
'
|
||||
|
||||
test_expect_success 'R: import and output marks can be the same file' '
|
||||
|
@ -2193,7 +2218,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
|
|||
rm -f io.marks &&
|
||||
>expect &&
|
||||
|
||||
git fast-import --export-marks=io.marks <<-\EOF &&
|
||||
git fast-import --export-marks=io.marks \
|
||||
--allow-unsafe-features <<-\EOF &&
|
||||
feature import-marks-if-exists=not_io.marks
|
||||
EOF
|
||||
test_cmp expect io.marks &&
|
||||
|
@ -2204,7 +2230,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
|
|||
echo ":1 $blob" >expect &&
|
||||
echo ":2 $blob" >>expect &&
|
||||
|
||||
git fast-import --export-marks=io.marks <<-\EOF &&
|
||||
git fast-import --export-marks=io.marks \
|
||||
--allow-unsafe-features <<-\EOF &&
|
||||
feature import-marks-if-exists=io.marks
|
||||
blob
|
||||
mark :2
|
||||
|
@ -2217,7 +2244,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
|
|||
echo ":3 $blob" >>expect &&
|
||||
|
||||
git fast-import --import-marks=io.marks \
|
||||
--export-marks=io.marks <<-\EOF &&
|
||||
--export-marks=io.marks \
|
||||
--allow-unsafe-features <<-\EOF &&
|
||||
feature import-marks-if-exists=not_io.marks
|
||||
blob
|
||||
mark :3
|
||||
|
@ -2230,7 +2258,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
|
|||
>expect &&
|
||||
|
||||
git fast-import --import-marks-if-exists=not_io.marks \
|
||||
--export-marks=io.marks <<-\EOF &&
|
||||
--export-marks=io.marks \
|
||||
--allow-unsafe-features <<-\EOF &&
|
||||
feature import-marks-if-exists=io.marks
|
||||
EOF
|
||||
test_cmp expect io.marks
|
||||
|
@ -2242,7 +2271,7 @@ test_expect_success 'R: import to output marks works without any content' '
|
|||
feature export-marks=marks.new
|
||||
EOF
|
||||
|
||||
cat input | git fast-import &&
|
||||
git fast-import --allow-unsafe-features <input &&
|
||||
test_cmp marks.out marks.new
|
||||
'
|
||||
|
||||
|
@ -2252,7 +2281,7 @@ test_expect_success 'R: import marks prefers commandline marks file over the str
|
|||
feature export-marks=marks.new
|
||||
EOF
|
||||
|
||||
cat input | git fast-import --import-marks=marks.out &&
|
||||
git fast-import --import-marks=marks.out --allow-unsafe-features <input &&
|
||||
test_cmp marks.out marks.new
|
||||
'
|
||||
|
||||
|
@ -2265,7 +2294,8 @@ test_expect_success 'R: multiple --import-marks= should be honoured' '
|
|||
|
||||
head -n2 marks.out > one.marks &&
|
||||
tail -n +3 marks.out > two.marks &&
|
||||
git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
|
||||
git fast-import --import-marks=one.marks --import-marks=two.marks \
|
||||
--allow-unsafe-features <input &&
|
||||
test_cmp marks.out combined.marks
|
||||
'
|
||||
|
||||
|
@ -2278,7 +2308,7 @@ test_expect_success 'R: feature relative-marks should be honoured' '
|
|||
|
||||
mkdir -p .git/info/fast-import/ &&
|
||||
cp marks.new .git/info/fast-import/relative.in &&
|
||||
git fast-import <input &&
|
||||
git fast-import --allow-unsafe-features <input &&
|
||||
test_cmp marks.new .git/info/fast-import/relative.out
|
||||
'
|
||||
|
||||
|
@ -2290,7 +2320,7 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
|
|||
feature export-marks=non-relative.out
|
||||
EOF
|
||||
|
||||
git fast-import <input &&
|
||||
git fast-import --allow-unsafe-features <input &&
|
||||
test_cmp marks.new non-relative.out
|
||||
'
|
||||
|
||||
|
@ -2560,7 +2590,7 @@ test_expect_success 'R: quiet option results in no stats being output' '
|
|||
|
||||
EOF
|
||||
|
||||
cat input | git fast-import 2> output &&
|
||||
git fast-import 2>output <input &&
|
||||
test_must_be_empty output
|
||||
'
|
||||
|
||||
|
|
|
@ -421,9 +421,10 @@ test_expect_success 'directory becomes symlink' '
|
|||
|
||||
test_expect_success 'fast-export quotes pathnames' '
|
||||
git init crazy-paths &&
|
||||
test_config -C crazy-paths core.protectNTFS false &&
|
||||
(cd crazy-paths &&
|
||||
blob=$(echo foo | git hash-object -w --stdin) &&
|
||||
git update-index --add \
|
||||
git -c core.protectNTFS=false update-index --add \
|
||||
--cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \
|
||||
--cacheinfo 100644 $blob "path with \"quote\"" \
|
||||
--cacheinfo 100644 $blob "path with \\backslash" \
|
||||
|
|
|
@ -431,6 +431,7 @@ static int get_importer(struct transport *transport, struct child_process *fasti
|
|||
child_process_init(fastimport);
|
||||
fastimport->in = helper->out;
|
||||
argv_array_push(&fastimport->args, "fast-import");
|
||||
argv_array_push(&fastimport->args, "--allow-unsafe-features");
|
||||
argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
|
||||
|
||||
if (data->bidi_import) {
|
||||
|
|
|
@ -41,6 +41,12 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l
|
|||
strbuf_addstr(err, _("empty filename in tree entry"));
|
||||
return -1;
|
||||
}
|
||||
#ifdef GIT_WINDOWS_NATIVE
|
||||
if (protect_ntfs && strchr(path, '\\')) {
|
||||
strbuf_addf(err, _("filename in tree entry contains backslash: '%s'"), path);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
len = strlen(path) + 1;
|
||||
|
||||
/* Initialize the descriptor entry */
|
||||
|
|
|
@ -1809,7 +1809,8 @@ static int merged_entry(const struct cache_entry *ce,
|
|||
invalidate_ce_path(old, o);
|
||||
}
|
||||
|
||||
do_add_entry(o, merge, update, CE_STAGEMASK);
|
||||
if (do_add_entry(o, merge, update, CE_STAGEMASK) < 0)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче