about summary refs log tree commit diff
path: root/third_party/git/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/git/path.c')
-rw-r--r--third_party/git/path.c265
1 files changed, 92 insertions, 173 deletions
diff --git a/third_party/git/path.c b/third_party/git/path.c
index 88cf593007..25e97b8c3f 100644
--- a/third_party/git/path.c
+++ b/third_party/git/path.c
@@ -11,7 +11,6 @@
 #include "path.h"
 #include "packfile.h"
 #include "object-store.h"
-#include "lockfile.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -102,36 +101,36 @@ struct common_dir {
 	/* Not considered garbage for report_linked_checkout_garbage */
 	unsigned ignore_garbage:1;
 	unsigned is_dir:1;
-	/* Belongs to the common dir, though it may contain paths that don't */
-	unsigned is_common:1;
-	const char *path;
+	/* Not common even though its parent is */
+	unsigned exclude:1;
+	const char *dirname;
 };
 
 static struct common_dir common_list[] = {
-	{ 0, 1, 1, "branches" },
-	{ 0, 1, 1, "common" },
-	{ 0, 1, 1, "hooks" },
-	{ 0, 1, 1, "info" },
-	{ 0, 0, 0, "info/sparse-checkout" },
-	{ 1, 1, 1, "logs" },
-	{ 1, 0, 0, "logs/HEAD" },
-	{ 0, 1, 0, "logs/refs/bisect" },
-	{ 0, 1, 0, "logs/refs/rewritten" },
-	{ 0, 1, 0, "logs/refs/worktree" },
-	{ 0, 1, 1, "lost-found" },
-	{ 0, 1, 1, "objects" },
-	{ 0, 1, 1, "refs" },
-	{ 0, 1, 0, "refs/bisect" },
-	{ 0, 1, 0, "refs/rewritten" },
-	{ 0, 1, 0, "refs/worktree" },
-	{ 0, 1, 1, "remotes" },
-	{ 0, 1, 1, "worktrees" },
-	{ 0, 1, 1, "rr-cache" },
-	{ 0, 1, 1, "svn" },
-	{ 0, 0, 1, "config" },
-	{ 1, 0, 1, "gc.pid" },
-	{ 0, 0, 1, "packed-refs" },
-	{ 0, 0, 1, "shallow" },
+	{ 0, 1, 0, "branches" },
+	{ 0, 1, 0, "common" },
+	{ 0, 1, 0, "hooks" },
+	{ 0, 1, 0, "info" },
+	{ 0, 0, 1, "info/sparse-checkout" },
+	{ 1, 1, 0, "logs" },
+	{ 1, 1, 1, "logs/HEAD" },
+	{ 0, 1, 1, "logs/refs/bisect" },
+	{ 0, 1, 1, "logs/refs/rewritten" },
+	{ 0, 1, 1, "logs/refs/worktree" },
+	{ 0, 1, 0, "lost-found" },
+	{ 0, 1, 0, "objects" },
+	{ 0, 1, 0, "refs" },
+	{ 0, 1, 1, "refs/bisect" },
+	{ 0, 1, 1, "refs/rewritten" },
+	{ 0, 1, 1, "refs/worktree" },
+	{ 0, 1, 0, "remotes" },
+	{ 0, 1, 0, "worktrees" },
+	{ 0, 1, 0, "rr-cache" },
+	{ 0, 1, 0, "svn" },
+	{ 0, 0, 0, "config" },
+	{ 1, 0, 0, "gc.pid" },
+	{ 0, 0, 0, "packed-refs" },
+	{ 0, 0, 0, "shallow" },
 	{ 0, 0, 0, NULL }
 };
 
@@ -237,41 +236,30 @@ static void *add_to_trie(struct trie *root, const char *key, void *value)
 	return old;
 }
 
