mirror of
https://github.com/git/git
synced 2024-11-05 01:58:18 +00:00
Sync with 2.43.4
* maint-2.43: (40 commits) Git 2.43.4 Git 2.42.2 Git 2.41.1 Git 2.40.2 Git 2.39.4 fsck: warn about symlink pointing inside a gitdir core.hooksPath: add some protection while cloning init.templateDir: consider this config setting protected clone: prevent hooks from running during a clone Add a helper function to compare file contents init: refactor the template directory discovery into its own function find_hook(): refactor the `STRIP_EXTENSION` logic clone: when symbolic links collide with directories, keep the latter entry: report more colliding paths t5510: verify that D/F confusion cannot lead to an RCE submodule: require the submodule path to contain directories only clone_submodule: avoid using `access()` on directories submodules: submodule paths must not contain symlinks clone: prevent clashing git dirs when cloning submodule in parallel t7423: add tests for symlinked submodule directories ...
This commit is contained in:
commit
e5e6663e69
45 changed files with 1281 additions and 87 deletions
79
Documentation/RelNotes/2.39.4.txt
Normal file
79
Documentation/RelNotes/2.39.4.txt
Normal file
|
@ -0,0 +1,79 @@
|
|||
Git v2.39.4 Release Notes
|
||||
=========================
|
||||
|
||||
This addresses the security issues CVE-2024-32002, CVE-2024-32004,
|
||||
CVE-2024-32020 and CVE-2024-32021.
|
||||
|
||||
This release also backports fixes necessary to let the CI builds pass
|
||||
successfully.
|
||||
|
||||
Fixes since v2.39.3
|
||||
-------------------
|
||||
|
||||
* CVE-2024-32002:
|
||||
|
||||
Recursive clones on case-insensitive filesystems that support symbolic
|
||||
links are susceptible to case confusion that can be exploited to
|
||||
execute just-cloned code during the clone operation.
|
||||
|
||||
* CVE-2024-32004:
|
||||
|
||||
Repositories can be configured to execute arbitrary code during local
|
||||
clones. To address this, the ownership checks introduced in v2.30.3
|
||||
are now extended to cover cloning local repositories.
|
||||
|
||||
* CVE-2024-32020:
|
||||
|
||||
Local clones may end up hardlinking files into the target repository's
|
||||
object database when source and target repository reside on the same
|
||||
disk. If the source repository is owned by a different user, then
|
||||
those hardlinked files may be rewritten at any point in time by the
|
||||
untrusted user.
|
||||
|
||||
* CVE-2024-32021:
|
||||
|
||||
When cloning a local source repository that contains symlinks via the
|
||||
filesystem, Git may create hardlinks to arbitrary user-readable files
|
||||
on the same filesystem as the target repository in the objects/
|
||||
directory.
|
||||
|
||||
* CVE-2024-32465:
|
||||
|
||||
It is supposed to be safe to clone untrusted repositories, even those
|
||||
unpacked from zip archives or tarballs originating from untrusted
|
||||
sources, but Git can be tricked to run arbitrary code as part of the
|
||||
clone.
|
||||
|
||||
* Defense-in-depth: submodule: require the submodule path to contain
|
||||
directories only.
|
||||
|
||||
* Defense-in-depth: clone: when symbolic links collide with directories, keep
|
||||
the latter.
|
||||
|
||||
* Defense-in-depth: clone: prevent hooks from running during a clone.
|
||||
|
||||
* Defense-in-depth: core.hooksPath: add some protection while cloning.
|
||||
|
||||
* Defense-in-depth: fsck: warn about symlink pointing inside a gitdir.
|
||||
|
||||
* Various fix-ups on HTTP tests.
|
||||
|
||||
* Test update.
|
||||
|
||||
* HTTP Header redaction code has been adjusted for a newer version of
|
||||
cURL library that shows its traces differently from earlier
|
||||
versions.
|
||||
|
||||
* Fix was added to work around a regression in libcURL 8.7.0 (which has
|
||||
already been fixed in their tip of the tree).
|
||||
|
||||
* Replace macos-12 used at GitHub CI with macos-13.
|
||||
|
||||
* ci(linux-asan/linux-ubsan): let's save some time
|
||||
|
||||
* Tests with LSan from time to time seem to emit harmless message that makes
|
||||
our tests unnecessarily flakey; we work it around by filtering the
|
||||
uninteresting output.
|
||||
|
||||
* Update GitHub Actions jobs to avoid warnings against using deprecated
|
||||
version of Node.js.
|
7
Documentation/RelNotes/2.40.2.txt
Normal file
7
Documentation/RelNotes/2.40.2.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Git v2.40.2 Release Notes
|
||||
=========================
|
||||
|
||||
This release merges up the fix that appears in v2.39.4 to address
|
||||
the security issues CVE-2024-32002, CVE-2024-32004, CVE-2024-32020,
|
||||
CVE-2024-32021 and CVE-2024-32465; see the release notes for that
|
||||
version for details.
|
7
Documentation/RelNotes/2.41.1.txt
Normal file
7
Documentation/RelNotes/2.41.1.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Git v2.41.1 Release Notes
|
||||
=========================
|
||||
|
||||
This release merges up the fix that appears in v2.39.4 and v2.40.2
|
||||
to address the security issues CVE-2024-32002, CVE-2024-32004,
|
||||
CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465; see the release
|
||||
notes for these versions for details.
|
7
Documentation/RelNotes/2.42.2.txt
Normal file
7
Documentation/RelNotes/2.42.2.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Git v2.42.2 Release Notes
|
||||
=========================
|
||||
|
||||
This release merges up the fix that appears in v2.39.4, v2.40.2
|
||||
and v2.41.1 to address the security issues CVE-2024-32002,
|
||||
CVE-2024-32004, CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465;
|
||||
see the release notes for these versions for details.
|
7
Documentation/RelNotes/2.43.4.txt
Normal file
7
Documentation/RelNotes/2.43.4.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Git v2.43.4 Release Notes
|
||||
=========================
|
||||
|
||||
This release merges up the fix that appears in v2.39.4, v2.40.2,
|
||||
v2.41.1 and v2.42.2 to address the security issues CVE-2024-32002,
|
||||
CVE-2024-32004, CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465;
|
||||
see the release notes for these versions for details.
|
|
@ -164,6 +164,18 @@
|
|||
`nullSha1`::
|
||||
(WARN) Tree contains entries pointing to a null sha1.
|
||||
|
||||
`symlinkPointsToGitDir`::
|
||||
(WARN) Symbolic link points inside a gitdir.
|
||||
|
||||
`symlinkTargetBlob`::
|
||||
(ERROR) A non-blob found instead of a symbolic link's target.
|
||||
|
||||
`symlinkTargetLength`::
|
||||
(WARN) Symbolic link target longer than maximum path length.
|
||||
|
||||
`symlinkTargetMissing`::
|
||||
(ERROR) Unable to read symbolic link target's blob.
|
||||
|
||||
`treeNotSorted`::
|
||||
(ERROR) A tree is not properly sorted.
|
||||
|
||||
|
|
|
@ -55,6 +55,37 @@ ENVIRONMENT
|
|||
admins may need to configure some transports to allow this
|
||||
variable to be passed. See the discussion in linkgit:git[1].
|
||||
|
||||
`GIT_NO_LAZY_FETCH`::
|
||||
When cloning or fetching from a partial repository (i.e., one
|
||||
itself cloned with `--filter`), the server-side `upload-pack`
|
||||
may need to fetch extra objects from its upstream in order to
|
||||
complete the request. By default, `upload-pack` will refuse to
|
||||
perform such a lazy fetch, because `git fetch` may run arbitrary
|
||||
commands specified in configuration and hooks of the source
|
||||
repository (and `upload-pack` tries to be safe to run even in
|
||||
untrusted `.git` directories).
|
||||
+
|
||||
This is implemented by having `upload-pack` internally set the
|
||||
`GIT_NO_LAZY_FETCH` variable to `1`. If you want to override it
|
||||
(because you are fetching from a partial clone, and you are sure
|
||||
you trust it), you can explicitly set `GIT_NO_LAZY_FETCH` to
|
||||
`0`.
|
||||
|
||||
SECURITY
|
||||
--------
|
||||
|
||||
Most Git commands should not be run in an untrusted `.git` directory
|
||||
(see the section `SECURITY` in linkgit:git[1]). `upload-pack` tries to
|
||||
avoid any dangerous configuration options or hooks from the repository
|
||||
it's serving, making it safe to clone an untrusted directory and run
|
||||
commands on the resulting clone.
|
||||
|
||||
For an extra level of safety, you may be able to run `upload-pack` as an
|
||||
alternate user. The details will be platform dependent, but on many
|
||||
systems you can run:
|
||||
|
||||
git clone --no-local --upload-pack='sudo -u nobody git-upload-pack' ...
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:gitnamespaces[7]
|
||||
|
|
|
@ -1049,6 +1049,37 @@ The index is also capable of storing multiple entries (called "stages")
|
|||
for a given pathname. These stages are used to hold the various
|
||||
unmerged version of a file when a merge is in progress.
|
||||
|
||||
SECURITY
|
||||
--------
|
||||
|
||||
Some configuration options and hook files may cause Git to run arbitrary
|
||||
shell commands. Because configuration and hooks are not copied using
|
||||
`git clone`, it is generally safe to clone remote repositories with
|
||||
untrusted content, inspect them with `git log`, and so on.
|
||||
|
||||
However, it is not safe to run Git commands in a `.git` directory (or
|
||||
the working tree that surrounds it) when that `.git` directory itself
|
||||
comes from an untrusted source. The commands in its config and hooks
|
||||
are executed in the usual way.
|
||||
|
||||
By default, Git will refuse to run when the repository is owned by
|
||||
someone other than the user running the command. See the entry for
|
||||
`safe.directory` in linkgit:git-config[1]. While this can help protect
|
||||
you in a multi-user environment, note that you can also acquire
|
||||
untrusted repositories that are owned by you (for example, if you
|
||||
extract a zip file or tarball from an untrusted source). In such cases,
|
||||
you'd need to "sanitize" the untrusted repository first.
|
||||
|
||||
If you have an untrusted `.git` directory, you should first clone it
|
||||
with `git clone --no-local` to obtain a clean copy. Git does restrict
|
||||
the set of options and hooks that will be run by `upload-pack`, which
|
||||
handles the server side of a clone or fetch, but beware that the
|
||||
surface area for attack against `upload-pack` is large, so this does
|
||||
carry some risk. The safest thing is to serve the repository as an
|
||||
unprivileged user (either via linkgit:git-daemon[1], ssh, or using
|
||||
other tools to change user ids). See the discussion in the `SECURITY`
|
||||
section of linkgit:git-upload-pack[1].
|
||||
|
||||
FURTHER DOCUMENTATION
|
||||
---------------------
|
||||
|
||||
|
|
2
INSTALL
2
INSTALL
|
@ -139,7 +139,7 @@ Issues of note:
|
|||
not need that functionality, use NO_CURL to build without
|
||||
it.
|
||||
|
||||
Git requires version "7.19.5" or later of "libcurl" to build
|
||||
Git requires version "7.21.3" or later of "libcurl" to build
|
||||
without NO_CURL. This version requirement may be bumped in
|
||||
the future.
|
||||
|
||||
|
|
|
@ -329,7 +329,20 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
|||
int src_len, dest_len;
|
||||
struct dir_iterator *iter;
|
||||
int iter_status;
|
||||
struct strbuf realpath = STRBUF_INIT;
|
||||
|
||||
/*
|
||||
* Refuse copying directories by default which aren't owned by us. The
|
||||
* code that performs either the copying or hardlinking is not prepared
|
||||
* to handle various edge cases where an adversary may for example
|
||||
* racily swap out files for symlinks. This can cause us to
|
||||
* inadvertently use the wrong source file.
|
||||
*
|
||||
* Furthermore, even if we were prepared to handle such races safely,
|
||||
* creating hardlinks across user boundaries is an inherently unsafe
|
||||
* operation as the hardlinked files can be rewritten at will by the
|
||||
* potentially-untrusted user. We thus refuse to do so by default.
|
||||
*/
|
||||
die_upon_dubious_ownership(NULL, NULL, src_repo);
|
||||
|
||||
mkdir_if_missing(dest->buf, 0777);
|
||||
|
||||
|
@ -377,9 +390,27 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
|||
if (unlink(dest->buf) && errno != ENOENT)
|
||||
die_errno(_("failed to unlink '%s'"), dest->buf);
|
||||
if (!option_no_hardlinks) {
|
||||
strbuf_realpath(&realpath, src->buf, 1);
|
||||
if (!link(realpath.buf, dest->buf))
|
||||
if (!link(src->buf, dest->buf)) {
|
||||
struct stat st;
|
||||
|
||||
/*
|
||||
* Sanity-check whether the created hardlink
|
||||
* actually links to the expected file now. This
|
||||
* catches time-of-check-time-of-use bugs in
|
||||
* case the source file was meanwhile swapped.
|
||||
*/
|
||||
if (lstat(dest->buf, &st))
|
||||
die(_("hardlink cannot be checked at '%s'"), dest->buf);
|
||||
if (st.st_mode != iter->st.st_mode ||
|
||||
st.st_ino != iter->st.st_ino ||
|
||||
st.st_dev != iter->st.st_dev ||
|
||||
st.st_size != iter->st.st_size ||
|
||||
st.st_uid != iter->st.st_uid ||
|
||||
st.st_gid != iter->st.st_gid)
|
||||
die(_("hardlink different from source at '%s'"), dest->buf);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (option_local > 0)
|
||||
die_errno(_("failed to create link '%s'"), dest->buf);
|
||||
option_no_hardlinks = 1;
|
||||
|
@ -392,8 +423,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
|||
strbuf_setlen(src, src_len);
|
||||
die(_("failed to iterate over '%s'"), src->buf);
|
||||
}
|
||||
|
||||
strbuf_release(&realpath);
|
||||
}
|
||||
|
||||
static void clone_local(const char *src_repo, const char *dest_repo)
|
||||
|
@ -936,6 +965,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
int hash_algo;
|
||||
unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
|
||||
const int do_not_override_repo_unix_permissions = -1;
|
||||
const char *template_dir;
|
||||
char *template_dir_dup = NULL;
|
||||
|
||||
struct transport_ls_refs_options transport_ls_refs_options =
|
||||
TRANSPORT_LS_REFS_OPTIONS_INIT;
|
||||
|
@ -955,6 +986,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
usage_msg_opt(_("You must specify a repository to clone."),
|
||||
builtin_clone_usage, builtin_clone_options);
|
||||
|
||||
xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */);
|
||||
template_dir = get_template_dir(option_template);
|
||||
if (*template_dir && !is_absolute_path(template_dir))
|
||||
template_dir = template_dir_dup =
|
||||
absolute_pathdup(template_dir);
|
||||
xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1);
|
||||
|
||||
if (option_depth || option_since || option_not.nr)
|
||||
deepen = 1;
|
||||
if (option_single_branch == -1)
|
||||
|
@ -1116,7 +1154,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
* repository, and reference backends may persist that information into
|
||||
* their on-disk data structures.
|
||||
*/
|
||||
init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
|
||||
init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN,
|
||||
ref_storage_format, NULL,
|
||||
do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
|
||||
|
||||
|
@ -1460,6 +1498,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
free(dir);
|
||||
free(path);
|
||||
free(repo_to_free);
|
||||
free(template_dir_dup);
|
||||
junk_mode = JUNK_LEAVE_ALL;
|
||||
|
||||
transport_ls_refs_options_release(&transport_ls_refs_options);
|
||||
|
|
|
@ -303,6 +303,9 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
|
|||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
char *displaypath;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
displaypath = get_submodule_displaypath(path, info->prefix,
|
||||
info->super_prefix);
|
||||
|
||||
|
@ -634,6 +637,9 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
|
|||
.free_removed_argv_elements = 1,
|
||||
};
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
if (!submodule_from_path(the_repository, null_oid(), path))
|
||||
die(_("no submodule mapping found in .gitmodules for path '%s'"),
|
||||
path);
|
||||
|
@ -1238,6 +1244,9 @@ static void sync_submodule(const char *path, const char *prefix,
|
|||
if (!is_submodule_active(the_repository, path))
|
||||
return;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
sub = submodule_from_path(the_repository, null_oid(), path);
|
||||
|
||||
if (sub && sub->url) {
|
||||
|
@ -1381,6 +1390,9 @@ static void deinit_submodule(const char *path, const char *prefix,
|
|||
struct strbuf sb_config = STRBUF_INIT;
|
||||
char *sub_git_dir = xstrfmt("%s/.git", path);
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
sub = submodule_from_path(the_repository, null_oid(), path);
|
||||
|
||||
if (!sub || !sub->name)
|
||||
|
@ -1662,16 +1674,42 @@ static char *clone_submodule_sm_gitdir(const char *name)
|
|||
return sm_gitdir;
|
||||
}
|
||||
|
||||
static int dir_contains_only_dotgit(const char *path)
|
||||
{
|
||||
DIR *dir = opendir(path);
|
||||
struct dirent *e;
|
||||
int ret = 1;
|
||||
|
||||
if (!dir)
|
||||
return 0;
|
||||
|
||||
e = readdir_skip_dot_and_dotdot(dir);
|
||||
if (!e)
|
||||
ret = 0;
|
||||
else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) ||
|
||||
(e = readdir_skip_dot_and_dotdot(dir))) {
|
||||
error("unexpected item '%s' in '%s'", e->d_name, path);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int clone_submodule(const struct module_clone_data *clone_data,
|
||||
struct string_list *reference)
|
||||
{
|
||||
char *p;
|
||||
char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name);
|
||||
char *sm_alternate = NULL, *error_strategy = NULL;
|
||||
struct stat st;
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
const char *clone_data_path = clone_data->path;
|
||||
char *to_free = NULL;
|
||||
|
||||
if (validate_submodule_path(clone_data_path) < 0)
|
||||
exit(128);
|
||||
|
||||
if (!is_absolute_path(clone_data->path))
|
||||
clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(),
|
||||
clone_data->path);
|
||||
|
@ -1681,6 +1719,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,
|
|||
"git dir"), sm_gitdir);
|
||||
|
||||
if (!file_exists(sm_gitdir)) {
|
||||
if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||
!is_empty_dir(clone_data_path))
|
||||
die(_("directory not empty: '%s'"), clone_data_path);
|
||||
|
||||
if (safe_create_leading_directories_const(sm_gitdir) < 0)
|
||||
die(_("could not create directory '%s'"), sm_gitdir);
|
||||
|
||||
|
@ -1725,10 +1767,18 @@ static int clone_submodule(const struct module_clone_data *clone_data,
|
|||
if(run_command(&cp))
|
||||
die(_("clone of '%s' into submodule path '%s' failed"),
|
||||
clone_data->url, clone_data_path);
|
||||
|
||||
if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||
!dir_contains_only_dotgit(clone_data_path)) {
|
||||
char *dot_git = xstrfmt("%s/.git", clone_data_path);
|
||||
unlink(dot_git);
|
||||
free(dot_git);
|
||||
die(_("directory not empty: '%s'"), clone_data_path);
|
||||
}
|
||||
} else {
|
||||
char *path;
|
||||
|
||||
if (clone_data->require_init && !access(clone_data_path, X_OK) &&
|
||||
if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||
!is_empty_dir(clone_data_path))
|
||||
die(_("directory not empty: '%s'"), clone_data_path);
|
||||
if (safe_create_leading_directories_const(clone_data_path) < 0)
|
||||
|
@ -1738,6 +1788,23 @@ static int clone_submodule(const struct module_clone_data *clone_data,
|
|||
free(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* We already performed this check at the beginning of this function,
|
||||
* before cloning the objects. This tries to detect racy behavior e.g.
|
||||
* in parallel clones, where another process could easily have made the
|
||||
* gitdir nested _after_ it was created.
|
||||
*
|
||||
* To prevent further harm coming from this unintentionally-nested
|
||||
* gitdir, let's disable it by deleting the `HEAD` file.
|
||||
*/
|
||||
if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) {
|
||||
char *head = xstrfmt("%s/HEAD", sm_gitdir);
|
||||
unlink(head);
|
||||
free(head);
|
||||
die(_("refusing to create/use '%s' in another submodule's "
|
||||
"git dir"), sm_gitdir);
|
||||
}
|
||||
|
||||
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
|
||||
|
||||
p = git_pathdup_submodule(clone_data_path, "config");
|
||||
|
@ -2517,6 +2584,9 @@ static int update_submodule(struct update_data *update_data)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (validate_submodule_path(update_data->sm_path) < 0)
|
||||
return -1;
|
||||
|
||||
ret = determine_submodule_update_strategy(the_repository,
|
||||
update_data->just_cloned,
|
||||
update_data->sm_path,
|
||||
|
@ -2624,12 +2694,21 @@ static int update_submodules(struct update_data *update_data)
|
|||
|
||||
for (i = 0; i < suc.update_clone_nr; i++) {
|
||||
struct update_clone_data ucd = suc.update_clone[i];
|
||||
int code;
|
||||
int code = 128;
|
||||
|
||||
oidcpy(&update_data->oid, &ucd.oid);
|
||||
update_data->just_cloned = ucd.just_cloned;
|
||||
update_data->sm_path = ucd.sub->path;
|
||||
|
||||
/*
|
||||
* Verify that the submodule path does not contain any
|
||||
* symlinks; if it does, it might have been tampered with.
|
||||
* TODO: allow exempting it via
|
||||
* `safe.submodule.path` or something
|
||||
*/
|
||||
if (validate_submodule_path(update_data->sm_path) < 0)
|
||||
goto fail;
|
||||
|
||||
code = ensure_core_worktree(update_data->sm_path);
|
||||
if (code)
|
||||
goto fail;
|
||||
|
@ -3356,6 +3435,9 @@ static int module_add(int argc, const char **argv, const char *prefix)
|
|||
normalize_path_copy(add_data.sm_path, add_data.sm_path);
|
||||
strip_dir_trailing_slashes(add_data.sm_path);
|
||||
|
||||
if (validate_submodule_path(add_data.sm_path) < 0)
|
||||
exit(128);
|
||||
|
||||
die_on_index_match(add_data.sm_path, force);
|
||||
die_on_repo_without_commits(add_data.sm_path);
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
|
|||
|
||||
packet_trace_identity("upload-pack");
|
||||
disable_replace_refs();
|
||||
/* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */
|
||||
xsetenv("GIT_NO_LAZY_FETCH", "1", 0);
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);
|
||||
|
||||
|
|
13
config.c
13
config.c
|
@ -1411,8 +1411,19 @@ static int git_default_core_config(const char *var, const char *value,
|
|||
if (!strcmp(var, "core.attributesfile"))
|
||||
return git_config_pathname(&git_attributes_file, var, value);
|
||||
|
||||
if (!strcmp(var, "core.hookspath"))
|
||||
if (!strcmp(var, "core.hookspath")) {
|
||||
if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL &&
|
||||
git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0))
|
||||
die(_("active `core.hooksPath` found in the local "
|
||||
"repository config:\n\t%s\nFor security "
|
||||
"reasons, this is disallowed by default.\nIf "
|
||||
"this is intentional and the hook should "
|
||||
"actually be run, please\nrun the command "
|
||||
"again with "
|
||||
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
|
||||
value);
|
||||
return git_config_pathname(&git_hooks_path, var, value);
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.bare")) {
|
||||
is_bare_repository_cfg = git_config_bool(var, value);
|
||||
|
|
61
copy.c
61
copy.c
|
@ -1,6 +1,9 @@
|
|||
#include "git-compat-util.h"
|
||||
#include "copy.h"
|
||||
#include "path.h"
|
||||
#include "gettext.h"
|
||||
#include "strbuf.h"
|
||||
#include "abspath.h"
|
||||
|
||||
int copy_fd(int ifd, int ofd)
|
||||
{
|
||||
|
@ -67,3 +70,61 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
|
|||
return copy_times(dst, src);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int do_symlinks_match(const char *path1, const char *path2)
|
||||
{
|
||||
struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
|
||||
int ret = 0;
|
||||
|
||||
if (!strbuf_readlink(&buf1, path1, 0) &&
|
||||
!strbuf_readlink(&buf2, path2, 0))
|
||||
ret = !strcmp(buf1.buf, buf2.buf);
|
||||
|
||||
strbuf_release(&buf1);
|
||||
strbuf_release(&buf2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int do_files_match(const char *path1, const char *path2)
|
||||
{
|
||||
struct stat st1, st2;
|
||||
int fd1 = -1, fd2 = -1, ret = 1;
|
||||
char buf1[8192], buf2[8192];
|
||||
|
||||
if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 ||
|
||||
fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) {
|
||||
if (fd1 < 0 && errno == ELOOP)
|
||||
/* maybe this is a symbolic link? */
|
||||
return do_symlinks_match(path1, path2);
|
||||
ret = 0;
|
||||
} else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 ||
|
||||
fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
/* to match, neither must be executable, or both */
|
||||
ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111);
|
||||
|
||||
if (ret)
|
||||
ret = st1.st_size == st2.st_size;
|
||||
|
||||
while (ret) {
|
||||
ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1));
|
||||
ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2));
|
||||
|
||||
if (len1 < 0 || len2 < 0 || len1 != len2)
|
||||
ret = 0; /* read error or different file size */
|
||||
else if (!len1) /* len2 is also 0; hit EOF on both */
|
||||
break; /* ret is still true */
|
||||
else
|
||||
ret = !memcmp(buf1, buf2, len1);
|
||||
}
|
||||
|
||||
if (fd1 >= 0)
|
||||
close(fd1);
|
||||
if (fd2 >= 0)
|
||||
close(fd2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
14
copy.h
14
copy.h
|
@ -7,4 +7,18 @@ int copy_fd(int ifd, int ofd);
|
|||
int copy_file(const char *dst, const char *src, int mode);
|
||||
int copy_file_with_time(const char *dst, const char *src, int mode);
|
||||
|
||||
/*
|
||||
* Compare the file mode and contents of two given files.
|
||||
*
|
||||
* If both files are actually symbolic links, the function returns 1 if the link
|
||||
* targets are identical or 0 if they are not.
|
||||
*
|
||||
* If any of the two files cannot be accessed or in case of read failures, this
|
||||
* function returns 0.
|
||||
*
|
||||
* If the file modes and contents are identical, the function returns 1,
|
||||
* otherwise it returns 0.
|
||||
*/
|
||||
int do_files_match(const char *path1, const char *path2);
|
||||
|
||||
#endif /* COPY_H */
|
||||
|
|
12
dir.c
12
dir.c
|
@ -100,6 +100,18 @@ int fspathncmp(const char *a, const char *b, size_t count)
|
|||
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
|
||||
}
|
||||
|
||||
int paths_collide(const char *a, const char *b)
|
||||
{
|
||||
size_t len_a = strlen(a), len_b = strlen(b);
|
||||
|
||||
if (len_a == len_b)
|
||||
return fspatheq(a, b);
|
||||
|
||||
if (len_a < len_b)
|
||||
return is_dir_sep(b[len_a]) && !fspathncmp(a, b, len_a);
|
||||
return is_dir_sep(a[len_b]) && !fspathncmp(a, b, len_b);
|
||||
}
|
||||
|
||||
unsigned int fspathhash(const char *str)
|
||||
{
|
||||
return ignore_case ? strihash(str) : strhash(str);
|
||||
|
|
7
dir.h
7
dir.h
|
@ -548,6 +548,13 @@ int fspatheq(const char *a, const char *b);
|
|||
int fspathncmp(const char *a, const char *b, size_t count);
|
||||
unsigned int fspathhash(const char *str);
|
||||
|
||||
/*
|
||||
* Reports whether paths collide. This may be because the paths differ only in
|
||||
* case on a case-sensitive filesystem, or that one path refers to a symlink
|
||||
* that collides with one of the parent directories of the other.
|
||||
*/
|
||||
int paths_collide(const char *a, const char *b);
|
||||
|
||||
/*
|
||||
* The prefix part of pattern must not contains wildcards.
|
||||
*/
|
||||
|
|
16
entry.c
16
entry.c
|
@ -460,7 +460,7 @@ static void mark_colliding_entries(const struct checkout *state,
|
|||
continue;
|
||||
|
||||
if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
|
||||
(!trust_ino && !fspathcmp(ce->name, dup->name))) {
|
||||
paths_collide(ce->name, dup->name)) {
|
||||
dup->ce_flags |= CE_MATCHED;
|
||||
break;
|
||||
}
|
||||
|
@ -547,6 +547,20 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
|
|||
/* If it is a gitlink, leave it alone! */
|
||||
if (S_ISGITLINK(ce->ce_mode))
|
||||
return 0;
|
||||
/*
|
||||
* We must avoid replacing submodules' leading
|
||||
* directories with symbolic links, lest recursive
|
||||
* clones can write into arbitrary locations.
|
||||
*
|
||||
* Technically, this logic is not limited
|
||||
* to recursive clones, or for that matter to
|
||||
* submodules' paths colliding with symbolic links'
|
||||
* paths. Yet it strikes a balance in favor of
|
||||
* simplicity, and if paths are colliding, we might
|
||||
* just as well keep the directories during a clone.
|
||||
*/
|
||||
if (state->clone && S_ISLNK(ce->ce_mode))
|
||||
return 0;
|
||||
remove_subtree(&path);
|
||||
} else if (unlink(path.buf))
|
||||
return error_errno("unable to unlink old '%s'", path.buf);
|
||||
|
|
56
fsck.c
56
fsck.c
|
@ -656,6 +656,8 @@ static int fsck_tree(const struct object_id *tree_oid,
|
|||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_MAILMAP_SYMLINK,
|
||||
".mailmap is a symlink");
|
||||
oidset_insert(&options->symlink_targets_found,
|
||||
entry_oid);
|
||||
}
|
||||
|
||||
if ((backslash = strchr(name, '\\'))) {
|
||||
|
@ -1164,6 +1166,56 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
|
|||
}
|
||||
}
|
||||
|
||||
if (oidset_contains(&options->symlink_targets_found, oid)) {
|
||||
const char *ptr = buf;
|
||||
const struct object_id *reported = NULL;
|
||||
|
||||
oidset_insert(&options->symlink_targets_done, oid);
|
||||
|
||||
if (!buf || size > PATH_MAX) {
|
||||
/*
|
||||
* A missing buffer here is a sign that the caller found the
|
||||
* blob too gigantic to load into memory. Let's just consider
|
||||
* that an error.
|
||||
*/
|
||||
return report(options, oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_TARGET_LENGTH,
|
||||
"symlink target too long");
|
||||
}
|
||||
|
||||
while (!reported && ptr) {
|
||||
const char *p = ptr;
|
||||
char c, *slash = strchrnul(ptr, '/');
|
||||
char *backslash = memchr(ptr, '\\', slash - ptr);
|
||||
|
||||
c = *slash;
|
||||
*slash = '\0';
|
||||
|
||||
while (!reported && backslash) {
|
||||
*backslash = '\0';
|
||||
if (is_ntfs_dotgit(p))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
*backslash = '\\';
|
||||
p = backslash + 1;
|
||||
backslash = memchr(p, '\\', slash - p);
|
||||
}
|
||||
if (!reported && is_ntfs_dotgit(p))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
|
||||
if (!reported && is_hfs_dotgit(ptr))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
|
||||
*slash = c;
|
||||
ptr = c ? slash + 1 : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1262,6 +1314,10 @@ int fsck_finish(struct fsck_options *options)
|
|||
FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
|
||||
options, ".gitattributes");
|
||||
|
||||
ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done,
|
||||
FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB,
|
||||
options, "<symlink-target>");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
12
fsck.h
12
fsck.h
|
@ -64,6 +64,8 @@ enum fsck_msg_type {
|
|||
FUNC(GITATTRIBUTES_LARGE, ERROR) \
|
||||
FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
|
||||
FUNC(GITATTRIBUTES_BLOB, ERROR) \
|
||||
FUNC(SYMLINK_TARGET_MISSING, ERROR) \
|
||||
FUNC(SYMLINK_TARGET_BLOB, ERROR) \
|
||||
/* warnings */ \
|
||||
FUNC(EMPTY_NAME, WARN) \
|
||||
FUNC(FULL_PATHNAME, WARN) \
|
||||
|
@ -74,6 +76,8 @@ enum fsck_msg_type {
|
|||
FUNC(ZERO_PADDED_FILEMODE, WARN) \
|
||||
FUNC(NUL_IN_COMMIT, WARN) \
|
||||
FUNC(LARGE_PATHNAME, WARN) \
|
||||
FUNC(SYMLINK_TARGET_LENGTH, WARN) \
|
||||
FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \
|
||||
/* infos (reported as warnings, but ignored by default) */ \
|
||||
FUNC(BAD_FILEMODE, INFO) \
|
||||
FUNC(GITMODULES_PARSE, INFO) \
|
||||
|
@ -141,6 +145,8 @@ struct fsck_options {
|
|||
struct oidset gitmodules_done;
|
||||
struct oidset gitattributes_found;
|
||||
struct oidset gitattributes_done;
|
||||
struct oidset symlink_targets_found;
|
||||
struct oidset symlink_targets_done;
|
||||
kh_oid_map_t *object_names;
|
||||
};
|
||||
|
||||
|
@ -150,6 +156,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function \
|
||||
}
|
||||
#define FSCK_OPTIONS_STRICT { \
|
||||
|
@ -158,6 +166,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function, \
|
||||
}
|
||||
#define FSCK_OPTIONS_MISSING_GITMODULES { \
|
||||
|
@ -166,6 +176,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_cb_print_missing_gitmodules, \
|
||||
}
|
||||
|
||||
|
|
|
@ -126,6 +126,15 @@
|
|||
#define GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Versions before curl 7.66.0 (September 2019) required manually setting the
|
||||
* transfer-encoding for a streaming POST; after that this is handled
|
||||
* automatically.
|
||||
*/
|
||||
#if LIBCURL_VERSION_NUM < 0x074200
|
||||
#define GIT_CURL_NEED_TRANSFER_ENCODING_HEADER
|
||||
#endif
|
||||
|
||||
/**
|
||||
* CURLOPT_PROTOCOLS_STR and CURLOPT_REDIR_PROTOCOLS_STR were added in 7.85.0,
|
||||
* released in August 2022.
|
||||
|
|
53
hook.c
53
hook.c
|
@ -7,25 +7,56 @@
|
|||
#include "run-command.h"
|
||||
#include "config.h"
|
||||
#include "strbuf.h"
|
||||
#include "environment.h"
|
||||
#include "setup.h"
|
||||
#include "copy.h"
|
||||
|
||||
static int identical_to_template_hook(const char *name, const char *path)
|
||||
{
|
||||
const char *env = getenv("GIT_CLONE_TEMPLATE_DIR");
|
||||
const char *template_dir = get_template_dir(env && *env ? env : NULL);
|
||||
struct strbuf template_path = STRBUF_INIT;
|
||||
int found_template_hook, ret;
|
||||
|
||||
strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name);
|
||||
found_template_hook = access(template_path.buf, X_OK) >= 0;
|
||||
#ifdef STRIP_EXTENSION
|
||||
if (!found_template_hook) {
|
||||
strbuf_addstr(&template_path, STRIP_EXTENSION);
|
||||
found_template_hook = access(template_path.buf, X_OK) >= 0;
|
||||
}
|
||||
#endif
|
||||
if (!found_template_hook)
|
||||
return 0;
|
||||
|
||||
ret = do_files_match(template_path.buf, path);
|
||||
|
||||
strbuf_release(&template_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *find_hook(const char *name)
|
||||
{
|
||||
static struct strbuf path = STRBUF_INIT;
|
||||
|
||||
int found_hook;
|
||||
|
||||
strbuf_reset(&path);
|
||||
strbuf_git_path(&path, "hooks/%s", name);
|
||||
if (access(path.buf, X_OK) < 0) {
|
||||
found_hook = access(path.buf, X_OK) >= 0;
|
||||
#ifdef STRIP_EXTENSION
|
||||
if (!found_hook) {
|
||||
int err = errno;
|
||||
|
||||
#ifdef STRIP_EXTENSION
|
||||
strbuf_addstr(&path, STRIP_EXTENSION);
|
||||
if (access(path.buf, X_OK) >= 0)
|
||||
return path.buf;
|
||||
if (errno == EACCES)
|
||||
err = errno;
|
||||
found_hook = access(path.buf, X_OK) >= 0;
|
||||
if (!found_hook)
|
||||
errno = err;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
|
||||
if (!found_hook) {
|
||||
if (errno == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
|
||||
static struct string_list advise_given = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (!string_list_lookup(&advise_given, name)) {
|
||||
|
@ -39,6 +70,14 @@ const char *find_hook(const char *name)
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) &&
|
||||
!identical_to_template_hook(name, path.buf))
|
||||
die(_("active `%s` hook found during `git clone`:\n\t%s\n"
|
||||
"For security reasons, this is disallowed by default.\n"
|
||||
"If this is intentional and the hook should actually "
|
||||
"be run, please\nrun the command again with "
|
||||
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
|
||||
name, path.buf);
|
||||
return path.buf;
|
||||
}
|
||||
|
||||
|
|
1
http.c
1
http.c
|
@ -1452,6 +1452,7 @@ struct active_request_slot *get_active_slot(void)
|
|||
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, -1L);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1);
|
||||
|
|
2
path.c
2
path.c
|
@ -846,6 +846,7 @@ const char *enter_repo(const char *path, int strict)
|
|||
if (!suffix[i])
|
||||
return NULL;
|
||||
gitfile = read_gitfile(used_path.buf);
|
||||
die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
|
||||
if (gitfile) {
|
||||
strbuf_reset(&used_path);
|
||||
strbuf_addstr(&used_path, gitfile);
|
||||
|
@ -856,6 +857,7 @@ const char *enter_repo(const char *path, int strict)
|
|||
}
|
||||
else {
|
||||
const char *gitfile = read_gitfile(path);
|
||||
die_upon_dubious_ownership(gitfile, NULL, path);
|
||||
if (gitfile)
|
||||
path = gitfile;
|
||||
if (chdir(path))
|
||||
|
|
|
@ -23,6 +23,16 @@ static int fetch_objects(struct repository *repo,
|
|||
int i;
|
||||
FILE *child_in;
|
||||
|
||||
/* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */
|
||||
if (git_env_bool("GIT_NO_LAZY_FETCH", 0)) {
|
||||
static int warning_shown;
|
||||
if (!warning_shown) {
|
||||
warning_shown = 1;
|
||||
warning(_("lazy fetching disabled; some objects may not be available"));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
child.git_cmd = 1;
|
||||
child.in = -1;
|
||||
if (repo != the_repository)
|
||||
|
|
72
read-cache.c
72
read-cache.c
|
@ -1116,19 +1116,32 @@ static int has_dir_name(struct index_state *istate,
|
|||
istate->cache[istate->cache_nr - 1]->name,
|
||||
&len_eq_last);
|
||||
if (cmp_last > 0) {
|
||||
if (len_eq_last == 0) {
|
||||
if (name[len_eq_last] != '/') {
|
||||
/*
|
||||
* The entry sorts AFTER the last one in the
|
||||
* index and their paths have no common prefix,
|
||||
* so there cannot be a F/D conflict.
|
||||
* index.
|
||||
*
|
||||
* If there were a conflict with "file", then our
|
||||
* name would start with "file/" and the last index
|
||||
* entry would start with "file" but not "file/".
|
||||
*
|
||||
* The next character after common prefix is
|
||||
* not '/', so there can be no conflict.
|
||||
*/
|
||||
return retval;
|
||||
} else {
|
||||
/*
|
||||
* The entry sorts AFTER the last one in the
|
||||
* index, but has a common prefix. Fall through
|
||||
* to the loop below to disect the entry's path
|
||||
* and see where the difference is.
|
||||
* index, and the next character after common
|
||||
* prefix is '/'.
|
||||
*
|
||||
* Either the last index entry is a file in
|
||||
* conflict with this entry, or it has a name
|
||||
* which sorts between this entry and the
|
||||
* potential conflicting file.
|
||||
*
|
||||
* In both cases, we fall through to the loop
|
||||
* below and let the regular search code handle it.
|
||||
*/
|
||||
}
|
||||
} else if (cmp_last == 0) {
|
||||
|
@ -1152,53 +1165,6 @@ static int has_dir_name(struct index_state *istate,
|
|||
}
|
||||
len = slash - name;
|
||||
|
||||
if (cmp_last > 0) {
|
||||
/*
|
||||
* (len + 1) is a directory boundary (including
|
||||
* the trailing slash). And since the loop is
|
||||
* decrementing "slash", the first iteration is
|
||||
* the longest directory prefix; subsequent
|
||||
* iterations consider parent directories.
|
||||
*/
|
||||
|
||||
if (len + 1 <= len_eq_last) {
|
||||
/*
|
||||
* The directory prefix (including the trailing
|
||||
* slash) also appears as a prefix in the last
|
||||
* entry, so the remainder cannot collide (because
|
||||
* strcmp said the whole path was greater).
|
||||
*
|
||||
* EQ: last: xxx/A
|
||||
* this: xxx/B
|
||||
*
|
||||
* LT: last: xxx/file_A
|
||||
* this: xxx/file_B
|
||||
*/
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (len > len_eq_last) {
|
||||
/*
|
||||
* This part of the directory prefix (excluding
|
||||
* the trailing slash) is longer than the known
|
||||
* equal portions, so this sub-directory cannot
|
||||
* collide with a file.
|
||||
*
|
||||
* GT: last: xxxA
|
||||
* this: xxxB/file
|
||||
*/
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a possible collision. Fall through and
|
||||
* let the regular search code handle it.
|
||||
*
|
||||
* last: xxx
|
||||
* this: xxx/file
|
||||
*/
|
||||
}
|
||||
|
||||
pos = index_name_stage_pos(istate, name, len, stage, EXPAND_SPARSE);
|
||||
if (pos >= 0) {
|
||||
/*
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "git-compat-util.h"
|
||||
#include "git-curl-compat.h"
|
||||
#include "config.h"
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
|
@ -960,7 +961,9 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
|
|||
/* The request body is large and the size cannot be predicted.
|
||||
* We must use chunked encoding to send it.
|
||||
*/
|
||||
#ifdef GIT_CURL_NEED_TRANSFER_ENCODING_HEADER
|
||||
headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
|
||||
#endif
|
||||
rpc->initial_buffer = 1;
|
||||
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
|
||||
curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
|
||||
|
|
|
@ -281,6 +281,8 @@ void repo_clear(struct repository *repo)
|
|||
parsed_object_pool_clear(repo->parsed_objects);
|
||||
FREE_AND_NULL(repo->parsed_objects);
|
||||
|
||||
FREE_AND_NULL(repo->settings.fsmonitor);
|
||||
|
||||
if (repo->config) {
|
||||
git_configset_clear(repo->config);
|
||||
FREE_AND_NULL(repo->config);
|
||||
|
|
91
setup.c
91
setup.c
|
@ -16,6 +16,7 @@
|
|||
#include "quote.h"
|
||||
#include "trace2.h"
|
||||
#include "worktree.h"
|
||||
#include "exec-cmd.h"
|
||||
|
||||
static int inside_git_dir = -1;
|
||||
static int inside_work_tree = -1;
|
||||
|
@ -1201,6 +1202,27 @@ static int ensure_valid_ownership(const char *gitfile,
|
|||
return data.is_safe;
|
||||
}
|
||||
|
||||
void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
|
||||
const char *gitdir)
|
||||
{
|
||||
struct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;
|
||||
const char *path;
|
||||
|
||||
if (ensure_valid_ownership(gitfile, worktree, gitdir, &report))
|
||||
return;
|
||||
|
||||
strbuf_complete(&report, '\n');
|
||||
path = gitfile ? gitfile : gitdir;
|
||||
sq_quote_buf_pretty("ed, path);
|
||||
|
||||
die(_("detected dubious ownership in repository at '%s'\n"
|
||||
"%s"
|
||||
"To add an exception for this directory, call:\n"
|
||||
"\n"
|
||||
"\tgit config --global --add safe.directory %s"),
|
||||
path, report.buf, quoted.buf);
|
||||
}
|
||||
|
||||
static int allowed_bare_repo_cb(const char *key, const char *value,
|
||||
const struct config_context *ctx UNUSED,
|
||||
void *d)
|
||||
|
@ -1733,6 +1755,57 @@ int daemonize(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
struct template_dir_cb_data {
|
||||
char *path;
|
||||
int initialized;
|
||||
};
|
||||
|
||||
static int template_dir_cb(const char *key, const char *value,
|
||||
const struct config_context *ctx, void *d)
|
||||
{
|
||||
struct template_dir_cb_data *data = d;
|
||||
|
||||
if (strcmp(key, "init.templatedir"))
|
||||
return 0;
|
||||
|
||||
if (!value) {
|
||||
data->path = NULL;
|
||||
} else {
|
||||
char *path = NULL;
|
||||
|
||||
FREE_AND_NULL(data->path);
|
||||
if (!git_config_pathname((const char **)&path, key, value))
|
||||
data->path = path ? path : xstrdup(value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *get_template_dir(const char *option_template)
|
||||
{
|
||||
const char *template_dir = option_template;
|
||||
|
||||
if (!template_dir)
|
||||
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
|
||||
if (!template_dir) {
|
||||
static struct template_dir_cb_data data;
|
||||
|
||||
if (!data.initialized) {
|
||||
git_protected_config(template_dir_cb, &data);
|
||||
data.initialized = 1;
|
||||
}
|
||||
template_dir = data.path;
|
||||
}
|
||||
if (!template_dir) {
|
||||
static char *dir;
|
||||
|
||||
if (!dir)
|
||||
dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
|
||||
template_dir = dir;
|
||||
}
|
||||
return template_dir;
|
||||
}
|
||||
|
||||
#ifdef NO_TRUSTABLE_FILEMODE
|
||||
#define TEST_FILEMODE 0
|
||||
#else
|
||||
|
@ -1808,8 +1881,9 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
|
|||
}
|
||||
}
|
||||
|
||||
static void copy_templates(const char *template_dir, const char *init_template_dir)
|
||||
static void copy_templates(const char *option_template)
|
||||
{
|
||||
const char *template_dir = get_template_dir(option_template);
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct strbuf template_path = STRBUF_INIT;
|
||||
size_t template_len;
|
||||
|
@ -1818,16 +1892,8 @@ static void copy_templates(const char *template_dir, const char *init_template_d
|
|||
DIR *dir;
|
||||
char *to_free = NULL;
|
||||
|
||||
if (!template_dir)
|
||||
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
|
||||
if (!template_dir)
|
||||
template_dir = init_template_dir;
|
||||
if (!template_dir)
|
||||
template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
|
||||
if (!template_dir[0]) {
|
||||
free(to_free);
|
||||
if (!template_dir || !*template_dir)
|
||||
return;
|
||||
}
|
||||
|
||||
strbuf_addstr(&template_path, template_dir);
|
||||
strbuf_complete(&template_path, '/');
|
||||
|
@ -1969,7 +2035,6 @@ static int create_default_files(const char *template_path,
|
|||
char *path;
|
||||
int reinit;
|
||||
int filemode;
|
||||
const char *init_template_dir = NULL;
|
||||
const char *work_tree = get_git_work_tree();
|
||||
|
||||
/*
|
||||
|
@ -1981,9 +2046,7 @@ static int create_default_files(const char *template_path,
|
|||
* values (since we've just potentially changed what's available on
|
||||
* disk).
|
||||
*/
|
||||
git_config_get_pathname("init.templatedir", &init_template_dir);
|
||||
copy_templates(template_path, init_template_dir);
|
||||
free((char *)init_template_dir);
|
||||
copy_templates(template_path);
|
||||
git_config_clear();
|
||||
reset_shared_repository();
|
||||
git_config(git_default_config, NULL);
|
||||
|
|
14
setup.h
14
setup.h
|
@ -41,6 +41,18 @@ const char *read_gitfile_gently(const char *path, int *return_error_code);
|
|||
const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
|
||||
#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
|
||||
|
||||
/*
|
||||
* Check if a repository is safe and die if it is not, by verifying the
|
||||
* ownership of the worktree (if any), the git directory, and the gitfile (if
|
||||
* any).
|
||||
*
|
||||
* Exemptions for known-safe repositories can be added via `safe.directory`
|
||||
* config settings; for non-bare repositories, their worktree needs to be
|
||||
* added, for bare ones their git directory.
|
||||
*/
|
||||
void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
|
||||
const char *gitdir);
|
||||
|
||||
void setup_work_tree(void);
|
||||
|
||||
/*
|
||||
|
@ -171,6 +183,8 @@ int verify_repository_format(const struct repository_format *format,
|
|||
*/
|
||||
void check_repository_format(struct repository_format *fmt);
|
||||
|
||||
const char *get_template_dir(const char *option_template);
|
||||
|
||||
#define INIT_DB_QUIET (1 << 0)
|
||||
#define INIT_DB_EXIST_OK (1 << 1)
|
||||
#define INIT_DB_SKIP_REFDB (1 << 2)
|
||||
|
|
89
submodule.c
89
submodule.c
|
@ -1010,6 +1010,9 @@ static int submodule_has_commits(struct repository *r,
|
|||
.super_oid = super_oid
|
||||
};
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
oid_array_for_each_unique(commits, check_has_commit, &has_commit);
|
||||
|
||||
if (has_commit.result) {
|
||||
|
@ -1132,6 +1135,9 @@ static int push_submodule(const char *path,
|
|||
const struct string_list *push_options,
|
||||
int dry_run)
|
||||
{
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
strvec_push(&cp.args, "push");
|
||||
|
@ -1181,6 +1187,9 @@ static void submodule_push_check(const char *path, const char *head,
|
|||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
int i;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
strvec_push(&cp.args, "submodule--helper");
|
||||
strvec_push(&cp.args, "push-check");
|
||||
strvec_push(&cp.args, head);
|
||||
|
@ -1512,6 +1521,9 @@ static struct fetch_task *fetch_task_create(struct submodule_parallel_fetch *spf
|
|||
struct fetch_task *task = xmalloc(sizeof(*task));
|
||||
memset(task, 0, sizeof(*task));
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
task->sub = submodule_from_path(spf->r, treeish_name, path);
|
||||
|
||||
if (!task->sub) {
|
||||
|
@ -1884,6 +1896,9 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
|
|||
const char *git_dir;
|
||||
int ignore_cp_exit_code = 0;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
strbuf_addf(&buf, "%s/.git", path);
|
||||
git_dir = read_gitfile(buf.buf);
|
||||
if (!git_dir)
|
||||
|
@ -1960,6 +1975,9 @@ int submodule_uses_gitfile(const char *path)
|
|||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *git_dir;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
strbuf_addf(&buf, "%s/.git", path);
|
||||
git_dir = read_gitfile(buf.buf);
|
||||
if (!git_dir) {
|
||||
|
@ -1999,6 +2017,9 @@ int bad_to_remove_submodule(const char *path, unsigned flags)
|
|||
struct strbuf buf = STRBUF_INIT;
|
||||
int ret = 0;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
if (!file_exists(path) || is_empty_dir(path))
|
||||
return 0;
|
||||
|
||||
|
@ -2049,6 +2070,9 @@ void submodule_unset_core_worktree(const struct submodule *sub)
|
|||
{
|
||||
struct strbuf config_path = STRBUF_INIT;
|
||||
|
||||
if (validate_submodule_path(sub->path) < 0)
|
||||
exit(128);
|
||||
|
||||
submodule_name_to_gitdir(&config_path, the_repository, sub->name);
|
||||
strbuf_addstr(&config_path, "/config");
|
||||
|
||||
|
@ -2063,6 +2087,9 @@ static int submodule_has_dirty_index(const struct submodule *sub)
|
|||
{
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
|
||||
if (validate_submodule_path(sub->path) < 0)
|
||||
exit(128);
|
||||
|
||||
prepare_submodule_repo_env(&cp.env);
|
||||
|
||||
cp.git_cmd = 1;
|
||||
|
@ -2080,6 +2107,10 @@ static int submodule_has_dirty_index(const struct submodule *sub)
|
|||
static void submodule_reset_index(const char *path, const char *super_prefix)
|
||||
{
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
prepare_submodule_repo_env(&cp.env);
|
||||
|
||||
cp.git_cmd = 1;
|
||||
|
@ -2143,10 +2174,27 @@ int submodule_move_head(const char *path, const char *super_prefix,
|
|||
if (!submodule_uses_gitfile(path))
|
||||
absorb_git_dir_into_superproject(path,
|
||||
super_prefix);
|
||||
else {
|
||||
char *dotgit = xstrfmt("%s/.git", path);
|
||||
char *git_dir = xstrdup(read_gitfile(dotgit));
|
||||
|
||||
free(dotgit);
|
||||
if (validate_submodule_git_dir(git_dir,
|
||||
sub->name) < 0)
|
||||
die(_("refusing to create/use '%s' in "
|
||||
"another submodule's git dir"),
|
||||
git_dir);
|
||||
free(git_dir);
|
||||
}
|
||||
} else {
|
||||
struct strbuf gitdir = STRBUF_INIT;
|
||||
submodule_name_to_gitdir(&gitdir, the_repository,
|
||||
sub->name);
|
||||
if (validate_submodule_git_dir(gitdir.buf,
|
||||
sub->name) < 0)
|
||||
die(_("refusing to create/use '%s' in another "
|
||||
"submodule's git dir"),
|
||||
gitdir.buf);
|
||||
connect_work_tree_and_git_dir(path, gitdir.buf, 0);
|
||||
strbuf_release(&gitdir);
|
||||
|
||||
|
@ -2267,6 +2315,34 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int validate_submodule_path(const char *path)
|
||||
{
|
||||
char *p = xstrdup(path);
|
||||
struct stat st;
|
||||
int i, ret = 0;
|
||||
char sep;
|
||||
|
||||
for (i = 0; !ret && p[i]; i++) {
|
||||
if (!is_dir_sep(p[i]))
|
||||
continue;
|
||||
|
||||
sep = p[i];
|
||||
p[i] = '\0';
|
||||
/* allow missing components, but no symlinks */
|
||||
ret = lstat(p, &st) || !S_ISLNK(st.st_mode) ? 0 : -1;
|
||||
p[i] = sep;
|
||||
if (ret)
|
||||
error(_("expected '%.*s' in submodule path '%s' not to "
|
||||
"be a symbolic link"), i, p, p);
|
||||
}
|
||||
if (!lstat(p, &st) && S_ISLNK(st.st_mode))
|
||||
ret = error(_("expected submodule path '%s' not to be a "
|
||||
"symbolic link"), p);
|
||||
free(p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Embeds a single submodules git directory into the superprojects git dir,
|
||||
* non recursively.
|
||||
|
@ -2278,6 +2354,9 @@ static void relocate_single_git_dir_into_superproject(const char *path,
|
|||
struct strbuf new_gitdir = STRBUF_INIT;
|
||||
const struct submodule *sub;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
if (submodule_uses_worktrees(path))
|
||||
die(_("relocate_gitdir for submodule '%s' with "
|
||||
"more than one worktree not supported"), path);
|
||||
|
@ -2319,6 +2398,9 @@ static void absorb_git_dir_into_superproject_recurse(const char *path,
|
|||
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
cp.dir = path;
|
||||
cp.git_cmd = 1;
|
||||
cp.no_stdin = 1;
|
||||
|
@ -2343,6 +2425,10 @@ void absorb_git_dir_into_superproject(const char *path,
|
|||
int err_code;
|
||||
const char *sub_git_dir;
|
||||
struct strbuf gitdir = STRBUF_INIT;
|
||||
|
||||
if (validate_submodule_path(path) < 0)
|
||||
exit(128);
|
||||
|
||||
strbuf_addf(&gitdir, "%s/.git", path);
|
||||
sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code);
|
||||
|
||||
|
@ -2485,6 +2571,9 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
|
|||
const char *git_dir;
|
||||
int ret = 0;
|
||||
|
||||
if (validate_submodule_path(submodule) < 0)
|
||||
exit(128);
|
||||
|
||||
strbuf_reset(buf);
|
||||
strbuf_addstr(buf, submodule);
|
||||
strbuf_complete(buf, '/');
|
||||
|
|
|
@ -148,6 +148,11 @@ void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
|
|||
*/
|
||||
int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
|
||||
|
||||
/*
|
||||
* Make sure that the given submodule path does not follow symlinks.
|
||||
*/
|
||||
int validate_submodule_path(const char *path);
|
||||
|
||||
#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)
|
||||
#define SUBMODULE_MOVE_HEAD_FORCE (1<<1)
|
||||
int submodule_move_head(const char *path, const char *super_prefix,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "string-list.h"
|
||||
#include "trace.h"
|
||||
#include "utf8.h"
|
||||
#include "copy.h"
|
||||
|
||||
/*
|
||||
* A "string_list_each_func_t" function that normalizes an entry from
|
||||
|
@ -500,6 +501,16 @@ int cmd__path_utils(int argc, const char **argv)
|
|||
return !!res;
|
||||
}
|
||||
|
||||
if (argc == 4 && !strcmp(argv[1], "do_files_match")) {
|
||||
int ret = do_files_match(argv[2], argv[3]);
|
||||
|
||||
if (ret)
|
||||
printf("equal\n");
|
||||
else
|
||||
printf("different\n");
|
||||
return !ret;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
|
||||
argv[1] ? argv[1] : "(there was none)");
|
||||
return 1;
|
||||
|
|
|
@ -1201,6 +1201,34 @@ test_expect_success 'very long name in the index handled sanely' '
|
|||
test $len = 4098
|
||||
'
|
||||
|
||||
# D/F conflict checking uses an optimization when adding to the end.
|
||||
# make sure it does not get confused by `a-` sorting _between_
|
||||
# `a` and `a/`.
|
||||
test_expect_success 'more update-index D/F conflicts' '
|
||||
# empty the index to make sure our entry is last
|
||||
git read-tree --empty &&
|
||||
cacheinfo=100644,$(test_oid empty_blob) &&
|
||||
git update-index --add --cacheinfo $cacheinfo,path5/a &&
|
||||
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file &&
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file &&
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file &&
|
||||
|
||||
# "a-" sorts between "a" and "a/"
|
||||
git update-index --add --cacheinfo $cacheinfo,path5/a- &&
|
||||
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file &&
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file &&
|
||||
test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file &&
|
||||
|
||||
cat >expected <<-\EOF &&
|
||||
path5/a
|
||||
path5/a-
|
||||
EOF
|
||||
git ls-files >actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'test_must_fail on a failing git command' '
|
||||
test_must_fail git notacommand
|
||||
'
|
||||
|
|
|
@ -80,4 +80,28 @@ test_expect_success 'safe.directory in included file' '
|
|||
git status
|
||||
'
|
||||
|
||||
test_expect_success 'local clone of unowned repo refused in unsafe directory' '
|
||||
test_when_finished "rm -rf source" &&
|
||||
git init source &&
|
||||
(
|
||||
sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
|
||||
test_commit -C source initial
|
||||
) &&
|
||||
test_must_fail git clone --local source target &&
|
||||
test_path_is_missing target
|
||||
'
|
||||
|
||||
test_expect_success 'local clone of unowned repo accepted in safe directory' '
|
||||
test_when_finished "rm -rf source" &&
|
||||
git init source &&
|
||||
(
|
||||
sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
|
||||
test_commit -C source initial
|
||||
) &&
|
||||
test_must_fail git clone --local source target &&
|
||||
git config --global --add safe.directory "$(pwd)/source/.git" &&
|
||||
git clone --local source target &&
|
||||
test_path_is_dir target
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -610,4 +610,45 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works'
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'do_files_match()' '
|
||||
test_seq 0 10 >0-10.txt &&
|
||||
test_seq -1 10 >-1-10.txt &&
|
||||
test_seq 1 10 >1-10.txt &&
|
||||
test_seq 1 9 >1-9.txt &&
|
||||
test_seq 0 8 >0-8.txt &&
|
||||
|
||||
test-tool path-utils do_files_match 0-10.txt 0-10.txt >out &&
|
||||
|
||||
assert_fails() {
|
||||
test_must_fail \
|
||||
test-tool path-utils do_files_match "$1" "$2" >out &&
|
||||
grep different out
|
||||
} &&
|
||||
|
||||
assert_fails 0-8.txt 1-9.txt &&
|
||||
assert_fails -1-10.txt 0-10.txt &&
|
||||
assert_fails 1-10.txt 1-9.txt &&
|
||||
assert_fails 1-10.txt .git &&
|
||||
assert_fails does-not-exist 1-10.txt &&
|
||||
|
||||
if test_have_prereq FILEMODE
|
||||
then
|
||||
cp 0-10.txt 0-10.x &&
|
||||
chmod a+x 0-10.x &&
|
||||
assert_fails 0-10.txt 0-10.x
|
||||
fi &&
|
||||
|
||||
if test_have_prereq SYMLINKS
|
||||
then
|
||||
ln -sf 0-10.txt symlink &&
|
||||
ln -s 0-10.txt another-symlink &&
|
||||
ln -s over-the-ocean yet-another-symlink &&
|
||||
ln -s "$PWD/0-10.txt" absolute-symlink &&
|
||||
assert_fails 0-10.txt symlink &&
|
||||
test-tool path-utils do_files_match symlink another-symlink &&
|
||||
assert_fails symlink yet-another-symlink &&
|
||||
assert_fails symlink absolute-symlink
|
||||
fi
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
78
t/t0411-clone-from-partial.sh
Executable file
78
t/t0411-clone-from-partial.sh
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='check that local clone does not fetch from promisor remotes'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'create evil repo' '
|
||||
git init tmp &&
|
||||
test_commit -C tmp a &&
|
||||
git -C tmp config uploadpack.allowfilter 1 &&
|
||||
git clone --filter=blob:none --no-local --no-checkout tmp evil &&
|
||||
rm -rf tmp &&
|
||||
|
||||
git -C evil config remote.origin.uploadpack \"\$TRASH_DIRECTORY/fake-upload-pack\" &&
|
||||
write_script fake-upload-pack <<-\EOF &&
|
||||
echo >&2 "fake-upload-pack running"
|
||||
>"$TRASH_DIRECTORY/script-executed"
|
||||
exit 1
|
||||
EOF
|
||||
export TRASH_DIRECTORY &&
|
||||
|
||||
# empty shallow file disables local clone optimization
|
||||
>evil/.git/shallow
|
||||
'
|
||||
|
||||
test_expect_success 'local clone must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
evil clone1 2>err &&
|
||||
test_grep "detected dubious ownership" err &&
|
||||
test_grep ! "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
test_expect_success 'clone from file://... must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
"file://$(pwd)/evil" clone2 2>err &&
|
||||
test_grep "detected dubious ownership" err &&
|
||||
test_grep ! "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
test_expect_success 'fetch from file://... must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git fetch \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
"file://$(pwd)/evil" 2>err &&
|
||||
test_grep "detected dubious ownership" err &&
|
||||
test_grep ! "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
test_expect_success 'pack-objects should fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
echo "HEAD" | test_must_fail git -C evil pack-objects --revs --stdout >/dev/null 2>err &&
|
||||
test_grep "fake-upload-pack running" err &&
|
||||
test_path_is_file script-executed
|
||||
'
|
||||
|
||||
test_expect_success 'clone from promisor remote does not lazy-fetch by default' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git clone evil no-lazy 2>err &&
|
||||
test_grep "lazy fetching disabled" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
test_expect_success 'promisor lazy-fetching can be re-enabled' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail env GIT_NO_LAZY_FETCH=0 \
|
||||
git clone evil lazy-ok 2>err &&
|
||||
test_grep "fake-upload-pack running" err &&
|
||||
test_path_is_file script-executed
|
||||
'
|
||||
|
||||
test_done
|
|
@ -1060,4 +1060,41 @@ test_expect_success 'fsck reports problems in current worktree index without fil
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'fsck warning on symlink target with excessive length' '
|
||||
symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) &&
|
||||
test_when_finished "remove_object $symlink_target" &&
|
||||
tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) &&
|
||||
test_when_finished "remove_object $tree" &&
|
||||
cat >expected <<-EOF &&
|
||||
warning in blob $symlink_target: symlinkTargetLength: symlink target too long
|
||||
EOF
|
||||
git fsck --no-dangling >actual 2>&1 &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'fsck warning on symlink target pointing inside git dir' '
|
||||
gitdir=$(printf ".git" | git hash-object -w --stdin) &&
|
||||
ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) &&
|
||||
hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) &&
|
||||
inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) &&
|
||||
benign_target=$(printf "legit/config" | git hash-object -w --stdin) &&
|
||||
tree=$(printf "120000 blob %s\t%s\n" \
|
||||
$benign_target benign_target \
|
||||
$gitdir gitdir \
|
||||
$hfs_gitdir hfs_gitdir \
|
||||
$inside_gitdir inside_gitdir \
|
||||
$ntfs_gitdir ntfs_gitdir |
|
||||
git mktree) &&
|
||||
for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree
|
||||
do
|
||||
test_when_finished "remove_object $o" || return 1
|
||||
done &&
|
||||
printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \
|
||||
$gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir |
|
||||
sort >expected &&
|
||||
git fsck --no-dangling >actual 2>&1 &&
|
||||
sort actual >actual.sorted &&
|
||||
test_cmp expected actual.sorted
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -185,4 +185,19 @@ test_expect_success 'stdin to hooks' '
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'clone protections' '
|
||||
test_config core.hooksPath "$(pwd)/my-hooks" &&
|
||||
mkdir -p my-hooks &&
|
||||
write_script my-hooks/test-hook <<-\EOF &&
|
||||
echo Hook ran $1
|
||||
EOF
|
||||
|
||||
git hook run test-hook 2>err &&
|
||||
test_grep "Hook ran" err &&
|
||||
test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \
|
||||
git hook run test-hook 2>err &&
|
||||
test_grep "active .core.hooksPath" err &&
|
||||
test_grep ! "Hook ran" err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -1252,6 +1252,30 @@ EOF
|
|||
test_cmp fatal-expect fatal-actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' '
|
||||
git init df-conflict &&
|
||||
(
|
||||
cd df-conflict &&
|
||||
ln -s .git a &&
|
||||
git add a &&
|
||||
test_tick &&
|
||||
git commit -m symlink &&
|
||||
test_commit a- &&
|
||||
rm a &&
|
||||
mkdir -p a/hooks &&
|
||||
write_script a/hooks/post-checkout <<-EOF &&
|
||||
echo WHOOPSIE >&2
|
||||
echo whoopsie >"$TRASH_DIRECTORY"/whoops
|
||||
EOF
|
||||
git add a/hooks/post-checkout &&
|
||||
test_tick &&
|
||||
git commit -m post-checkout
|
||||
) &&
|
||||
git clone df-conflict clone 2>err &&
|
||||
test_grep ! WHOOPS err &&
|
||||
test_path_is_missing whoops
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
|
|
@ -650,6 +650,21 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
|
|||
test_grep "the following paths have collided" icasefs/warning
|
||||
'
|
||||
|
||||
test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
|
||||
'colliding symlink/directory keeps directory' '
|
||||
git init icasefs-colliding-symlink &&
|
||||
(
|
||||
cd icasefs-colliding-symlink &&
|
||||
a=$(printf a | git hash-object -w --stdin) &&
|
||||
printf "100644 %s 0\tA/dir/b\n120000 %s 0\ta\n" $a $a >idx &&
|
||||
git update-index --index-info <idx &&
|
||||
test_tick &&
|
||||
git commit -m initial
|
||||
) &&
|
||||
git clone icasefs-colliding-symlink icasefs-colliding-symlink-clone &&
|
||||
test_file_not_empty icasefs-colliding-symlink-clone/A/dir/b
|
||||
'
|
||||
|
||||
test_expect_success 'clone with GIT_DEFAULT_HASH' '
|
||||
(
|
||||
sane_unset GIT_DEFAULT_HASH &&
|
||||
|
@ -773,6 +788,57 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe
|
|||
git clone --filter=blob:limit=0 "file://$(pwd)/server" client
|
||||
'
|
||||
|
||||
test_expect_success 'clone with init.templatedir runs hooks' '
|
||||
git init tmpl/hooks &&
|
||||
write_script tmpl/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo I was here >hook.run
|
||||
EOF
|
||||
git -C tmpl/hooks add . &&
|
||||
test_tick &&
|
||||
git -C tmpl/hooks commit -m post-checkout &&
|
||||
|
||||
test_when_finished "git config --global --unset init.templateDir || :" &&
|
||||
test_when_finished "git config --unset init.templateDir || :" &&
|
||||
(
|
||||
sane_unset GIT_TEMPLATE_DIR &&
|
||||
NO_SET_GIT_TEMPLATE_DIR=t &&
|
||||
export NO_SET_GIT_TEMPLATE_DIR &&
|
||||
|
||||
git -c core.hooksPath="$(pwd)/tmpl/hooks" \
|
||||
clone tmpl/hooks hook-run-hookspath 2>err &&
|
||||
test_grep ! "active .* hook found" err &&
|
||||
test_path_is_file hook-run-hookspath/hook.run &&
|
||||
|
||||
git -c init.templateDir="$(pwd)/tmpl" \
|
||||
clone tmpl/hooks hook-run-config 2>err &&
|
||||
test_grep ! "active .* hook found" err &&
|
||||
test_path_is_file hook-run-config/hook.run &&
|
||||
|
||||
git clone --template=tmpl tmpl/hooks hook-run-option 2>err &&
|
||||
test_grep ! "active .* hook found" err &&
|
||||
test_path_is_file hook-run-option/hook.run &&
|
||||
|
||||
git config --global init.templateDir "$(pwd)/tmpl" &&
|
||||
git clone tmpl/hooks hook-run-global-config 2>err &&
|
||||
git config --global --unset init.templateDir &&
|
||||
test_grep ! "active .* hook found" err &&
|
||||
test_path_is_file hook-run-global-config/hook.run &&
|
||||
|
||||
# clone ignores local `init.templateDir`; need to create
|
||||
# a new repository because we deleted `.git/` in the
|
||||
# `setup` test case above
|
||||
git init local-clone &&
|
||||
cd local-clone &&
|
||||
|
||||
git config init.templateDir "$(pwd)/../tmpl" &&
|
||||
git clone ../tmpl/hooks hook-run-local-config 2>err &&
|
||||
git config --unset init.templateDir &&
|
||||
test_grep ! "active .* hook found" err &&
|
||||
test_path_is_missing hook-run-local-config/hook.run
|
||||
)
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
|
|
@ -1452,4 +1452,35 @@ test_expect_success 'recursive clone respects -q' '
|
|||
test_must_be_empty actual
|
||||
'
|
||||
|
||||
test_expect_success '`submodule init` and `init.templateDir`' '
|
||||
mkdir -p tmpl/hooks &&
|
||||
write_script tmpl/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo I was here >hook.run
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
test_config init.templateDir "$(pwd)/tmpl" &&
|
||||
test_when_finished \
|
||||
"git config --global --unset init.templateDir || true" &&
|
||||
(
|
||||
sane_unset GIT_TEMPLATE_DIR &&
|
||||
NO_SET_GIT_TEMPLATE_DIR=t &&
|
||||
export NO_SET_GIT_TEMPLATE_DIR &&
|
||||
|
||||
git config --global init.templateDir "$(pwd)/tmpl" &&
|
||||
test_must_fail git submodule \
|
||||
add "$submodurl" sub-global 2>err &&
|
||||
git config --global --unset init.templateDir &&
|
||||
test_grep HOOK-RUN err &&
|
||||
test_path_is_file sub-global/hook.run &&
|
||||
|
||||
git config init.templateDir "$(pwd)/tmpl" &&
|
||||
git submodule add "$submodurl" sub-local 2>err &&
|
||||
git config --unset init.templateDir &&
|
||||
test_grep ! HOOK-RUN err &&
|
||||
test_path_is_missing sub-local/hook.run
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -1202,4 +1202,52 @@ test_expect_success 'commit with staged submodule change with ignoreSubmodules a
|
|||
add_submodule_commit_and_validate
|
||||
'
|
||||
|
||||
test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
|
||||
'submodule paths must not follow symlinks' '
|
||||
|
||||
# This is only needed because we want to run this in a self-contained
|
||||
# test without having to spin up an HTTP server; However, it would not
|
||||
# be needed in a real-world scenario where the submodule is simply
|
||||
# hosted on a public site.
|
||||
test_config_global protocol.file.allow always &&
|
||||
|
||||
# Make sure that Git tries to use symlinks on Windows
|
||||
test_config_global core.symlinks true &&
|
||||
|
||||
tell_tale_path="$PWD/tell.tale" &&
|
||||
git init hook &&
|
||||
(
|
||||
cd hook &&
|
||||
mkdir -p y/hooks &&
|
||||
write_script y/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo hook-run >"$tell_tale_path"
|
||||
EOF
|
||||
git add y/hooks/post-checkout &&
|
||||
test_tick &&
|
||||
git commit -m post-checkout
|
||||
) &&
|
||||
|
||||
hook_repo_path="$(pwd)/hook" &&
|
||||
git init captain &&
|
||||
(
|
||||
cd captain &&
|
||||
git submodule add --name x/y "$hook_repo_path" A/modules/x &&
|
||||
test_tick &&
|
||||
git commit -m add-submodule &&
|
||||
|
||||
printf .git >dotgit.txt &&
|
||||
git hash-object -w --stdin <dotgit.txt >dot-git.hash &&
|
||||
printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info &&
|
||||
git update-index --index-info <index.info &&
|
||||
test_tick &&
|
||||
git commit -m add-symlink
|
||||
) &&
|
||||
|
||||
test_path_is_missing "$tell_tale_path" &&
|
||||
git clone --recursive captain hooked 2>err &&
|
||||
test_grep ! HOOK-RUN err &&
|
||||
test_path_is_missing "$tell_tale_path"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
67
t/t7423-submodule-symlinks.sh
Executable file
67
t/t7423-submodule-symlinks.sh
Executable file
|
@ -0,0 +1,67 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='check that submodule operations do not follow symlinks'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'prepare' '
|
||||
git config --global protocol.file.allow always &&
|
||||
test_commit initial &&
|
||||
git init upstream &&
|
||||
test_commit -C upstream upstream submodule_file &&
|
||||
git submodule add ./upstream a/sm &&
|
||||
test_tick &&
|
||||
git commit -m submodule
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'git submodule update must not create submodule behind symlink' '
|
||||
rm -rf a b &&
|
||||
mkdir b &&
|
||||
ln -s b a &&
|
||||
test_path_is_missing b/sm &&
|
||||
test_must_fail git submodule update &&
|
||||
test_path_is_missing b/sm
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'git submodule update must not create submodule behind symlink on case insensitive fs' '
|
||||
rm -rf a b &&
|
||||
mkdir b &&
|
||||
ln -s b A &&
|
||||
test_must_fail git submodule update &&
|
||||
test_path_is_missing b/sm
|
||||
'
|
||||
|
||||
prepare_symlink_to_repo() {
|
||||
rm -rf a &&
|
||||
mkdir a &&
|
||||
git init a/target &&
|
||||
git -C a/target fetch ../../upstream &&
|
||||
ln -s target a/sm
|
||||
}
|
||||
|
||||
test_expect_success SYMLINKS 'git restore --recurse-submodules must not be confused by a symlink' '
|
||||
prepare_symlink_to_repo &&
|
||||
test_must_fail git restore --recurse-submodules a/sm &&
|
||||
test_path_is_missing a/sm/submodule_file &&
|
||||
test_path_is_dir a/target/.git &&
|
||||
test_path_is_missing a/target/submodule_file
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'git restore --recurse-submodules must not migrate git dir of symlinked repo' '
|
||||
prepare_symlink_to_repo &&
|
||||
rm -rf .git/modules &&
|
||||
test_must_fail git restore --recurse-submodules a/sm &&
|
||||
test_path_is_dir a/target/.git &&
|
||||
test_path_is_missing .git/modules/a/sm &&
|
||||
test_path_is_missing a/target/submodule_file
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'git checkout -f --recurse-submodules must not migrate git dir of symlinked repo when removing submodule' '
|
||||
prepare_symlink_to_repo &&
|
||||
rm -rf .git/modules &&
|
||||
test_must_fail git checkout -f --recurse-submodules initial &&
|
||||
test_path_is_dir a/target/.git &&
|
||||
test_path_is_missing .git/modules/a/sm
|
||||
'
|
||||
|
||||
test_done
|
|
@ -320,7 +320,7 @@ test_expect_success WINDOWS 'prevent git~1 squatting on Windows' '
|
|||
fi
|
||||
'
|
||||
|
||||
test_expect_success 'git dirs of sibling submodules must not be nested' '
|
||||
test_expect_success 'setup submodules with nested git dirs' '
|
||||
git init nested &&
|
||||
test_commit -C nested nested &&
|
||||
(
|
||||
|
@ -338,9 +338,39 @@ test_expect_success 'git dirs of sibling submodules must not be nested' '
|
|||
git add .gitmodules thing1 thing2 &&
|
||||
test_tick &&
|
||||
git commit -m nested
|
||||
) &&
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'git dirs of sibling submodules must not be nested' '
|
||||
test_must_fail git clone --recurse-submodules nested clone 2>err &&
|
||||
test_grep "is inside git dir" err
|
||||
'
|
||||
|
||||
test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
|
||||
test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err &&
|
||||
cat err &&
|
||||
grep -E "(already exists|is inside git dir|not a git repository)" err &&
|
||||
{
|
||||
test_path_is_missing .git/modules/hippo/HEAD ||
|
||||
test_path_is_missing .git/modules/hippo/hooks/HEAD
|
||||
}
|
||||
'
|
||||
|
||||
test_expect_success 'checkout -f --recurse-submodules must not use a nested gitdir' '
|
||||
git clone nested nested_checkout &&
|
||||
(
|
||||
cd nested_checkout &&
|
||||
git submodule init &&
|
||||
git submodule update thing1 &&
|
||||
mkdir -p .git/modules/hippo/hooks/refs &&
|
||||
mkdir -p .git/modules/hippo/hooks/objects/info &&
|
||||
echo "../../../../objects" >.git/modules/hippo/hooks/objects/info/alternates &&
|
||||
echo "ref: refs/heads/master" >.git/modules/hippo/hooks/HEAD
|
||||
) &&
|
||||
test_must_fail git -C nested_checkout checkout -f --recurse-submodules HEAD 2>err &&
|
||||
cat err &&
|
||||
grep "is inside git dir" err &&
|
||||
test_path_is_missing nested_checkout/thing2/.git
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
Loading…
Reference in a new issue