]> git.ipfire.org Git - thirdparty/git.git/blobdiff - remote.c
Sync with 2.38.4
[thirdparty/git.git] / remote.c
index 6d1e8d02dfe7863cef5bbbe27b555d30fda8c5b7..60869beebe7364a594cd45938d4ed97dcdd28840 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "config.h"
 #include "remote.h"
+#include "urlmatch.h"
 #include "refs.h"
 #include "refspec.h"
 #include "object-store.h"
 #include "dir.h"
 #include "tag.h"
 #include "string-list.h"
-#include "mergesort.h"
 #include "strvec.h"
 #include "commit-reach.h"
 #include "advice.h"
+#include "connect.h"
 
 enum map_direction { FROM_SRC, FROM_DST };
 
@@ -21,33 +22,6 @@ struct counted_string {
        size_t len;
        const char *s;
 };
-struct rewrite {
-       const char *base;
-       size_t baselen;
-       struct counted_string *instead_of;
-       int instead_of_nr;
-       int instead_of_alloc;
-};
-struct rewrites {
-       struct rewrite **rewrite;
-       int rewrite_alloc;
-       int rewrite_nr;
-};
-
-static struct remote **remotes;
-static int remotes_alloc;
-static int remotes_nr;
-static struct hashmap remotes_hash;
-
-static struct branch **branches;
-static int branches_alloc;
-static int branches_nr;
-
-static struct branch *current_branch;
-static const char *pushremote_name;
-
-static struct rewrites rewrites;
-static struct rewrites rewrites_push;
 
 static int valid_remote(const struct remote *remote)
 {
@@ -92,17 +66,19 @@ static void add_pushurl(struct remote *remote, const char *pushurl)
        remote->pushurl[remote->pushurl_nr++] = pushurl;
 }
 
-static void add_pushurl_alias(struct remote *remote, const char *url)
+static void add_pushurl_alias(struct remote_state *remote_state,
+                             struct remote *remote, const char *url)
 {
-       const char *pushurl = alias_url(url, &rewrites_push);
+       const char *pushurl = alias_url(url, &remote_state->rewrites_push);
        if (pushurl != url)
                add_pushurl(remote, pushurl);
 }
 
