Revision 9cf85473209ea8ae2b56c13145c4704d12ee1374 authored by Filip Hejsek on 28 January 2024, 04:09:17 UTC, committed by Johannes Schindelin on 17 April 2024, 20:30:01 UTC
While it is expected to have several git dirs within the `.git/modules/`
tree, it is important that they do not interfere with each other. For
example, if one submodule was called "captain" and another submodule
"captain/hooks", their respective git dirs would clash, as they would be
located in `.git/modules/captain/` and `.git/modules/captain/hooks/`,
respectively, i.e. the latter's files could clash with the actual Git
hooks of the former.

To prevent these clashes, and in particular to prevent hooks from being
written and then executed as part of a recursive clone, we introduced
checks as part of the fix for CVE-2019-1387 in a8dee3ca61 (Disallow
dubiously-nested submodule git directories, 2019-10-01).

It is currently possible to bypass the check for clashing submodule
git dirs in two ways:

1. parallel cloning
2. checkout --recurse-submodules

Let's check not only before, but also after parallel cloning (and before
checking out the submodule), that the git dir is not clashing with
another one, otherwise fail. This addresses the parallel cloning issue.

As to the parallel checkout issue: It requires quite a few manual steps
to create clashing git dirs because Git itself would refuse to
initialize the inner one, as demonstrated by the test case.

Nevertheless, let's teach the recursive checkout (namely, the
`submodule_move_head()` function that is used by the recursive checkout)
to be careful to verify that it does not use a clashing git dir, and if
it does, disable it (by deleting the `HEAD` file so that subsequent Git
calls won't recognize it as a git dir anymore).

Note: The parallel cloning test case contains a `cat err` that proved to
be highly useful when analyzing the racy nature of the operation (the
operation can fail with three different error messages, depending on
timing), and was left on purpose to ease future debugging should the
need arise.

Signed-off-by: Filip Hejsek <filip.hejsek@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent b20c10f
Raw File
merge-ort.h
#ifndef MERGE_ORT_H
#define MERGE_ORT_H

#include "merge-recursive.h"
#include "hash.h"

struct commit;
struct tree;
struct strmap;

struct merge_result {
	/*
	 * Whether the merge is clean; possible values:
	 *    1: clean
	 *    0: not clean (merge conflicts)
	 *   <0: operation aborted prematurely.  (object database
	 *       unreadable, disk full, etc.)  Worktree may be left in an
	 *       inconsistent state if operation failed near the end.
	 */
	int clean;

	/*
	 * Result of merge.  If !clean, represents what would go in worktree
	 * (thus possibly including files containing conflict markers).
	 */
	struct tree *tree;

	/*
	 * Special messages and conflict notices for various paths
	 *
	 * This is a map of pathnames to a string_list. It contains various
	 * warning/conflict/notice messages (possibly multiple per path)
	 * that callers may want to use.
	 */
	struct strmap *path_messages;

	/*
	 * Additional metadata used by merge_switch_to_result() or future calls
	 * to merge_incore_*().  Includes data needed to update the index (if
	 * !clean) and to print "CONFLICT" messages.  Not for external use.
	 */
	void *priv;
	/* Also private */
	unsigned _properly_initialized;
};

/*
 * rename-detecting three-way merge with recursive ancestor consolidation.
 * working tree and index are untouched.
 *
 * merge_bases will be consumed (emptied) so make a copy if you need it.
 *
 * NOTE: empirically, the recursive algorithm will perform better if you
 *       pass the merge_bases in the order of oldest commit to the
 *       newest[1][2].
 *
 *       [1] https://lore.kernel.org/git/nycvar.QRO.7.76.6.1907252055500.21907@tvgsbejvaqbjf.bet/
 *       [2] commit 8918b0c9c2 ("merge-recur: try to merge older merge bases
 *           first", 2006-08-09)
 */
void merge_incore_recursive(struct merge_options *opt,
			    struct commit_list *merge_bases,
			    struct commit *side1,
			    struct commit *side2,
			    struct merge_result *result);

/*
 * rename-detecting three-way merge, no recursion.
 * working tree and index are untouched.
 */
void merge_incore_nonrecursive(struct merge_options *opt,
			       struct tree *merge_base,
			       struct tree *side1,
			       struct tree *side2,
			       struct merge_result *result);

/* Update the working tree and index from head to result after incore merge */
void merge_switch_to_result(struct merge_options *opt,
			    struct tree *head,
			    struct merge_result *result,
			    int update_worktree_and_index,
			    int display_update_msgs);

/*
 * Display messages about conflicts and which files were 3-way merged.
 * Automatically called by merge_switch_to_result() with stream == stdout,
 * so only call this when bypassing merge_switch_to_result().
 */
void merge_display_update_messages(struct merge_options *opt,
				   int detailed,
				   struct merge_result *result);

struct stage_info {
	struct object_id oid;
	int mode;
	int stage;
};

/*
 * Provide a list of path -> {struct stage_info*} mappings for
 * all conflicted files.  Note that each path could appear up to three
 * times in the list, corresponding to 3 different stage entries.  In short,
 * this basically provides the info that would be printed by `ls-files -u`.
 *
 * result should have been populated by a call to
 * one of the merge_incore_[non]recursive() functions.
 *
 * conflicted_files should be empty before calling this function.
 */
void merge_get_conflicted_files(struct merge_result *result,
				struct string_list *conflicted_files);

/* Do needed cleanup when not calling merge_switch_to_result() */
void merge_finalize(struct merge_options *opt,
		    struct merge_result *result);

#endif
back to top