-typedef int (*match_fn)(const char *unmatched, void *value, void *baton);
+typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
 
 /*
  * Search a trie for some key.  Find the longest /-or-\0-terminated
- * prefix of the key for which the trie contains a value.  If there is
- * no such prefix, return -1.  Otherwise call fn with the unmatched
- * portion of the key and the found value.  If fn returns 0 or
- * positive, then return its return value.  If fn returns negative,
- * then call fn with the next-longest /-terminated prefix of the key
- * (i.e. a parent directory) for which the trie contains a value, and
- * handle its return value the same way.  If there is no shorter
- * /-terminated prefix with a value left, then return the negative
- * return value of the most recent fn invocation.
+ * prefix of the key for which the trie contains a value.  Call fn
+ * with the unmatched portion of the key and the found value, and
+ * return its return value.  If there is no such prefix, return -1.
  *
  * The key is partially normalized: consecutive slashes are skipped.
  *
- * For example, consider the trie containing only [logs,
- * logs/refs/bisect], both with values, but not logs/refs.
+ * For example, consider the trie containing only [refs,
+ * refs/worktree] (both with values).
+ *
+ * | key             | unmatched  | val from node | return value |
+ * |-----------------|------------|---------------|--------------|
+ * | a               | not called | n/a           | -1           |
+ * | refs            | \0         | refs          | as per fn    |
+ * | refs/           | /          | refs          | as per fn    |
+ * | refs/w          | /w         | refs          | as per fn    |
+ * | refs/worktree   | \0         | refs/worktree | as per fn    |
+ * | refs/worktree/  | /          | refs/worktree | as per fn    |
+ * | refs/worktree/a | /a         | refs/worktree | as per fn    |
+ * |-----------------|------------|---------------|--------------|
  *
- * | key                | unmatched      | prefix to node   | return value |
- * |--------------------|----------------|------------------|--------------|
- * | a                  | not called     | n/a              | -1           |
- * | logstore           | not called     | n/a              | -1           |
- * | logs               | \0             | logs             | as per fn    |
- * | logs/              | /              | logs             | as per fn    |
- * | logs/refs          | /refs          | logs             | as per fn    |
- * | logs/refs/         | /refs/         | logs             | as per fn    |
- * | logs/refs/b        | /refs/b        | logs             | as per fn    |
- * | logs/refs/bisected | /refs/bisected | logs             | as per fn    |
- * | logs/refs/bisect   | \0             | logs/refs/bisect | as per fn    |
- * | logs/refs/bisect/  | /              | logs/refs/bisect | as per fn    |
- * | logs/refs/bisect/a | /a             | logs/refs/bisect | as per fn    |
- * | (If fn in the previous line returns -1, then fn is called once more:) |
- * | logs/refs/bisect/a | /refs/bisect/a | logs             | as per fn    |
- * |--------------------|----------------|------------------|--------------|
  */
 static int trie_find(struct trie *root, const char *key, match_fn fn,
 		     void *baton)
@@ -300,13 +288,9 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key) {
+	if (!*key)
 		/* End of key */
-		if (root->value)
-			return fn(key, root->value, baton);
-		else
-			return -1;
-	}
+		return fn(key, root->value, baton);
 
 	/* Partial path normalization: skip consecutive slashes */
 	while (key[0] == '/' && key[1] == '/')
@@ -336,8 +320,8 @@ static void init_common_trie(void)
 	if (common_trie_done_setup)
 		return;
 
-	for (p = common_list; p->path; p++)
-		add_to_trie(&common_trie, p->path, p);
+	for (p = common_list; p->dirname; p++)
+		add_to_trie(&common_trie, p->dirname, p);
 
 	common_trie_done_setup = 1;
 }
@@ -350,11 +334,14 @@ static int check_common(const char *unmatched, void *value, void *baton)
 {
 	struct common_dir *dir = value;
 
+	if (!dir)
+		return 0;
+
 	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
-		return dir->is_common;
+		return !dir->exclude;
 
 	if (!dir->is_dir && unmatched[0] == 0)
-		return dir->is_common;
+		return !dir->exclude;
 
 	return 0;
 }
@@ -363,14 +350,9 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
 	char *base = buf->buf + git_dir_len;