-static void add_url_alias(struct remote *remote, const char *url)
+static void add_url_alias(struct remote_state *remote_state,
+                         struct remote *remote, const char *url)
 {
-       add_url(remote, alias_url(url, &rewrites));
-       add_pushurl_alias(remote, url);
+       add_url(remote, alias_url(url, &remote_state->rewrites));
+       add_pushurl_alias(remote_state, remote, url);
 }
 
 struct remotes_hash_key {
@@ -110,7 +86,7 @@ struct remotes_hash_key {
        int len;
 };
 
-static int remotes_hash_cmp(const void *unused_cmp_data,
+static int remotes_hash_cmp(const void *cmp_data UNUSED,
                            const struct hashmap_entry *eptr,
                            const struct hashmap_entry *entry_or_key,
                            const void *keydata)
@@ -127,27 +103,21 @@ static int remotes_hash_cmp(const void *unused_cmp_data,
                return strcmp(a->name, b->name);
 }
 
-static inline void init_remotes_hash(void)
-{
-       if (!remotes_hash.cmpfn)
-               hashmap_init(&remotes_hash, remotes_hash_cmp, NULL, 0);
-}
-
-static struct remote *make_remote(const char *name, int len)
+static struct remote *make_remote(struct remote_state *remote_state,
+                                 const char *name, int len)
 {
-       struct remote *ret, *replaced;
+       struct remote *ret;
        struct remotes_hash_key lookup;
        struct hashmap_entry lookup_entry, *e;
 
        if (!len)
                len = strlen(name);
 
-       init_remotes_hash();
        lookup.str = name;
        lookup.len = len;
        hashmap_entry_init(&lookup_entry, memhash(name, len));
 
-       e = hashmap_get(&remotes_hash, &lookup_entry, &lookup);
+       e = hashmap_get(&remote_state->remotes_hash, &lookup_entry, &lookup);
        if (e)
                return container_of(e, struct remote, ent);
 
@@ -158,15 +128,36 @@ static struct remote *make_remote(const char *name, int len)
        refspec_init(&ret->push, REFSPEC_PUSH);
        refspec_init(&ret->fetch, REFSPEC_FETCH);
 
-       ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
-       remotes[remotes_nr++] = ret;
+       ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1,
+                  remote_state->remotes_alloc);
+       remote_state->remotes[remote_state->remotes_nr++] = ret;
 
        hashmap_entry_init(&ret->ent, lookup_entry.hash);
-       replaced = hashmap_put_entry(&remotes_hash, ret, ent);
-       assert(replaced == NULL);  /* no previous entry overwritten */
+       if (hashmap_put_entry(&remote_state->remotes_hash, ret, ent))
+               BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
        return ret;
 }
 
+static void remote_clear(struct remote *remote)
+{
+       int i;
+
+       free((char *)remote->name);
+       free((char *)remote->foreign_vcs);
+
+       for (i = 0; i < remote->url_nr; i++)
+               free((char *)remote->url[i]);
+       FREE_AND_NULL(remote->url);
+
+       for (i = 0; i < remote->pushurl_nr; i++)
+               free((char *)remote->pushurl[i]);
+       FREE_AND_NULL(remote->pushurl);
+       free((char *)remote->receivepack);
+       free((char *)remote->uploadpack);
+       FREE_AND_NULL(remote->http_proxy);
+       FREE_AND_NULL(remote->http_proxy_authmethod);
+}
+
 static void add_merge(struct branch *branch, const char *name)
 {
        ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
@@ -174,23 +165,72 @@ static void add_merge(struct branch *branch, const char *name)
        branch->merge_name[branch->merge_nr++] = name;
 }
 
-static struct branch *make_branch(const char *name, size_t len)
+struct branches_hash_key {
+       const char *str;
+       int len;
+};
+
+static int branches_hash_cmp(const void *cmp_data UNUSED,
+                            const struct hashmap_entry *eptr,
+                            const struct hashmap_entry *entry_or_key,
+                            const void *keydata)
+{
+       const struct branch *a, *b;
+       const struct branches_hash_key *key = keydata;
+
+       a = container_of(eptr, const struct branch, ent);
+       b = container_of(entry_or_key, const struct branch, ent);
+
+       if (key)
+               return strncmp(a->name, key->str, key->len) ||
+                      a->name[key->len];
+       else
+               return strcmp(a->name, b->name);
+}
+
+static struct branch *find_branch(struct remote_state *remote_state,
+                                 const char *name, size_t len)
+{
+       struct branches_hash_key lookup;
+       struct hashmap_entry lookup_entry, *e;
+
+       lookup.str = name;
+       lookup.len = len;
+       hashmap_entry_init(&lookup_entry, memhash(name, len));
+
+       e = hashmap_get(&remote_state->branches_hash, &lookup_entry, &lookup);
+       if (e)
+               return container_of(e, struct branch, ent);
+
+       return NULL;
+}
+
+static void die_on_missing_branch(struct repository *repo,
+                                 struct branch *branch)
+{
+       /* branch == NULL is always valid because it represents detached HEAD. */
+       if (branch &&
+           branch != find_branch(repo->remote_state, branch->name,
+                                 strlen(branch->name)))
+               die("branch %s was not found in the repository", branch->name);
+}
+
+static struct branch *make_branch(struct remote_state *remote_state,
+                                 const char *name, size_t len)
 {
        struct branch *ret;
-       int i;
 
-       for (i = 0; i < branches_nr; i++) {
-               if (!strncmp(name, branches[i]->name, len) &&
-                   !branches[i]->name[len])
-                       return branches[i];
-       }
+       ret = find_branch(remote_state, name, len);
+       if (ret)
+               return ret;
 
-       ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
        CALLOC_ARRAY(ret, 1);
-       branches[branches_nr++] = ret;
        ret->name = xstrndup(name, len);
        ret->refname = xstrfmt("refs/heads/%s", ret->name);
 
+       hashmap_entry_init(&ret->ent, memhash(name, len));
+       if (hashmap_put_entry(&remote_state->branches_hash, ret, ent))
+               BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
        return ret;
 }
 
@@ -229,7 +269,8 @@ static const char *skip_spaces(const char *s)
        return s;
 }
 
-static void read_remotes_file(struct remote *remote)
+static void read_remotes_file(struct remote_state *remote_state,
+                             struct remote *remote)
 {
        struct strbuf buf = STRBUF_INIT;
        FILE *f = fopen_or_warn(git_path("remotes/%s", remote->name), "r");
@@ -244,7 +285,8 @@ static void read_remotes_file(struct remote *remote)
                strbuf_rtrim(&buf);
 
                if (skip_prefix(buf.buf, "URL:", &v))
-                       add_url_alias(remote, xstrdup(skip_spaces(v)));
+                       add_url_alias(remote_state, remote,
+                                     xstrdup(skip_spaces(v)));
                else if (skip_prefix(buf.buf, "Push:", &v))
                        refspec_append(&remote->push, skip_spaces(v));
                else if (skip_prefix(buf.buf, "Pull:", &v))
@@ -254,7 +296,8 @@ static void read_remotes_file(struct remote *remote)
        fclose(f);
 }
 
