Sync with 2.20.2
* maint-2.20: (36 commits) Git 2.20.2 t7415: adjust test for dubiously-nested submodule gitdirs for v2.20.x Git 2.19.3 Git 2.18.2 Git 2.17.3 Git 2.16.6 test-drop-caches: use `has_dos_drive_prefix()` Git 2.15.4 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 ...maint
						commit
						fc346cb292
					
				|  | @ -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. | ||||
|  | @ -0,0 +1,11 @@ | |||
| Git v2.15.4 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6 to address | ||||
| 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; see the release notes for that version for details. | ||||
|  | ||||
| In conjunction with a vulnerability that was fixed in v2.20.2, | ||||
| `.gitmodules` is no longer allowed to contain entries of the form | ||||
| `submodule.<name>.update=!command`. | ||||
|  | @ -0,0 +1,8 @@ | |||
| Git v2.16.6 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6 and in | ||||
| v2.15.4 addressing 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; see the release notes for those | ||||
| versions for details. | ||||
|  | @ -0,0 +1,12 @@ | |||
| Git v2.17.3 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6 and in | ||||
| v2.15.4 addressing 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; see the release notes for those | ||||
| versions for details. | ||||
|  | ||||
| In addition, `git fsck` was taught to identify `.gitmodules` entries | ||||
| of the form `submodule.<name>.update=!command`, which have been | ||||
| disallowed in v2.15.4. | ||||
|  | @ -0,0 +1,8 @@ | |||
| Git v2.18.2 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6, v2.15.4 | ||||
| and in v2.17.3, addressing 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; see the release notes | ||||
| for those versions for details. | ||||
|  | @ -0,0 +1,8 @@ | |||
| Git v2.19.3 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6, v2.15.4 | ||||
| and in v2.17.3, addressing 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; see the release notes | ||||
| for those versions for details. | ||||
|  | @ -0,0 +1,18 @@ | |||
| Git v2.20.2 Release Notes | ||||
| ========================= | ||||
|  | ||||
| This release merges up the fixes that appear in v2.14.6, v2.15.4 | ||||
| and in v2.17.3, addressing 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; see the release notes | ||||
| for those versions for details. | ||||
|  | ||||
| The change to disallow `submodule.<name>.update=!command` entries in | ||||
| `.gitmodules` which was introduced v2.15.4 (and for which v2.17.3 | ||||
| added explicit fsck checks) fixes the vulnerability in v2.20.x where a | ||||
| recursive clone followed by a submodule update could execute code | ||||
| contained within the repository without the user explicitly having | ||||
| asked for that (CVE-2019-19604). | ||||
|  | ||||
| Credit for finding this vulnerability goes to Joern Schneeweisz, | ||||
| credit for the fixes goes to Jonathan Nieder. | ||||
|  | @ -51,6 +51,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 | ||||
| ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|  |  | |||
|  | @ -44,9 +44,8 @@ submodule.<name>.update:: | |||
| 	submodule init` to initialize the configuration variable of | ||||
| 	the same name. Allowed values here are 'checkout', 'rebase', | ||||
| 	'merge' or 'none'. See description of 'update' command in | ||||
| 	linkgit:git-submodule[1] for their meaning. Note that the | ||||
| 	'!command' form is intentionally ignored here for security | ||||
| 	reasons. | ||||
| 	linkgit:git-submodule[1] for their meaning. For security | ||||
| 	reasons, the '!command' form is not accepted here. | ||||
|  | ||||
| submodule.<name>.branch:: | ||||
| 	A remote branch name for tracking updates in the upstream submodule. | ||||
|  |  | |||
|  | @ -775,7 +775,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"); | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #include "diffcore.h" | ||||
| #include "diff.h" | ||||
| #include "object-store.h" | ||||
| #include "dir.h" | ||||
|  | ||||
| #define OPT_QUIET (1 << 0) | ||||
| #define OPT_CACHED (1 << 1) | ||||
|  | @ -1358,7 +1359,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 dissociate = 0; | ||||
| 	int dissociate = 0, require_init = 0; | ||||
| 	char *sm_alternate = NULL, *error_strategy = NULL; | ||||
|  | ||||
| 	struct option module_clone_options[] = { | ||||
|  | @ -1385,6 +1386,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() | ||||
| 	}; | ||||
|  | ||||
|  | @ -1412,6 +1415,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); | ||||
|  | @ -1423,6 +1430,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); | ||||
|  | @ -1477,6 +1486,8 @@ static void determine_submodule_update_strategy(struct repository *r, | |||
| 			die(_("Invalid update mode '%s' configured for submodule path '%s'"), | ||||
| 				val, path); | ||||
| 	} else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { | ||||
| 		if (sub->update_strategy.type == SM_UPDATE_COMMAND) | ||||
| 			BUG("how did we read update = !command from .gitmodules?"); | ||||
| 		out->type = sub->update_strategy.type; | ||||
| 		out->command = sub->update_strategy.command; | ||||
| 	} else | ||||
|  | @ -1535,6 +1546,7 @@ struct submodule_update_clone { | |||
| 	int recommend_shallow; | ||||
| 	struct string_list references; | ||||
| 	int dissociate; | ||||
| 	unsigned require_init; | ||||
| 	const char *depth; | ||||
| 	const char *recursive_prefix; | ||||
| 	const char *prefix; | ||||
|  | @ -1553,7 +1565,7 @@ struct submodule_update_clone { | |||
| 	int max_jobs; | ||||
| }; | ||||
| #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ | ||||
| 	SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ | ||||
| 	SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \ | ||||
| 	NULL, NULL, NULL, \ | ||||
| 	NULL, 0, 0, 0, NULL, 0, 0, 1} | ||||
|  | ||||
|  | @ -1680,6 +1692,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); | ||||
|  | @ -1870,6 +1884,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() | ||||
| 	}; | ||||
|  | ||||
|  |  | |||
|  | @ -390,6 +390,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); | ||||
|  | @ -463,7 +469,7 @@ int mingw_open (const char *filename, int oflags, ...) | |||
| 	typedef int (*open_fn_t)(wchar_t const *wfilename, 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]; | ||||
| 	open_fn_t open_fn; | ||||
|  | ||||
|  | @ -471,6 +477,11 @@ int mingw_open (const char *filename, int 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"; | ||||
|  | ||||
|  | @ -537,6 +548,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 || | ||||
|  | @ -559,6 +575,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 || | ||||
|  | @ -1052,7 +1073,7 @@ static const char *quote_arg_msvc(const char *arg) | |||
| 				p++; | ||||
| 				len++; | ||||
| 			} | ||||
| 			if (*p == '"') | ||||
| 			if (*p == '"' || !*p) | ||||
| 				n += count*2 + 1; | ||||
| 			continue; | ||||
| 		} | ||||
|  | @ -1074,16 +1095,19 @@ static const char *quote_arg_msvc(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; | ||||
| } | ||||
|  | ||||
|  | @ -2464,6 +2488,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). | ||||
|  |  | |||
|  | @ -459,6 +459,20 @@ extern char *mingw_query_user_email(void); | |||
| #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. | ||||
|  * | ||||
|  |  | |||
|  | @ -1,5 +1,29 @@ | |||
| #include "../../git-compat-util.h" | ||||
|  | ||||
| int win32_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 win32_skip_dos_drive_prefix(char **path) | ||||
| { | ||||
| 	int ret = has_dos_drive_prefix(*path); | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| #define has_dos_drive_prefix(path) \ | ||||
| 	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) | ||||
| int win32_has_dos_drive_prefix(const char *path); | ||||
| #define has_dos_drive_prefix win32_has_dos_drive_prefix | ||||
|  | ||||
| int win32_skip_dos_drive_prefix(char **path); | ||||
| #define skip_dos_drive_prefix win32_skip_dos_drive_prefix | ||||
| static inline int win32_is_dir_sep(int c) | ||||
|  |  | |||
|  | @ -399,7 +399,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 | ||||
|  | @ -549,7 +548,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) | |||
| 		compat/win32/path-utils.o \ | ||||
| 		compat/win32/pthread.o compat/win32/syslog.o \ | ||||
| 		compat/win32/dirent.o | ||||
| 	BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1 | ||||
| 	BASIC_CFLAGS += -DWIN32 | ||||
| 	EXTLIBS += -lws2_32 | ||||
| 	GITLIBS += git.res | ||||
| 	PTHREAD_LIBS = | ||||
|  |  | |||
|  | @ -511,7 +511,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) | ||||
|  |  | |||
|  | @ -80,7 +80,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; | ||||
| const char *core_fsmonitor; | ||||
|  |  | |||
|  | @ -210,6 +210,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; | ||||
|  | @ -1672,6 +1673,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); | ||||
|  | @ -3056,7 +3063,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; | ||||
| } | ||||
|  | @ -3097,7 +3103,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) | ||||
|  | @ -3140,10 +3145,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; | ||||
| 	} | ||||
|  | @ -3151,6 +3158,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; | ||||
|  | @ -3158,10 +3172,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 */ | ||||
|  | @ -3288,6 +3305,20 @@ int cmd_main(int argc, const char **argv) | |||
| 	avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); | ||||
| 	marks = mem_pool_calloc(&fi_mem_pool, 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; | ||||
|  | ||||
|  |  | |||
							
								
								
									
										25
									
								
								fsck.c
								
								
								
								
							
							
						
						
									
										25
									
								
								fsck.c
								
								
								
								
							|  | @ -68,6 +68,7 @@ static struct oidset gitmodules_done = OIDSET_INIT; | |||
| 	FUNC(GITMODULES_SYMLINK, ERROR) \ | ||||
| 	FUNC(GITMODULES_URL, ERROR) \ | ||||
| 	FUNC(GITMODULES_PATH, ERROR) \ | ||||
| 	FUNC(GITMODULES_UPDATE, ERROR) \ | ||||
| 	/* warnings */ \ | ||||
| 	FUNC(BAD_FILEMODE, WARN) \ | ||||
| 	FUNC(EMPTY_NAME, WARN) \ | ||||
|  | @ -605,7 +606,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); | ||||
|  | @ -627,6 +628,22 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) | |||
| 						 ".gitmodules is a symbolic link"); | ||||
| 		} | ||||
|  | ||||
| 		if ((backslash = strchr(name, '\\'))) { | ||||
| 			while (backslash) { | ||||
| 				backslash++; | ||||
| 				has_dotgit |= is_ntfs_dotgit(backslash); | ||||
| 				if (is_ntfs_dotgitmodules(backslash)) { | ||||
| 					if (!S_ISLNK(mode)) | ||||
| 						oidset_insert(&gitmodules_found, oid); | ||||
| 					else | ||||
| 						retval += report(options, &item->object, | ||||
| 								 FSCK_MSG_GITMODULES_SYMLINK, | ||||
| 								 ".gitmodules is a symbolic link"); | ||||
| 				} | ||||
| 				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; | ||||
|  | @ -1000,6 +1017,12 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) | |||
| 				    FSCK_MSG_GITMODULES_PATH, | ||||
| 				    "disallowed submodule path: %s", | ||||
| 				    value); | ||||
| 	if (!strcmp(key, "update") && value && | ||||
| 	    parse_submodule_update_type(value) == SM_UPDATE_COMMAND) | ||||
| 		data->ret |= report(data->options, data->obj, | ||||
| 				    FSCK_MSG_GITMODULES_UPDATE, | ||||
| 				    "disallowed submodule update setting: %s", | ||||
| 				    value); | ||||
| 	free(name); | ||||
|  | ||||
| 	return 0; | ||||
|  |  | |||
|  | @ -386,6 +386,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= | ||||
|  | @ -457,6 +458,10 @@ cmd_update() | |||
| 		-i|--init) | ||||
| 			init=1 | ||||
| 			;; | ||||
| 		--require-init) | ||||
| 			init=1 | ||||
| 			require_init=1 | ||||
| 			;; | ||||
| 		--remote) | ||||
| 			remote=1 | ||||
| 			;; | ||||
|  | @ -539,6 +544,7 @@ cmd_update() | |||
| 		${reference:+"$reference"} \ | ||||
| 		${dissociate:+"--dissociate"} \ | ||||
| 		${depth:+--depth "$depth"} \ | ||||
| 		${require_init:+--require-init} \ | ||||
| 		$recommend_shallow \ | ||||
| 		$jobs \ | ||||
| 		"$@" || echo "#unmatched" $? | ||||
|  |  | |||
							
								
								
									
										98
									
								
								path.c
								
								
								
								
							
							
						
						
									
										98
									
								
								path.c
								
								
								
								
							|  | @ -1292,37 +1292,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, | ||||
|  | @ -1338,7 +1378,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
								
								
								
								
							|  | @ -954,6 +954,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) | ||||
|  | @ -981,7 +984,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++; | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -398,6 +398,13 @@ struct parse_config_parameter { | |||
| 	int overwrite; | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * Parse a config item from .gitmodules. | ||||
|  * | ||||
|  * This does not handle submodule-related configuration from the main | ||||
|  * config store (.git/config, etc).  Callers are responsible for | ||||
|  * checking for overrides in the main config store when appropriate. | ||||
|  */ | ||||
| static int parse_config(const char *var, const char *value, void *data) | ||||
| { | ||||
| 	struct parse_config_parameter *me = data; | ||||
|  | @ -475,8 +482,9 @@ static int parse_config(const char *var, const char *value, void *data) | |||
| 			warn_multiple_config(me->treeish_name, submodule->name, | ||||
| 					     "update"); | ||||
| 		else if (parse_submodule_update_strategy(value, | ||||
| 			 &submodule->update_strategy) < 0) | ||||
| 				die(_("invalid value for %s"), var); | ||||
| 			 &submodule->update_strategy) < 0 || | ||||
| 			 submodule->update_strategy.type == SM_UPDATE_COMMAND) | ||||
| 			die(_("invalid value for %s"), var); | ||||
| 	} else if (!strcmp(item.buf, "shallow")) { | ||||
| 		if (!me->overwrite && submodule->recommend_shallow != -1) | ||||
| 			warn_multiple_config(me->treeish_name, submodule->name, | ||||
|  |  | |||
							
								
								
									
										49
									
								
								submodule.c
								
								
								
								
							
							
						
						
									
										49
									
								
								submodule.c
								
								
								
								
							|  | @ -1981,6 +1981,47 @@ out: | |||
| 	return ret; | ||||
| } | ||||
|  | ||||
| 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. | ||||
|  | @ -1989,7 +2030,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)) | ||||
|  | @ -2007,10 +2048,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, | ||||
|  |  | |||
|  | @ -124,6 +124,11 @@ int push_unpushed_submodules(struct repository *r, | |||
|  */ | ||||
| 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) | ||||
| int submodule_move_head(const char *path, | ||||
|  |  | |||
|  | @ -8,18 +8,21 @@ static int cmd_sync(void) | |||
| { | ||||
| 	char Buffer[MAX_PATH]; | ||||
| 	DWORD dwRet; | ||||
| 	char szVolumeAccessPath[] = "\\\\.\\X:"; | ||||
| 	char szVolumeAccessPath[] = "\\\\.\\XXXX:"; | ||||
| 	HANDLE hVolWrite; | ||||
| 	int success = 0; | ||||
| 	int success = 0, dos_drive_prefix; | ||||
|  | ||||
| 	dwRet = GetCurrentDirectory(MAX_PATH, Buffer); | ||||
| 	if ((0 == dwRet) || (dwRet > MAX_PATH)) | ||||
| 		return error("Error getting current directory"); | ||||
|  | ||||
| 	if (!has_dos_drive_prefix(Buffer)) | ||||
| 	dos_drive_prefix = has_dos_drive_prefix(Buffer); | ||||
| 	if (!dos_drive_prefix) | ||||
| 		return error("'%s': invalid drive letter", Buffer); | ||||
|  | ||||
| 	szVolumeAccessPath[4] = Buffer[0]; | ||||
| 	memcpy(szVolumeAccessPath, Buffer, dos_drive_prefix); | ||||
| 	szVolumeAccessPath[dos_drive_prefix] = '\0'; | ||||
|  | ||||
| 	hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE, | ||||
| 		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); | ||||
| 	if (INVALID_HANDLE_VALUE == hVolWrite) | ||||
|  |  | |||
|  | @ -185,6 +185,99 @@ static int cmp_by_st_size(const void *a, const void *b) | |||
| 	return x > y ? -1 : (x < y ? +1 : 0); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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__path_utils(int argc, const char **argv) | ||||
| { | ||||
| 	if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { | ||||
|  | @ -355,6 +448,26 @@ int cmd__path_utils(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; | ||||
|  |  | |||
|  | @ -13,8 +13,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, | ||||
|  | @ -50,11 +50,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-tool 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-tool", "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__run_command(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; | ||||
| 	while (!strcmp(argv[1], "env")) { | ||||
|  |  | |||
|  | @ -165,6 +165,15 @@ test_expect_success 'absolute path rejects the empty string' ' | |||
| 	test_must_fail test-tool 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-tool path-utils absolute_path "$path")" && | ||||
| 		test "$path" = "$absolute" || return 1 | ||||
| 	done | ||||
| ' | ||||
|  | ||||
| test_expect_success 'real path rejects the empty string' ' | ||||
| 	test_must_fail test-tool path-utils real_path "" | ||||
| ' | ||||
|  | @ -423,6 +432,9 @@ test_expect_success 'match .gitmodules' ' | |||
| 		~1000000 \ | ||||
| 		~9999999 \ | ||||
| 		\ | ||||
| 		.gitmodules:\$DATA \ | ||||
| 		"gitmod~4 . :\$DATA" \ | ||||
| 		\ | ||||
| 		--not \ | ||||
| 		".gitmodules x"  \ | ||||
| 		".gitmodules .x" \ | ||||
|  | @ -447,7 +459,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-tool 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' ' | ||||
|  |  | |||
|  | @ -453,6 +453,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 && | ||||
|  |  | |||
|  | @ -407,12 +407,26 @@ test_expect_success 'submodule update - command in .git/config' ' | |||
| 	) | ||||
| ' | ||||
|  | ||||
| test_expect_success 'submodule update - command in .gitmodules is ignored' ' | ||||
| test_expect_success 'submodule update - command in .gitmodules is rejected' ' | ||||
| 	test_when_finished "git -C super reset --hard HEAD^" && | ||||
| 	git -C super config -f .gitmodules submodule.submodule.update "!false" && | ||||
| 	git -C super commit -a -m "add command to .gitmodules file" && | ||||
| 	git -C super/submodule reset --hard $submodulesha1^ && | ||||
| 	git -C super submodule update submodule | ||||
| 	test_must_fail git -C super submodule update submodule | ||||
| ' | ||||
|  | ||||
| test_expect_success 'fsck detects command in .gitmodules' ' | ||||
| 	git init command-in-gitmodules && | ||||
| 	( | ||||
| 		cd command-in-gitmodules && | ||||
| 		git submodule add ../submodule submodule && | ||||
| 		test_commit adding-submodule && | ||||
|  | ||||
| 		git config -f .gitmodules submodule.submodule.update "!false" && | ||||
| 		git add .gitmodules && | ||||
| 		test_commit configuring-update && | ||||
| 		test_must_fail git fsck | ||||
| 	) | ||||
| ' | ||||
|  | ||||
| cat << EOF >expect | ||||
|  | @ -481,6 +495,9 @@ test_expect_success 'recursive submodule update - command in .git/config catches | |||
| ' | ||||
|  | ||||
| test_expect_success 'submodule init does not copy command into .git/config' ' | ||||
| 	test_when_finished "git -C super update-index --force-remove submodule1" && | ||||
| 	test_when_finished git config -f super/.gitmodules \ | ||||
| 		--remove-section submodule.submodule1 && | ||||
| 	(cd super && | ||||
| 	 git ls-files -s submodule >out && | ||||
| 	 H=$(cut -d" " -f2 out) && | ||||
|  | @ -489,10 +506,9 @@ test_expect_success 'submodule init does not copy command into .git/config' ' | |||
| 	 git config -f .gitmodules submodule.submodule1.path submodule1 && | ||||
| 	 git config -f .gitmodules submodule.submodule1.url ../submodule && | ||||
| 	 git config -f .gitmodules submodule.submodule1.update !false && | ||||
| 	 git submodule init submodule1 && | ||||
| 	 echo "none" >expect && | ||||
| 	 git config submodule.submodule1.update >actual && | ||||
| 	 test_cmp expect actual | ||||
| 	 test_must_fail git submodule init submodule1 && | ||||
| 	 test_expect_code 1 git config submodule.submodule1.update >actual && | ||||
| 	 test_must_be_empty actual | ||||
| 	) | ||||
| ' | ||||
|  | ||||
|  |  | |||
|  | @ -191,4 +191,60 @@ test_expect_success 'fsck detects corrupt .gitmodules' ' | |||
| 	) | ||||
| ' | ||||
|  | ||||
| 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 -E "(is inside git dir|hippo already exists|not a git repository: .*/hippo)" err | ||||
| ' | ||||
|  | ||||
| test_done | ||||
|  |  | |||
|  | @ -46,4 +46,18 @@ test_expect_success 'fsck rejects unprotected dash' ' | |||
| 	grep gitmodulesUrl 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 | ||||
|  |  | |||
|  | @ -25,4 +25,21 @@ test_expect_success 'fsck rejects unprotected dash' ' | |||
| 	grep gitmodulesPath 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' ' | ||||
|  | @ -2192,7 +2217,8 @@ test_expect_success 'R: --import-marks-if-exists' ' | |||
| test_expect_success 'R: feature import-marks-if-exists' ' | ||||
| 	rm -f io.marks && | ||||
|  | ||||
| 	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_must_be_empty io.marks && | ||||
|  | @ -2203,7 +2229,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 | ||||
|  | @ -2216,7 +2243,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 | ||||
|  | @ -2227,7 +2255,8 @@ test_expect_success 'R: feature import-marks-if-exists' ' | |||
| 	test_cmp expect io.marks && | ||||
|  | ||||
| 	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_must_be_empty io.marks | ||||
|  | @ -2239,7 +2268,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 | ||||
| ' | ||||
|  | ||||
|  | @ -2249,7 +2278,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 | ||||
| ' | ||||
|  | ||||
|  | @ -2262,7 +2291,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 | ||||
| ' | ||||
|  | ||||
|  | @ -2275,7 +2305,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 | ||||
| ' | ||||
|  | ||||
|  | @ -2287,7 +2317,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 | ||||
| ' | ||||
|  | ||||
|  | @ -2557,7 +2587,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 | ||||
| ' | ||||
|  | ||||
|  |  | |||
|  | @ -482,9 +482,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" \ | ||||
|  |  | |||
|  | @ -423,6 +423,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) { | ||||
|  |  | |||
|  | @ -43,6 +43,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 */ | ||||
|  |  | |||
|  | @ -2072,7 +2072,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; | ||||
| } | ||||
|  | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Johannes Schindelin
						Johannes Schindelin