checkout: generalize die_if_checked_out() branch name argument
[gitweb.git] / path.c
diff --git a/path.c b/path.c
index 6991103d436ef61dfecb2a35c8ea0422ce622e77..a5c51a33bd03158f1ac9624e98e7d6b4ddf32e18 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)
 {
@@ -60,13 +61,117 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        return cleanup_path(buf);
 }
 
+static int dir_prefix(const char *buf, const char *dir)
+{
+       int len = strlen(dir);
+       return !strncmp(buf, dir, len) &&
+               (is_dir_sep(buf[len]) || buf[len] == '\0');
+}
+
+/* $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)
+{
+       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 (is_dir && dir_prefix(base, path)) {
+                       replace_dir(buf, git_dir_len, get_git_common_dir());
+                       return;
+               }
+               if (!is_dir && !strcmp(base, path)) {
+                       replace_dir(buf, git_dir_len, get_git_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("unused in linked checkout", 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);
+}
+
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 {
-       const char *git_dir = get_git_dir();
-       strbuf_addstr(buf, git_dir);
+       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);
 }
 
@@ -78,6 +183,16 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        va_end(args);
 }
 
+const char *git_path(const char *fmt, ...)
+{
+       struct strbuf *pathname = get_pathname();
+       va_list args;
+       va_start(args, fmt);
+       do_git_path(pathname, fmt, args);
+       va_end(args);
+       return pathname->buf;
+}
+
 char *git_pathdup(const char *fmt, ...)
 {
        struct strbuf path = STRBUF_INIT;
@@ -109,16 +224,6 @@ const char *mkpath(const char *fmt, ...)
        return cleanup_path(pathname->buf);
 }
 
-const char *git_path(const char *fmt, ...)
-{
-       struct strbuf *pathname = get_pathname();
-       va_list args;
-       va_start(args, fmt);
-       do_git_path(pathname, fmt, args);
-       va_end(args);
-       return pathname->buf;
-}
-
 void home_config_paths(char **global, char **xdg, char *file)
 {
        char *xdg_home = getenv("XDG_CONFIG_HOME");