-static void read_branches_file(struct remote *remote)
+static void read_branches_file(struct remote_state *remote_state,
+                              struct remote *remote)
 {
        char *frag;
        struct strbuf buf = STRBUF_INIT;
@@ -286,7 +329,7 @@ static void read_branches_file(struct remote *remote)
        else
                frag = (char *)git_default_branch_name(0);
 
-       add_url_alias(remote, strbuf_detach(&buf, NULL));
+       add_url_alias(remote_state, remote, strbuf_detach(&buf, NULL));
        refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
                        frag, remote->name);
 
@@ -305,10 +348,16 @@ static int handle_config(const char *key, const char *value, void *cb)
        const char *subkey;
        struct remote *remote;
        struct branch *branch;
+       struct remote_state *remote_state = cb;
+
        if (parse_config_key(key, "branch", &name, &namelen, &subkey) >= 0) {
+               /* There is no subsection. */
                if (!name)
                        return 0;
-               branch = make_branch(name, namelen);
+               /* There is a subsection, but it is empty. */
+               if (!namelen)
+                       return -1;
+               branch = make_branch(remote_state, name, namelen);
                if (!strcmp(subkey, "remote")) {
                        return git_config_string(&branch->remote_name, key, value);
                } else if (!strcmp(subkey, "pushremote")) {
@@ -327,12 +376,14 @@ static int handle_config(const char *key, const char *value, void *cb)
                if (!strcmp(subkey, "insteadof")) {
                        if (!value)
                                return config_error_nonbool(key);
-                       rewrite = make_rewrite(&rewrites, name, namelen);
+                       rewrite = make_rewrite(&remote_state->rewrites, name,
+                                              namelen);
                        add_instead_of(rewrite, xstrdup(value));
                } else if (!strcmp(subkey, "pushinsteadof")) {
                        if (!value)
                                return config_error_nonbool(key);
-                       rewrite = make_rewrite(&rewrites_push, name, namelen);
+                       rewrite = make_rewrite(&remote_state->rewrites_push,
+                                              name, namelen);
                        add_instead_of(rewrite, xstrdup(value));
                }
        }
@@ -342,7 +393,8 @@ static int handle_config(const char *key, const char *value, void *cb)
 
        /* Handle remote.* variables */
        if (!name && !strcmp(subkey, "pushdefault"))
-               return git_config_string(&pushremote_name, key, value);
+               return git_config_string(&remote_state->pushremote_name, key,
+                                        value);
 
        if (!name)
                return 0;
@@ -352,7 +404,7 @@ static int handle_config(const char *key, const char *value, void *cb)
                        name);
                return 0;
        }
-       remote = make_remote(name, namelen);
+       remote = make_remote(remote_state, name, namelen);
        remote->origin = REMOTE_CONFIG;
        if (current_config_scope() == CONFIG_SCOPE_LOCAL ||
            current_config_scope() == CONFIG_SCOPE_WORKTREE)
@@ -422,44 +474,51 @@ static int handle_config(const char *key, const char *value, void *cb)
        return 0;
 }
 
