diff options
40 files changed, 914 insertions, 82 deletions
diff --git a/Documentation/RelNotes/2.14.6.txt b/Documentation/RelNotes/2.14.6.txt new file mode 100644 index 0000000000..72b7af6799 --- /dev/null +++ b/Documentation/RelNotes/2.14.6.txt @@ -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. diff --git a/Documentation/RelNotes/2.15.4.txt b/Documentation/RelNotes/2.15.4.txt new file mode 100644 index 0000000000..dc241cba34 --- /dev/null +++ b/Documentation/RelNotes/2.15.4.txt @@ -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`. diff --git a/Documentation/RelNotes/2.16.6.txt b/Documentation/RelNotes/2.16.6.txt new file mode 100644 index 0000000000..438306e60b --- /dev/null +++ b/Documentation/RelNotes/2.16.6.txt @@ -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. diff --git a/Documentation/RelNotes/2.17.3.txt b/Documentation/RelNotes/2.17.3.txt new file mode 100644 index 0000000000..5a46c94271 --- /dev/null +++ b/Documentation/RelNotes/2.17.3.txt @@ -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. diff --git a/Documentation/RelNotes/2.18.2.txt b/Documentation/RelNotes/2.18.2.txt new file mode 100644 index 0000000000..98b168aade --- /dev/null +++ b/Documentation/RelNotes/2.18.2.txt @@ -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. diff --git a/Documentation/RelNotes/2.19.3.txt b/Documentation/RelNotes/2.19.3.txt new file mode 100644 index 0000000000..92d7f89de6 --- /dev/null +++ b/Documentation/RelNotes/2.19.3.txt @@ -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. diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index e81117d27f..9e3b9581d7 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -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 ~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 312b6f9259..164995d1af 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -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. diff --git a/builtin/clone.c b/builtin/clone.c index 15b142d646..5d31bea55e 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -768,7 +768,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"); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index d38113a31a..b93d624a85 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -18,6 +18,7 @@ #include "diffcore.h" #include "diff.h" #include "object-store.h" +#include "dir.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -1354,7 +1355,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[] = { @@ -1381,6 +1382,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() }; @@ -1408,6 +1411,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); @@ -1419,6 +1426,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); @@ -1531,6 +1540,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; @@ -1549,7 +1559,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, 0} @@ -1676,6 +1686,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); @@ -1866,6 +1878,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() }; diff --git a/compat/mingw.c b/compat/mingw.c index 34b3880b29..a010e0b69d 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -389,6 +389,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); @@ -462,7 +468,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; @@ -470,6 +476,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"; @@ -536,6 +547,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 || @@ -558,6 +574,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 || @@ -1051,7 +1072,7 @@ static const char *quote_arg(const char *arg) p++; len++; } - if (*p == '"') + if (*p == '"' || !*p) n += count*2 + 1; continue; } @@ -1073,16 +1094,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; } @@ -2275,6 +2299,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); @@ -2416,6 +2464,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). diff --git a/compat/mingw.h b/compat/mingw.h index 8c24ddaa3e..163ae1b59e 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -443,8 +443,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) @@ -480,6 +480,20 @@ extern char *mingw_query_user_email(void); #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. * * To support repositories with legacy-encoded file names, invalid UTF-8 bytes diff --git a/config.mak.uname b/config.mak.uname index 3ee7da0e23..85af9f9cf1 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -396,7 +396,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 @@ -529,7 +528,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_OBJS += compat/mingw.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o - BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1 + BASIC_CFLAGS += -DWIN32 EXTLIBS += -lws2_32 GITLIBS += git.res PTHREAD_LIBS = @@ -514,7 +514,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) diff --git a/environment.c b/environment.c index 3465597707..c7b76d3bbb 100644 --- a/environment.c +++ b/environment.c @@ -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; diff --git a/fast-import.c b/fast-import.c index 69886687ce..c8b372bc4a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -364,6 +364,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; @@ -1826,6 +1827,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); @@ -3198,7 +3205,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; } @@ -3239,7 +3245,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) @@ -3282,10 +3287,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; } @@ -3293,6 +3300,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; @@ -3300,10 +3314,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 */ @@ -3430,6 +3447,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; @@ -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; diff --git a/git-compat-util.h b/git-compat-util.h index 09b0102cae..d17360e897 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -385,6 +385,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) { diff --git a/git-submodule.sh b/git-submodule.sh index 5e608f8bad..65d62c888c 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -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" $? |