summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes/2.14.6.txt54
-rw-r--r--Documentation/RelNotes/2.15.4.txt11
-rw-r--r--Documentation/RelNotes/2.16.6.txt8
-rw-r--r--Documentation/RelNotes/2.17.3.txt12
-rw-r--r--Documentation/RelNotes/2.18.2.txt8
-rw-r--r--Documentation/git-fast-import.txt15
-rw-r--r--Documentation/gitmodules.txt5
-rw-r--r--builtin/clone.c2
-rw-r--r--builtin/submodule--helper.c18
-rw-r--r--compat/mingw.c100
-rw-r--r--compat/mingw.h18
-rw-r--r--config.mak.uname3
-rw-r--r--connect.c2
-rw-r--r--environment.c2
-rw-r--r--fast-import.c39
-rw-r--r--fsck.c25
-rw-r--r--git-compat-util.h4
-rwxr-xr-xgit-submodule.sh6
-rw-r--r--path.c96
-rw-r--r--read-cache.c11
-rw-r--r--submodule-config.c12
-rw-r--r--submodule.c49
-rw-r--r--submodule.h5
-rw-r--r--t/helper/test-drop-caches.c11
-rw-r--r--t/helper/test-path-utils.c113
-rw-r--r--t/helper/test-run-command.c138
-rwxr-xr-xt/t0060-path-utils.sh32
-rwxr-xr-xt/t1014-read-tree-confusing.sh1
-rwxr-xr-xt/t1450-fsck.sh1
-rwxr-xr-xt/t6130-pathspec-noglob.sh1
-rwxr-xr-xt/t7406-submodule-update.sh28
-rwxr-xr-xt/t7415-submodule-names.sh56
-rwxr-xr-xt/t7416-submodule-dash-url.sh14
-rwxr-xr-xt/t7417-submodule-path-url.sh17
-rwxr-xr-xt/t9300-fast-import.sh58
-rwxr-xr-xt/t9350-fast-export.sh3
-rw-r--r--transport-helper.c1
-rw-r--r--tree-walk.c6
-rw-r--r--unpack-trees.c3
39 files changed, 906 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/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 fd2c3ef090..4843c57723 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -767,7 +767,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 5e6f2db4cd..382e823b3c 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)
@@ -1346,7 +1347,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[] = {
@@ -1373,6 +1374,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()
};
@@ -1400,6 +1403,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);
@@ -1411,6 +1418,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);
@@ -1459,6 +1468,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;
@@ -1474,7 +1484,7 @@ struct submodule_update_clone {
int failed_clones_nr, failed_clones_alloc;
};
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
- SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
+ SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \
NULL, NULL, NULL, \
STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
@@ -1593,6 +1603,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);
@@ -1748,6 +1760,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 18caf21969..886d60a46c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -333,6 +333,12 @@ int mingw_mkdir(const char *path, int mode)
{
int ret;
wchar_t wpath[MAX_PATH];
+
+ if (!is_valid_win32_path(path)) {
+ errno = EINVAL;
+ return -1;
+ }
+
if (xutftowcs_path(wpath, path) < 0)
return -1;
ret = _wmkdir(wpath);
@@ -406,7 +412,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;
@@ -414,6 +420,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";
@@ -480,6 +491,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 ||
@@ -502,6 +518,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 ||
@@ -950,7 +971,7 @@ static const char *quote_arg(const char *arg)
p++;
len++;
}
- if (*p == '"')
+ if (*p == '"' || !*p)
n += count*2 + 1;
continue;
}
@@ -972,16 +993,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;
}
@@ -2043,6 +2067,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);
@@ -2184,6 +2232,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 571019d0bd..5b43e6557e 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -397,8 +397,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)
@@ -432,6 +432,20 @@ int mingw_offset_1st_component(const char *path);
#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 e47af72e01..d0bd271ff6 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -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
@@ -538,7 +537,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 =
diff --git a/connect.c b/connect.c
index 94547e5056..93778aa25b 100644
--- a/connect.c
+++ b/connect.c
@@ -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 3f3c8746c2..8c34e99f4e 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 89bb0c9db3..7321ccca57 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -363,6 +363,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;
@@ -1825,6 +1826,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);
@@ -3197,7 +3204,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;
}
@@ -3238,7 +3244,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)
@@ -3281,10 +3286,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;
}
@@ -3292,6 +3299,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;
@@ -3299,10 +3313,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 */
@@ -3429,6 +3446,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;
diff --git a/fsck.c b/fsck.c
index d44c46c527..78ce79cfee 100644
--- a/fsck.c
+++ b/fsck.c
@@ -69,6 +69,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) \
@@ -611,7 +612,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);
@@ -633,6 +634,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;
@@ -1006,6 +1023,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 5f2e90932f..16316c62e5 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -374,6 +374,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 1cb2c0a31b..4bc2f9d752 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -34,6 +34,7 @@ reference=
cached=
recursive=
init=
+require_init=
files=
remote=
nofetch=