-static void alias_all_urls(void)
+static void alias_all_urls(struct remote_state *remote_state)
 {
        int i, j;
-       for (i = 0; i < remotes_nr; i++) {
+       for (i = 0; i < remote_state->remotes_nr; i++) {
                int add_pushurl_aliases;
-               if (!remotes[i])
+               if (!remote_state->remotes[i])
                        continue;
-               for (j = 0; j < remotes[i]->pushurl_nr; j++) {
-                       remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites);
+               for (j = 0; j < remote_state->remotes[i]->pushurl_nr; j++) {
+                       remote_state->remotes[i]->pushurl[j] =
+                               alias_url(remote_state->remotes[i]->pushurl[j],
+                                         &remote_state->rewrites);
                }
-               add_pushurl_aliases = remotes[i]->pushurl_nr == 0;
-               for (j = 0; j < remotes[i]->url_nr; j++) {
+               add_pushurl_aliases = remote_state->remotes[i]->pushurl_nr == 0;
+               for (j = 0; j < remote_state->remotes[i]->url_nr; j++) {
                        if (add_pushurl_aliases)
-                               add_pushurl_alias(remotes[i], remotes[i]->url[j]);
-                       remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites);
+                               add_pushurl_alias(
+                                       remote_state, remote_state->remotes[i],
+                                       remote_state->remotes[i]->url[j]);
+                       remote_state->remotes[i]->url[j] =
+                               alias_url(remote_state->remotes[i]->url[j],
+                                         &remote_state->rewrites);
                }
        }
 }
 
-static void read_config(void)
+static void read_config(struct repository *repo)
 {
-       static int loaded;
        int flag;
 
-       if (loaded)
+       if (repo->remote_state->initialized)
                return;
-       loaded = 1;
+       repo->remote_state->initialized = 1;
 
-       current_branch = NULL;
+       repo->remote_state->current_branch = NULL;
        if (startup_info->have_repository) {
-               const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag);
+               const char *head_ref = refs_resolve_ref_unsafe(
+                       get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
                if (head_ref && (flag & REF_ISSYMREF) &&
                    skip_prefix(head_ref, "refs/heads/", &head_ref)) {
-                       current_branch = make_branch(head_ref, strlen(head_ref));
+                       repo->remote_state->current_branch = make_branch(
+                               repo->remote_state, head_ref, strlen(head_ref));
                }
        }
-       git_config(handle_config, NULL);
-       alias_all_urls();
+       repo_config(repo, handle_config, repo->remote_state);
+       alias_all_urls(repo->remote_state);
 }
 
 static int valid_remote_nick(const char *name)
@@ -474,7 +533,9 @@ static int valid_remote_nick(const char *name)
        return 1;
 }
 
-const char *remote_for_branch(struct branch *branch, int *explicit)
+static const char *remotes_remote_for_branch(struct remote_state *remote_state,
+                                            struct branch *branch,
+                                            int *explicit)
 {
        if (branch && branch->remote_name) {
                if (explicit)
@@ -483,35 +544,66 @@ const char *remote_for_branch(struct branch *branch, int *explicit)
        }
        if (explicit)
                *explicit = 0;
+       if (remote_state->remotes_nr == 1)
+               return remote_state->remotes[0]->name;
        return "origin";
 }
 
