Documentation: git diff --check detects conflict markers
[gitweb.git] / path.c
diff --git a/path.c b/path.c
index 6b537ccfff452b7fbbe41446c001c8fda0679bea..38f2ebd6bfad2b1557b06ce34a0154603533dd65 100644 (file)
--- a/path.c
+++ b/path.c
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "dir.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode)
 
 static char bad_path[] = "/bad-path/";
 
-static char *get_pathname(void)
+static struct strbuf *get_pathname(void)
 {
-       static char pathname_array[4][PATH_MAX];
+       static struct strbuf pathname_array[4] = {
+               STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
+       };
        static int index;
-       return pathname_array[3 & ++index];
+       struct strbuf *sb = &pathname_array[3 & ++index];
+       strbuf_reset(sb);
+       return sb;
 }
 
 static char *cleanup_path(char *path)
@@ -34,6 +39,13 @@ static char *cleanup_path(char *path)
        return path;
 }
 
+static void strbuf_cleanup_path(struct strbuf *sb)
+{
+       char *path = cleanup_path(sb->buf);
+       if (path > sb->buf)
+               strbuf_remove(sb, 0, path - sb->buf);
+}
+
 char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 {
        va_list args;
@@ -49,124 +61,221 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        return cleanup_path(buf);
 }
 
-static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+static int dir_prefix(const char *buf, const char *dir)
 {
-       const char *git_dir = get_git_dir();
-       size_t len;
+       int len = strlen(dir);
+       return !strncmp(buf, dir, len) &&
+               (is_dir_sep(buf[len]) || buf[len] == '\0');
+}
 
-       len = strlen(git_dir);
-       if (n < len + 1)
-               goto bad;
-       memcpy(buf, git_dir, len);
-       if (len && !is_dir_sep(git_dir[len-1]))
-               buf[len++] = '/';
-       len += vsnprintf(buf + len, n - len, fmt, args);
-       if (len >= n)
-               goto bad;
-       return cleanup_path(buf);
-bad:
-       strlcpy(buf, bad_path, n);
-       return buf;
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+       int len = strlen(dir);
+       if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+               return 0;
+       while (is_dir_sep(buf[len]))
+               len++;
+       return !strcmp(buf + len, file);
+}
+
+static void replace_dir(struct strbuf *buf, int len, const char *newdir)
+{
+       int newlen = strlen(newdir);
+       int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
+               !is_dir_sep(newdir[newlen - 1]);
+       if (need_sep)
+               len--;   /* keep one char, to be replaced with '/'  */
+       strbuf_splice(buf, 0, len, newdir, newlen);
+       if (need_sep)
+               buf->buf[newlen] = '/';
+}
+
+static const char *common_list[] = {
+       "/branches", "/hooks", "/info", "!/logs", "/lost-found",
+       "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
+       "config", "!gc.pid", "packed-refs", "shallow",
+       NULL
+};
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len, const char *common_dir)
+{
+       char *base = buf->buf + git_dir_len;
+       const char **p;
+
+       if (is_dir_file(base, "logs", "HEAD") ||
+           is_dir_file(base, "info", "sparse-checkout"))
+               return; /* keep this in $GIT_DIR */
+       for (p = common_list; *p; p++) {
+               const char *path = *p;
+               int is_dir = 0;
+               if (*path == '!')
+                       path++;
+               if (*path == '/') {
+                       path++;
+                       is_dir = 1;
+               }
+
+               if (!common_dir)
+                       common_dir = get_git_common_dir();
+
+               if (is_dir && dir_prefix(base, path)) {
+                       replace_dir(buf, git_dir_len, common_dir);
+                       return;
+               }
+               if (!is_dir && !strcmp(base, path)) {
+                       replace_dir(buf, git_dir_len, common_dir);
+                       return;
+               }
+       }
+}
+
+void report_linked_checkout_garbage(void)
+{
+       struct strbuf sb = STRBUF_INIT;
+       const char **p;
+       int len;
+
+       if (!git_common_dir_env)
+               return;
+       strbuf_addf(&sb, "%s/", get_git_dir());
+       len = sb.len;
+       for (p = common_list; *p; p++) {
+               const char *path = *p;
+               if (*path == '!')
+                       continue;
+               strbuf_setlen(&sb, len);
+               strbuf_addstr(&sb, path);
+               if (file_exists(sb.buf))
+                       report_garbage(PACKDIR_FILE_GARBAGE, sb.buf);
+       }
+       strbuf_release(&sb);
+}
+
+static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+{
+       const char *base = buf->buf + git_dir_len;
+       if (git_graft_env && is_dir_file(base, "info", "grafts"))
+               strbuf_splice(buf, 0, buf->len,
+                             get_graft_file(), strlen(get_graft_file()));
+       else if (git_index_env && !strcmp(base, "index"))
+               strbuf_splice(buf, 0, buf->len,
+                             get_index_file(), strlen(get_index_file()));
+       else if (git_db_env && dir_prefix(base, "objects"))
+               replace_dir(buf, git_dir_len + 7, get_object_directory());
+       else if (git_common_dir_env)
+               update_common_dir(buf, git_dir_len, NULL);
+}
+
+static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+{
+       int gitdir_len;
+       strbuf_addstr(buf, get_git_dir());
+       if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+               strbuf_addch(buf, '/');
+       gitdir_len = buf->len;
+       strbuf_vaddf(buf, fmt, args);
+       adjust_git_path(buf, gitdir_len);
+       strbuf_cleanup_path(buf);
 }
 