-	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);
-
 	init_common_trie();
 	if (trie_find(&common_trie, base, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
-
-	if (has_lock_suffix)
-		strbuf_addstr(buf, LOCK_SUFFIX);
 }
 
 void report_linked_checkout_garbage(void)
@@ -383,8 +365,8 @@ void report_linked_checkout_garbage(void)
 		return;
 	strbuf_addf(&sb, "%s/", get_git_dir());
 	len = sb.len;
-	for (p = common_list; p->path; p++) {
-		const char *path = p->path;
+	for (p = common_list; p->dirname; p++) {
+		const char *path = p->dirname;
 		if (p->ignore_garbage)
 			continue;
 		strbuf_setlen(&sb, len);
@@ -1077,8 +1059,6 @@ const char *remove_leading_path(const char *in, const char *prefix)
 
 /*
  * It is okay if dst == src, but they should not overlap otherwise.
- * The "dst" buffer must be at least as long as "src"; normalizing may shrink
- * the size of the path, but will never grow it.
  *
  * Performs the following normalizations on src, storing the result in dst:
  * - Ensures that components are separated by '/' (Windows only)
@@ -1241,52 +1221,31 @@ static inline int chomp_trailing_dir_sep(const char *path, int len)
 }
 
 /*
- * If path ends with suffix (complete path components), returns the offset of
- * the last character in the path before the suffix (sans trailing directory
- * separators), and -1 otherwise.
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
  */
-static ssize_t stripped_path_suffix_offset(const char *path, const char *suffix)
+char *strip_path_suffix(const char *path, const char *suffix)
 {
 	int path_len = strlen(path), suffix_len = strlen(suffix);
 
 	while (suffix_len) {
 		if (!path_len)
-			return -1;
+			return NULL;
 
 		if (is_dir_sep(path[path_len - 1])) {
 			if (!is_dir_sep(suffix[suffix_len - 1]))
-				return -1;
+				return NULL;
 			path_len = chomp_trailing_dir_sep(path, path_len);
 			suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
 		}
 		else if (path[--path_len] != suffix[--suffix_len])
-			return -1;
+			return NULL;
 	}
 
 	if (path_len && !is_dir_sep(path[path_len - 1]))
-		return -1;
-	return chomp_trailing_dir_sep(path, path_len);
-}
-
-/*
- * Returns true if the path ends with components, considering only complete path
- * components, and false otherwise.
- */
-int ends_with_path_components(const char *path, const char *components)
-{
-	return stripped_path_suffix_offset(path, components) != -1;
-}
-
-/*
- * If path ends with suffix (complete path components), returns the
- * part before suffix (sans trailing directory separators).
- * Otherwise returns NULL.
- */
-char *strip_path_suffix(const char *path, const char *suffix)
-{
-	ssize_t offset = stripped_path_suffix_offset(path, suffix);
-
-	return offset == -1 ? NULL : xstrndup(path, offset);
+		return NULL;
+	return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
 }
 
 int daemon_avoid_alias(const char *p)
@@ -1336,77 +1295,37 @@ int daemon_avoid_alias(const char *p)
 	}
 }
 
-/*
- * On NTFS, we need to be careful to disallow certain synonyms of the `.git/`
- * directory:
- *
- * - For historical reasons, file names that end in spaces or periods are
- *   automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
- *   to `.git/`.
- *
- * - For other historical reasons, file names that do not conform to the 8.3
- *   format (up to eight characters for the basename, three for the file
- *   extension, certain characters not allowed such as `+`, etc) are associated
- *   with a so-called "short name", at least on the `C:` drive by default.
- *   Which means that `git~1/` is a valid way to refer to `.git/`.
- *
- *   Note: Technically, `.git/` could receive the short name `git~2` if the
- *   short name `git~1` were already used. In Git, however, we guarantee that
- *   `.git` is the first item in a directory, therefore it will be associated
- *   with the short name `git~1` (unless short names are disabled).
- *
- * - For yet other historical reasons, NTFS supports so-called "Alternate Data
- *   Streams", i.e. metadata associated with a given file, referred to via
- *   `<filename>:<stream-name>:<stream-type>`. There exists a default stream
- *   type for directories, allowing `.git/` to be accessed via
- *   `.git::$INDEX_ALLOCATION/`.
- *
- * When this function returns 1, it indicates that the specified file/directory
- * name refers to a `.git` file or directory, or to any of these synonyms, and
- * Git should therefore not track it.
- *
- * For performance reasons, _all_ Alternate Data Streams of `.git/` are
- * forbidden, not just `::$INDEX_ALLOCATION`.
- *
- * This function is intended to be used by `git fsck` even on platforms where
- * the backslash is a regular filename character, therefore it needs to handle
- * backlash characters in the provided `name` specially: they are interpreted
- * as directory separators.
- */
-int is_ntfs_dotgit(const char *name)
+static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
 {
-	char c;
-
-	/*
-	 * Note that when we don't find `.git` or `git~1` we end up with `name`
-	 * advanced partway through the string. That's okay, though, as we
-	 * return immediately in those cases, without looking at `name` any
-	 * further.
-	 */
-	c = *(name++);
-	if (c == '.') {
-		/* .git */
-		if (((c = *(name++)) != 'g' && c != 'G') ||
-		    ((c = *(name++)) != 'i' && c != 'I') ||
-		    ((c = *(name++)) != 't' && c != 'T'))
-			return 0;
-	} else if (c == 'g' || c == 'G') {
-		/* git ~1 */
-		if (((c = *(name++)) != 'i' && c != 'I') ||
-		    ((c = *(name++)) != 't' && c != 'T') ||
-		    *(name++) != '~' ||
-		    *(name++) != '1')
-			return 0;
-	} else
+	if (len < skip)
 		return 0;
-
-	for (;;) {
-		c = *(name++);
-		if (!c || c == '\\' || c == '/' || c == ':')
-			return 1;
-		if (c != '.' && c != ' ')
+	len -= skip;
+	path += skip;
+	while (len-- > 0) {
+		char c = *(path++);
+		if (c != ' ' && c != '.')
 			return 0;
 	}
+	return 1;
+}
+
+int is_ntfs_dotgit(const char *name)
+{
+	size_t len;
+
+	for (len = 0; ; len++)
+		if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
+			if (only_spaces_and_periods(name, len, 4) &&
+					!strncasecmp(name, ".git", 4))
+				return 1;
+			if (only_spaces_and_periods(name, len, 5) &&
+					!strncasecmp(name, "git~1", 5))
+				return 1;
+			if (name[len] != '\\')
+				return 0;
+			name += len + 1;
+			len = -1;
+		}
 }
 
 static int is_ntfs_dot_generic(const char *name,
@@ -1422,7 +1341,7 @@ static int is_ntfs_dot_generic(const char *name,
 only_spaces_and_periods:
 		for (;;) {
 			char c = name[i++];
-			if (!c || c == ':')
+			if (!c)
 				return 1;
 			if (c != ' ' && c != '.')
 				return 0;