-const char *pushremote_for_branch(struct branch *branch, int *explicit)
+const char *remote_for_branch(struct branch *branch, int *explicit)
+{
+       read_config(the_repository);
+       die_on_missing_branch(the_repository, branch);
+
+       return remotes_remote_for_branch(the_repository->remote_state, branch,
+                                        explicit);
+}
+
+static const char *
+remotes_pushremote_for_branch(struct remote_state *remote_state,
+                             struct branch *branch, int *explicit)
 {
        if (branch && branch->pushremote_name) {
                if (explicit)
                        *explicit = 1;
                return branch->pushremote_name;
        }
-       if (pushremote_name) {
+       if (remote_state->pushremote_name) {
                if (explicit)
                        *explicit = 1;
-               return pushremote_name;
+               return remote_state->pushremote_name;
        }
-       return remote_for_branch(branch, explicit);
+       return remotes_remote_for_branch(remote_state, branch, explicit);
 }
 
+const char *pushremote_for_branch(struct branch *branch, int *explicit)
+{
+       read_config(the_repository);
+       die_on_missing_branch(the_repository, branch);
+
+       return remotes_pushremote_for_branch(the_repository->remote_state,
+                                            branch, explicit);
+}
+
+static struct remote *remotes_remote_get(struct remote_state *remote_state,
+                                        const char *name);
+
 const char *remote_ref_for_branch(struct branch *branch, int for_push)
 {
+       read_config(the_repository);
+       die_on_missing_branch(the_repository, branch);
+
        if (branch) {
                if (!for_push) {
                        if (branch->merge_nr) {
                                return branch->merge_name[0];
                        }
                } else {
-                       const char *dst, *remote_name =
-                               pushremote_for_branch(branch, NULL);
-                       struct remote *remote = remote_get(remote_name);
+                       const char *dst,
+                               *remote_name = remotes_pushremote_for_branch(
+                                       the_repository->remote_state, branch,
+                                       NULL);
+                       struct remote *remote = remotes_remote_get(
+                               the_repository->remote_state, remote_name);
 
                        if (remote && remote->push.nr &&
                            (dst = apply_refspecs(&remote->push,
@@ -523,41 +615,105 @@ const char *remote_ref_for_branch(struct branch *branch, int for_push)
        return NULL;
 }
 
-static struct remote *remote_get_1(const char *name,
-                                  const char *(*get_default)(struct branch *, int *))
+static void validate_remote_url(struct remote *remote)
+{
+       int i;
+       const char *value;
+       struct strbuf redacted = STRBUF_INIT;
+       int warn_not_die;
+
+       if (git_config_get_string_tmp("transfer.credentialsinurl", &value))
+               return;
+
+       if (!strcmp("warn", value))
+               warn_not_die = 1;
+       else if (!strcmp("die", value))
+               warn_not_die = 0;
+       else if (!strcmp("allow", value))
+               return;
+       else
+               die(_("unrecognized value transfer.credentialsInUrl: '%s'"), value);
+
+       for (i = 0; i < remote->url_nr; i++) {
+               struct url_info url_info = { 0 };
+
+               if (!url_normalize(remote->url[i], &url_info) ||
+                   !url_info.passwd_off)
+                       goto loop_cleanup;
+
+               strbuf_reset(&redacted);
+               strbuf_add(&redacted, url_info.url, url_info.passwd_off);
+               strbuf_addstr(&redacted, "<redacted>");
+               strbuf_addstr(&redacted,
+                             url_info.url + url_info.passwd_off + url_info.passwd_len);
+
+               if (warn_not_die)
+                       warning(_("URL '%s' uses plaintext credentials"), redacted.buf);
+               else
+                       die(_("URL '%s' uses plaintext credentials"), redacted.buf);
+
+loop_cleanup:
+               free(url_info.url);
+       }
+
+       strbuf_release(&redacted);
+}
+
+static struct remote *
+remotes_remote_get_1(struct remote_state *remote_state, const char *name,
+                    const char *(*get_default)(struct remote_state *,
+                                               struct branch *, int *))
 {
        struct remote *ret;
        int name_given = 0;
 
-       read_config();
-
        if (name)
                name_given = 1;
        else
-               name = get_default(current_branch, &name_given);
+               name = get_default(remote_state, remote_state->current_branch,
+                                  &name_given);
 
-       ret = make_remote(name, 0);
+       ret = make_remote(remote_state, name, 0);
        if (valid_remote_nick(name) && have_git_dir()) {
                if (!valid_remote(ret))
-                       read_remotes_file(ret);
+                       read_remotes_file(remote_state, ret);
                if (!valid_remote(ret))
-                       read_branches_file(ret);
+                       read_branches_file(remote_state, ret);
        }
        if (name_given && !valid_remote(ret))
-               add_url_alias(ret, name);
+               add_url_alias(remote_state, ret, name);
        if (!valid_remote(ret))
                return NULL;
+
+       validate_remote_url(ret);
+
        return ret;
 }
 
+static inline struct remote *
+remotes_remote_get(struct remote_state *remote_state, const char *name)
+{
+       return remotes_remote_get_1(remote_state, name,
+                                   remotes_remote_for_branch);
+}
+
 struct remote *remote_get(const char *name)
 {
-       return remote_get_1(name, remote_for_branch);
+       read_config(the_repository);
+       return remotes_remote_get(the_repository->remote_state, name);
+}
+
+static inline struct remote *
+remotes_pushremote_get(struct remote_state *remote_state, const char *name)
+{
+       return remotes_remote_get_1(remote_state, name,
+                                   remotes_pushremote_for_branch);
 }
 
 struct remote *pushremote_get(const char *name)
 {
-       return remote_get_1(name, pushremote_for_branch);
+       read_config(the_repository);
+       return remotes_pushremote_get(the_repository->remote_state, name);
 }
 
 int remote_is_configured(struct remote *remote, int in_repo)
@@ -572,12 +728,14 @@ int remote_is_configured(struct remote *remote, int in_repo)
 int for_each_remote(each_remote_fn fn, void *priv)
 {
        int i, result = 0;
-       read_config();
-       for (i = 0; i < remotes_nr && !result; i++) {
-               struct remote *r = remotes[i];
-               if (!r)
+       read_config(the_repository);
+       for (i = 0; i < the_repository->remote_state->remotes_nr && !result;
+            i++) {
+               struct remote *remote =
+                       the_repository->remote_state->remotes[i];
+               if (!remote)
                        continue;
-               result = fn(r, priv);
+               result = fn(remote, priv);
        }
        return result;
 }
@@ -691,7 +849,7 @@ static int refspec_match(const struct refspec_item *refspec,
        return !strcmp(refspec->src, name);
 }
 
-static int omit_name_by_refspec(const char *name, struct refspec *rs)
+int omit_name_by_refspec(const char *name, struct refspec *rs)
 {
        int i;
 
@@ -923,27 +1081,6 @@ void free_refs(struct ref *ref)
        }
 }
 
-int ref_compare_name(const void *va, const void *vb)
-{
-       const struct ref *a = va, *b = vb;
-       return strcmp(a->name, b->name);
-}
-
-static void *ref_list_get_next(const void *a)
-{
-       return ((const struct ref *)a)->next;
-}
-
-static void ref_list_set_next(void *a, void *next)
-{
-       ((struct ref *)a)->next = next;
-}
-
-void sort_ref_list(struct ref **l, int (*cmp)(const void *, const void *))
-{
-       *l = llist_mergesort(*l, ref_list_get_next, ref_list_set_next, cmp);
-}
-
 int count_refspec_match(const char *pattern,
                        struct ref *refs,
                        struct ref **matched_ref)
@@ -1111,7 +1248,7 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
                "Neither worked, so we gave up. You must fully qualify the ref."),
              dst_value, matched_src_name);
 
-       if (!advice_push_unqualified_ref_name)
+       if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
                return;
 
        if (get_oid(matched_src_name, &oid))
@@ -1592,7 +1729,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
                        else
                                /*
                                 * If the ref isn't stale, and is reachable
-                                * from from one of the reflog entries of
+                                * from one of the reflog entries of
                                 * the local branch, force the update.
                                 */
                                force_ref_update = 1;
@@ -1642,7 +1779,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        }
 }
 
-static void set_merge(struct branch *ret)
+static void set_merge(struct remote_state *remote_state, struct branch *ret)
 {
        struct remote *remote;
        char *ref;
@@ -1662,7 +1799,7 @@ static void set_merge(struct branch *ret)
                return;
        }
 
-       remote = remote_get(ret->remote_name);
+       remote = remotes_remote_get(remote_state, ret->remote_name);
 
        CALLOC_ARRAY(ret->merge, ret->merge_nr);
        for (i = 0; i < ret->merge_nr; i++) {
@@ -1683,12 +1820,13 @@ struct branch *branch_get(const char *name)
 {
        struct branch *ret;
 
-       read_config();
+       read_config(the_repository);
        if (!name || !*name || !strcmp(name, "HEAD"))
-               ret = current_branch;
+               ret = the_repository->remote_state->current_branch;
        else
-               ret = make_branch(name, strlen(name));
-       set_merge(ret);
+               ret = make_branch(the_repository->remote_state, name,
+                                 strlen(name));
+       set_merge(the_repository->remote_state, ret);
        return ret;
 }
 
@@ -1759,11 +1897,14 @@ static const char *tracking_for_push_dest(struct remote *remote,
        return ret;
 }
 
-static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
+static const char *branch_get_push_1(struct remote_state *remote_state,
+                                    struct branch *branch, struct strbuf *err)
 {
        struct remote *remote;
 
-       remote = remote_get(pushremote_for_branch(branch, NULL));
+       remote = remotes_remote_get(
+               remote_state,
+               remotes_pushremote_for_branch(remote_state, branch, NULL));
        if (!remote)
                return error_buf(err,
                                 _("branch '%s' has no remote for pushing"),
@@ -1821,21 +1962,21 @@ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err)
 
 const char *branch_get_push(struct branch *branch, struct strbuf *err)
 {
+       read_config(the_repository);
+       die_on_missing_branch(the_repository, branch);
+
        if (!branch)
                return error_buf(err, _("HEAD does not point to a branch"));
 
        if (!branch->push_tracking_ref)
-               branch->push_tracking_ref = branch_get_push_1(branch, err);
+               branch->push_tracking_ref = branch_get_push_1(
+                       the_repository->remote_state, branch, err);
        return branch->push_tracking_ref;
 }
 
-static int ignore_symref_update(const char *refname)
+static int ignore_symref_update(const char *refname, struct strbuf *scratch)
 {
-       int flag;
-
-       if (!resolve_ref_unsafe(refname, 0, NULL, &flag))
-               return 0; /* non-existing refs are OK */
-       return (flag & REF_ISSYMREF);
+       return !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);
 }
 
 /*
@@ -1848,6 +1989,7 @@ static int ignore_symref_update(const char *refname)
 static struct ref *get_expanded_map(const struct ref *remote_refs,
                                    const struct refspec_item *refspec)
 {
+       struct strbuf scratch = STRBUF_INIT;
        const struct ref *ref;
        struct ref *ret = NULL;
        struct ref **tail = &ret;
@@ -1855,11 +1997,13 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
        for (ref = remote_refs; ref; ref = ref->next) {
                char *expn_name = NULL;
 
+               strbuf_reset(&scratch);
+
                if (strchr(ref->name, '^'))
                        continue; /* a dereference item */
                if (match_name_with_pattern(refspec->src, ref->name,
                                            refspec->dst, &expn_name) &&
-                   !ignore_symref_update(expn_name)) {
+                   !ignore_symref_update(expn_name, &scratch)) {
                        struct ref *cpy = copy_ref(ref);
 
                        cpy->peer_ref = alloc_ref(expn_name);
@@ -1871,6 +2015,7 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
                free(expn_name);
        }
 
+       strbuf_release(&scratch);
        return ret;
 }
 
@@ -2002,6 +2147,9 @@ static int stat_branch_pair(const char *branch_name, const char *base,
        struct object_id oid;
        struct commit *ours, *theirs;
        struct rev_info revs;
+       struct setup_revision_opt opt = {
+               .free_removed_argv_elements = 1,
+       };
        struct strvec argv = STRVEC_INIT;
 
        /* Cannot stat if what we used to build on no longer exists */
@@ -2036,7 +2184,7 @@ static int stat_branch_pair(const char *branch_name, const char *base,
        strvec_push(&argv, "--");
 
        repo_init_revisions(the_repository, &revs, NULL);
-       setup_revisions(argv.nr, argv.v, &revs, NULL);
+       setup_revisions(argv.nr, argv.v, &revs, &opt);
        if (prepare_revision_walk(&revs))
                die(_("revision walk setup failed"));
 
@@ -2056,6 +2204,7 @@ static int stat_branch_pair(const char *branch_name, const char *base,
        clear_commit_marks(theirs, ALL_REV_FLAGS);
 
        strvec_clear(&argv);
+       release_revisions(&revs);
        return 1;
 }
 
@@ -2118,7 +2267,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                strbuf_addf(sb,
                        _("Your branch is based on '%s', but the upstream is gone.\n"),
                        base);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git branch --unset-upstream\" to fixup)\n"));
        } else if (!sti) {
@@ -2129,7 +2278,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                strbuf_addf(sb,
                            _("Your branch and '%s' refer to different commits.\n"),
                            base);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
                                    "git status --ahead-behind");
        } else if (!theirs) {
@@ -2138,7 +2287,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                           "Your branch is ahead of '%s' by %d commits.\n",
                           ours),
                        base, ours);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git push\" to publish your local commits)\n"));
        } else if (!ours) {
@@ -2149,7 +2298,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                               "and can be fast-forwarded.\n",
                           theirs),
                        base, theirs);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git pull\" to update your local branch)\n"));
        } else {
@@ -2162,7 +2311,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                               "respectively.\n",
                           ours + theirs),
                        base, ours, theirs);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git pull\" to merge the remote branch into yours)\n"));
        }