-char *git_snpath(char *buf, size_t n, const char *fmt, ...)
+void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
-       char *ret;
        va_list args;
        va_start(args, fmt);
-       ret = vsnpath(buf, n, fmt, args);
+       do_git_path(sb, fmt, args);
        va_end(args);
-       return ret;
 }
 
-char *git_pathdup(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
 {
-       char path[PATH_MAX], *ret;
+       struct strbuf *pathname = get_pathname();
        va_list args;
        va_start(args, fmt);
-       ret = vsnpath(path, sizeof(path), fmt, args);
+       do_git_path(pathname, fmt, args);
        va_end(args);
-       return xstrdup(ret);
+       return pathname->buf;
 }
 
-char *mkpathdup(const char *fmt, ...)
+char *git_pathdup(const char *fmt, ...)
 {
-       char *path;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf path = STRBUF_INIT;
        va_list args;
-
        va_start(args, fmt);
-       strbuf_vaddf(&sb, fmt, args);
+       do_git_path(&path, fmt, args);
        va_end(args);
-       path = xstrdup(cleanup_path(sb.buf));
-
-       strbuf_release(&sb);
-       return path;
+       return strbuf_detach(&path, NULL);
 }
 
-char *mkpath(const char *fmt, ...)
+char *mkpathdup(const char *fmt, ...)
 {
+       struct strbuf sb = STRBUF_INIT;
        va_list args;
-       unsigned len;
-       char *pathname = get_pathname();
-
        va_start(args, fmt);
-       len = vsnprintf(pathname, PATH_MAX, fmt, args);
+       strbuf_vaddf(&sb, fmt, args);
        va_end(args);
-       if (len >= PATH_MAX)
-               return bad_path;
-       return cleanup_path(pathname);
+       strbuf_cleanup_path(&sb);
+       return strbuf_detach(&sb, NULL);
 }
 
-char *git_path(const char *fmt, ...)
+const char *mkpath(const char *fmt, ...)
 {
-       char *pathname = get_pathname();
        va_list args;
-       char *ret;
-
+       struct strbuf *pathname = get_pathname();
        va_start(args, fmt);
-       ret = vsnpath(pathname, PATH_MAX, fmt, args);
+       strbuf_vaddf(pathname, fmt, args);
        va_end(args);
-       return ret;
+       return cleanup_path(pathname->buf);
 }
 
-char *git_path_submodule(const char *path, const char *fmt, ...)
+static void do_submodule_path(struct strbuf *buf, const char *path,
+                             const char *fmt, va_list args)
 {
-       char *pathname = get_pathname();
-       struct strbuf buf = STRBUF_INIT;
        const char *git_dir;
-       va_list args;
-       unsigned len;
-
-       len = strlen(path);
-       if (len > PATH_MAX-100)
-               return bad_path;
+       struct strbuf git_submodule_common_dir = STRBUF_INIT;
+       struct strbuf git_submodule_dir = STRBUF_INIT;
 
-       strbuf_addstr(&buf, path);
-       if (len && path[len-1] != '/')
-               strbuf_addch(&buf, '/');
-       strbuf_addstr(&buf, ".git");
+       strbuf_addstr(buf, path);
+       if (buf->len && buf->buf[buf->len - 1] != '/')
+               strbuf_addch(buf, '/');
+       strbuf_addstr(buf, ".git");
 
-       git_dir = read_gitfile(buf.buf);
+       git_dir = read_gitfile(buf->buf);
        if (git_dir) {
-               strbuf_reset(&buf);
-               strbuf_addstr(&buf, git_dir);
+               strbuf_reset(buf);
+               strbuf_addstr(buf, git_dir);
        }
-       strbuf_addch(&buf, '/');
+       strbuf_addch(buf, '/');
+       strbuf_addstr(&git_submodule_dir, buf->buf);
 
-       if (buf.len >= PATH_MAX)
-               return bad_path;
-       memcpy(pathname, buf.buf, buf.len + 1);
+       strbuf_vaddf(buf, fmt, args);
 
-       strbuf_release(&buf);
-       len = strlen(pathname);
+       if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf))
+               update_common_dir(buf, git_submodule_dir.len, git_submodule_common_dir.buf);
 