@@ -2171,7 +2320,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 }
 
 static int one_local_ref(const char *refname, const struct object_id *oid,
-                        int flag, void *cb_data)
+                        int flag UNUSED,
+                        void *cb_data)
 {
        struct ref ***local_tail = cb_data;
        struct ref *ref;
@@ -2403,7 +2553,7 @@ struct reflog_commit_array {
        size_t nr, alloc;
 };
 
-#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 }
+#define REFLOG_COMMIT_ARRAY_INIT { 0 }
 
 /* Append a commit to the array. */
 static void append_commit(struct reflog_commit_array *arr,
@@ -2427,19 +2577,22 @@ struct check_and_collect_until_cb_data {
 };
 
 /* Get the timestamp of the latest entry. */
-static int peek_reflog(struct object_id *o_oid, struct object_id *n_oid,
-                      const char *ident, timestamp_t timestamp,
-                      int tz, const char *message, void *cb_data)
+static int peek_reflog(struct object_id *o_oid UNUSED,
+                      struct object_id *n_oid UNUSED,
+                      const char *ident UNUSED,
+                      timestamp_t timestamp, int tz UNUSED,
+                      const char *message UNUSED, void *cb_data)
 {
        timestamp_t *ts = cb_data;
        *ts = timestamp;
        return 1;
 }
 
-static int check_and_collect_until(struct object_id *o_oid,
+static int check_and_collect_until(struct object_id *o_oid UNUSED,
                                   struct object_id *n_oid,
-                                  const char *ident, timestamp_t timestamp,
-                                  int tz, const char *message, void *cb_data)
+                                  const char *ident UNUSED,
+                                  timestamp_t timestamp, int tz UNUSED,
+                                  const char *message UNUSED, void *cb_data)
 {
        struct commit *commit;
        struct check_and_collect_until_cb_data *cb = cb_data;
@@ -2585,3 +2738,126 @@ void apply_push_cas(struct push_cas_option *cas,
                        check_if_includes_upstream(ref);
        }
 }
+
+struct remote_state *remote_state_new(void)
+{
+       struct remote_state *r = xmalloc(sizeof(*r));
+
+       memset(r, 0, sizeof(*r));
+
+       hashmap_init(&r->remotes_hash, remotes_hash_cmp, NULL, 0);
+       hashmap_init(&r->branches_hash, branches_hash_cmp, NULL, 0);
+       return r;
+}
+
+void remote_state_clear(struct remote_state *remote_state)
+{
+       int i;
+
+       for (i = 0; i < remote_state->remotes_nr; i++)
+               remote_clear(remote_state->remotes[i]);
+       FREE_AND_NULL(remote_state->remotes);
+       remote_state->remotes_alloc = 0;
+       remote_state->remotes_nr = 0;
+
+       hashmap_clear_and_free(&remote_state->remotes_hash, struct remote, ent);
+       hashmap_clear_and_free(&remote_state->branches_hash, struct remote, ent);
+}
+
+/*
+ * Returns 1 if it was the last chop before ':'.
+ */
+static int chop_last_dir(char **remoteurl, int is_relative)
+{
+       char *rfind = find_last_dir_sep(*remoteurl);
+       if (rfind) {
+               *rfind = '\0';
+               return 0;
+       }
+
+       rfind = strrchr(*remoteurl, ':');
+       if (rfind) {
+               *rfind = '\0';
+               return 1;
+       }
+
+       if (is_relative || !strcmp(".", *remoteurl))
+               die(_("cannot strip one component off url '%s'"),
+                       *remoteurl);
+
+       free(*remoteurl);
+       *remoteurl = xstrdup(".");
+       return 0;
+}
+
+char *relative_url(const char *remote_url, const char *url,
+                  const char *up_path)
+{
+       int is_relative = 0;
+       int colonsep = 0;
+       char *out;
+       char *remoteurl;
+       struct strbuf sb = STRBUF_INIT;
+       size_t len;
+
+       if (!url_is_local_not_ssh(url) || is_absolute_path(url))
+               return xstrdup(url);
+
+       len = strlen(remote_url);
+       if (!len)
+               BUG("invalid empty remote_url");
+
+       remoteurl = xstrdup(remote_url);
+       if (is_dir_sep(remoteurl[len-1]))
+               remoteurl[len-1] = '\0';
+
+       if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
+               is_relative = 0;
+       else {
+               is_relative = 1;
+               /*
+                * Prepend a './' to ensure all relative
+                * remoteurls start with './' or '../'
+                */
+               if (!starts_with_dot_slash_native(remoteurl) &&
+                   !starts_with_dot_dot_slash_native(remoteurl)) {
+                       strbuf_reset(&sb);
+                       strbuf_addf(&sb, "./%s", remoteurl);
+                       free(remoteurl);
+                       remoteurl = strbuf_detach(&sb, NULL);
+               }
+       }
+       /*
+        * When the url starts with '../', remove that and the
+        * last directory in remoteurl.
+        */
+       while (*url) {
+               if (starts_with_dot_dot_slash_native(url)) {
+                       url += 3;
+                       colonsep |= chop_last_dir(&remoteurl, is_relative);
+               } else if (starts_with_dot_slash_native(url))
+                       url += 2;
+               else
+                       break;
+       }
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
+       if (ends_with(url, "/"))
+               strbuf_setlen(&sb, sb.len - 1);
+       free(remoteurl);
+
+       if (starts_with_dot_slash_native(sb.buf))
+               out = xstrdup(sb.buf + 2);
+       else
+               out = xstrdup(sb.buf);
+
+       if (!up_path || !is_relative) {
+               strbuf_release(&sb);
+               return out;
+       }
+
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "%s%s", up_path, out);
+       free(out);
+       return strbuf_detach(&sb, NULL);
+}