+       strbuf_cleanup_path(buf);
+
+       strbuf_release(&git_submodule_dir);
+       strbuf_release(&git_submodule_common_dir);
+}
+
+char *git_pathdup_submodule(const char *path, const char *fmt, ...)
+{
+       va_list args;
+       struct strbuf buf = STRBUF_INIT;
        va_start(args, fmt);
-       len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+       do_submodule_path(&buf, path, fmt, args);
+       va_end(args);
+       return strbuf_detach(&buf, NULL);
+}
+
+void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+                              const char *fmt, ...)
+{
+       va_list args;
+       va_start(args, fmt);
+       do_submodule_path(buf, path, fmt, args);
        va_end(args);
-       if (len >= PATH_MAX)
-               return bad_path;
-       return cleanup_path(pathname);
 }
 
 int validate_headref(const char *path)
@@ -336,18 +445,22 @@ const char *enter_repo(const char *path, int strict)
                }
                if (!suffix[i])
                        return NULL;
-               gitfile = read_gitfile(used_path) ;
+               gitfile = read_gitfile(used_path);
                if (gitfile)
                        strcpy(used_path, gitfile);
                if (chdir(used_path))
                        return NULL;
                path = validated_path;
        }
-       else if (chdir(path))
-               return NULL;
+       else {
+               const char *gitfile = read_gitfile(path);
+               if (gitfile)
+                       path = gitfile;
+               if (chdir(path))
+                       return NULL;
+       }
 
-       if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
-           validate_headref("HEAD") == 0) {
+       if (is_git_directory(".")) {
                set_git_dir(".");
                check_repository_format();
                return path;
@@ -581,6 +694,11 @@ const char *remove_leading_path(const char *in, const char *prefix)
  * normalized, any time "../" eats up to the prefix_len part,
  * prefix_len is reduced. In the end prefix_len is the remaining
  * prefix that has not been overridden by user pathspec.
+ *
+ * NEEDSWORK: This function doesn't perform normalization w.r.t. trailing '/'.
+ * For everything but the root folder itself, the normalized path should not
+ * end with a '/', then the callers need to be fixed up accordingly.
+ *
  */
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
@@ -838,3 +956,13 @@ char *xdg_config_home(const char *filename)
                return mkpathdup("%s/.config/git/%s", home, filename);
        return NULL;
 }
+
+GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD")
+GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD")
+GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG")
+GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG")
+GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR")
+GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE")
+GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD")
+GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD")
+GIT_PATH_FUNC(git_path_shallow, "shallow")