]> git.proxmox.com Git - libgit2.git/blobdiff - src/libgit2/submodule.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / src / libgit2 / submodule.c
diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c
new file mode 100644 (file)
index 0000000..0f4f072
--- /dev/null
@@ -0,0 +1,2380 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "submodule.h"
+
+#include "buf.h"
+#include "branch.h"
+#include "vector.h"
+#include "posix.h"
+#include "config_backend.h"
+#include "config.h"
+#include "repository.h"
+#include "tree.h"
+#include "iterator.h"
+#include "fs_path.h"
+#include "str.h"
+#include "index.h"
+#include "worktree.h"
+#include "clone.h"
+#include "path.h"
+
+#include "git2/config.h"
+#include "git2/sys/config.h"
+#include "git2/types.h"
+#include "git2/index.h"
+
+#define GIT_MODULES_FILE ".gitmodules"
+
+static git_configmap _sm_update_map[] = {
+       {GIT_CONFIGMAP_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
+       {GIT_CONFIGMAP_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
+       {GIT_CONFIGMAP_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
+       {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
+       {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE},
+       {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT},
+};
+
+static git_configmap _sm_ignore_map[] = {
+       {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
+       {GIT_CONFIGMAP_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
+       {GIT_CONFIGMAP_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
+       {GIT_CONFIGMAP_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
+       {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE},
+       {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL},
+};
+
+static git_configmap _sm_recurse_map[] = {
+       {GIT_CONFIGMAP_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND},
+       {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO},
+       {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES},
+};
+
+enum {
+       CACHE_OK = 0,
+       CACHE_REFRESH = 1,
+       CACHE_FLUSH = 2
+};
+enum {
+       GITMODULES_EXISTING = 0,
+       GITMODULES_CREATE = 1
+};
+
+static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name);
+static git_config_backend *open_gitmodules(git_repository *repo, int gitmod);
+static int gitmodules_snapshot(git_config **snap, git_repository *repo);
+static int get_url_base(git_str *url, git_repository *repo);
+static int lookup_head_remote_key(git_str *remote_key, git_repository *repo);
+static int lookup_default_remote(git_remote **remote, git_repository *repo);
+static int submodule_load_each(const git_config_entry *entry, void *payload);
+static int submodule_read_config(git_submodule *sm, git_config *cfg);
+static int submodule_load_from_wd_lite(git_submodule *);
+static void submodule_get_index_status(unsigned int *, git_submodule *);
+static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t);
+static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie);
+static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id);
+
+static int submodule_cmp(const void *a, const void *b)
+{
+       return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+}
+
+static int submodule_config_key_trunc_puts(git_str *key, const char *suffix)
+{
+       ssize_t idx = git_str_rfind(key, '.');
+       git_str_truncate(key, (size_t)(idx + 1));
+       return git_str_puts(key, suffix);
+}
+
+/*
+ * PUBLIC APIS
+ */
+
+static void submodule_set_lookup_error(int error, const char *name)
+{
+       if (!error)
+               return;
+
+       git_error_set(GIT_ERROR_SUBMODULE, (error == GIT_ENOTFOUND) ?
+               "no submodule named '%s'" :
+               "submodule '%s' has not been added yet", name);
+}
+
+typedef struct {
+       const char *path;
+       char *name;
+} fbp_data;
+
+static int find_by_path(const git_config_entry *entry, void *payload)
+{
+       fbp_data *data = payload;
+
+       if (!strcmp(entry->value, data->path)) {
+               const char *fdot, *ldot;
+               fdot = strchr(entry->name, '.');
+               ldot = strrchr(entry->name, '.');
+               data->name = git__strndup(fdot + 1, ldot - fdot - 1);
+               GIT_ERROR_CHECK_ALLOC(data->name);
+       }
+
+       return 0;
+}
+
+/*
+ * Checks to see if the submodule shares its name with a file or directory that
+ * already exists on the index. If so, the submodule cannot be added.
+ */
+static int is_path_occupied(bool *occupied, git_repository *repo, const char *path)
+{
+       int error = 0;
+       git_index *index;
+       git_str dir = GIT_STR_INIT;
+       *occupied = false;
+
+       if ((error = git_repository_index__weakptr(&index, repo)) < 0)
+               goto out;
+
+       if ((error = git_index_find(NULL, index, path)) != GIT_ENOTFOUND) {
+               if (!error) {
+                       git_error_set(GIT_ERROR_SUBMODULE,
+                               "File '%s' already exists in the index", path);
+                       *occupied = true;
+               }
+               goto out;
+       }
+
+       if ((error = git_str_sets(&dir, path)) < 0)
+               goto out;
+
+       if ((error = git_fs_path_to_dir(&dir)) < 0)
+               goto out;
+
+       if ((error = git_index_find_prefix(NULL, index, dir.ptr)) != GIT_ENOTFOUND) {
+               if (!error) {
+                       git_error_set(GIT_ERROR_SUBMODULE,
+                               "Directory '%s' already exists in the index", path);
+                       *occupied = true;
+               }
+               goto out;
+       }
+
+       error = 0;
+
+out:
+       git_str_dispose(&dir);
+       return error;
+}
+
+/**
+ * Release the name map returned by 'load_submodule_names'.
+ */
+static void free_submodule_names(git_strmap *names)
+{
+       const char *key;
+       char *value;
+
+       if (names == NULL)
+               return;
+
+       git_strmap_foreach(names, key, value, {
+               git__free((char *) key);
+               git__free(value);
+       });
+       git_strmap_free(names);
+
+       return;
+}
+
+/**
+ * Map submodule paths to names.
+ * TODO: for some use-cases, this might need case-folding on a
+ * case-insensitive filesystem
+ */
+static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg)
+{
+       const char *key = "submodule\\..*\\.path";
+       git_config_iterator *iter = NULL;
+       git_config_entry *entry;
+       git_str buf = GIT_STR_INIT;
+       git_strmap *names;
+       int isvalid, error;
+
+       *out = NULL;
+
+       if ((error = git_strmap_new(&names)) < 0)
+               goto out;
+
+       if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
+               goto out;
+
+       while ((error = git_config_next(&entry, iter)) == 0) {
+               const char *fdot, *ldot;
+               fdot = strchr(entry->name, '.');
+               ldot = strrchr(entry->name, '.');
+
+               if (git_strmap_exists(names, entry->value)) {
+                       git_error_set(GIT_ERROR_SUBMODULE,
+                                  "duplicated submodule path '%s'", entry->value);
+                       error = -1;
+                       goto out;
+               }
+
+               git_str_clear(&buf);
+               git_str_put(&buf, fdot + 1, ldot - fdot - 1);
+               isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0);
+               if (isvalid < 0) {
+                       error = isvalid;
+                       goto out;
+               }
+               if (!isvalid)
+                       continue;
+
+               if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) {
+                       git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table");
+                       error = -1;
+                       goto out;
+               }
+       }
+       if (error == GIT_ITEROVER)
+               error = 0;
+
+       *out = names;
+       names = NULL;
+
+out:
+       free_submodule_names(names);
+       git_str_dispose(&buf);
+       git_config_iterator_free(iter);
+       return error;
+}
+
+int git_submodule_cache_init(git_strmap **out, git_repository *repo)
+{
+       int error = 0;
+       git_strmap *cache = NULL;
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(repo);
+       if ((error = git_strmap_new(&cache)) < 0)
+               return error;
+       if ((error = git_submodule__map(repo, cache)) < 0) {
+               git_submodule_cache_free(cache);
+               return error;
+       }
+       *out = cache;
+       return error;
+}
+
+int git_submodule_cache_free(git_strmap *cache)
+{
+       git_submodule *sm = NULL;
+       if (cache == NULL)
+               return 0;
+       git_strmap_foreach_value(cache, sm, {
+               git_submodule_free(sm);
+       });
+       git_strmap_free(cache);
+       return 0;
+}
+
+int git_submodule_lookup(
+       git_submodule **out, /* NULL if user only wants to test existence */
+       git_repository *repo,
+       const char *name)    /* trailing slash is allowed */
+{
+       return git_submodule__lookup_with_cache(out, repo, name, repo->submodule_cache);
+}
+
+int git_submodule__lookup_with_cache(
+       git_submodule **out, /* NULL if user only wants to test existence */
+       git_repository *repo,
+       const char *name,    /* trailing slash is allowed */
+       git_strmap *cache)
+{
+       int error;
+       unsigned int location;
+       git_submodule *sm;
+
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       if (repo->is_bare) {
+               git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree");
+               return -1;
+       }
+
+       if (cache != NULL) {
+               if ((sm = git_strmap_get(cache, name)) != NULL) {
+                       if (out) {
+                               *out = sm;
+                               GIT_REFCOUNT_INC(*out);
+                       }
+                       return 0;
+               }
+       }
+
+       if ((error = submodule_alloc(&sm, repo, name)) < 0)
+               return error;
+
+       if ((error = git_submodule_reload(sm, false)) < 0) {
+               git_submodule_free(sm);
+               return error;
+       }
+
+       if ((error = git_submodule_location(&location, sm)) < 0) {
+               git_submodule_free(sm);
+               return error;
+       }
+
+       /* If it's not configured or we're looking by path  */
+       if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
+               git_config_backend *mods;
+               const char *pattern = "submodule\\..*\\.path";
+               git_str path = GIT_STR_INIT;
+               fbp_data data = { NULL, NULL };
+
+               git_str_puts(&path, name);
+               while (path.ptr[path.size-1] == '/') {
+                       path.ptr[--path.size] = '\0';
+               }
+               data.path = path.ptr;
+
+               mods = open_gitmodules(repo, GITMODULES_EXISTING);
+
+               if (mods)
+                       error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data);
+
+               git_config_backend_free(mods);
+
+               if (error < 0) {
+                       git_submodule_free(sm);
+                       git_str_dispose(&path);
+                       return error;
+               }
+
+               if (data.name) {
+                       git__free(sm->name);
+                       sm->name = data.name;
+                       sm->path = git_str_detach(&path);
+
+                       /* Try to load again with the right name */
+                       if ((error = git_submodule_reload(sm, false)) < 0) {
+                               git_submodule_free(sm);
+                               return error;
+                       }
+               }
+
+               git_str_dispose(&path);
+       }
+
+       if ((error = git_submodule_location(&location, sm)) < 0) {
+               git_submodule_free(sm);
+               return error;
+       }
+
+       /* If we still haven't found it, do the WD check */
+       if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) {
+               git_submodule_free(sm);
+               error = GIT_ENOTFOUND;
+
+               /* If it's not configured, we still check if there's a repo at the path */
+               if (git_repository_workdir(repo)) {
+                       git_str path = GIT_STR_INIT;
+                       if (git_str_join3(&path, '/',
+                                         git_repository_workdir(repo),
+                                         name, DOT_GIT) < 0 ||
+                           git_path_validate_str_length(NULL, &path) < 0)
+                               return -1;
+
+                       if (git_fs_path_exists(path.ptr))
+                               error = GIT_EEXISTS;
+
+                       git_str_dispose(&path);
+               }
+
+               submodule_set_lookup_error(error, name);
+               return error;
+       }
+
+       if (out)
+               *out = sm;
+       else
+               git_submodule_free(sm);
+
+       return 0;
+}
+
+int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags)
+{
+       git_str buf = GIT_STR_INIT;
+       int error, isvalid;
+
+       if (flags == 0)
+               flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS;
+
+       /* Avoid allocating a new string if we can avoid it */
+       if (strchr(name, '\\') != NULL) {
+               if ((error = git_fs_path_normalize_slashes(&buf, name)) < 0)
+                       return error;
+       } else {
+               git_str_attach_notowned(&buf, name, strlen(name));
+       }
+
+       isvalid = git_path_is_valid(repo, buf.ptr, 0, flags);
+       git_str_dispose(&buf);
+
+       return isvalid;
+}
+
+static void submodule_free_dup(void *sm)
+{
+       git_submodule_free(sm);
+}
+
+static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name)
+{
+       git_submodule *sm = NULL;
+       int error;
+
+       if ((sm = git_strmap_get(map, name)) != NULL)
+               goto done;
+
+       /* if the submodule doesn't exist yet in the map, create it */
+       if ((error = submodule_alloc(&sm, repo, name)) < 0)
+               return error;
+
+       if ((error = git_strmap_set(map, sm->name, sm)) < 0) {
+               git_submodule_free(sm);
+               return error;
+       }
+
+done:
+       GIT_REFCOUNT_INC(sm);
+       *out = sm;
+       return 0;
+}
+
+static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg)
+{
+       int error;
+       git_iterator *i = NULL;
+       const git_index_entry *entry;
+       git_strmap *names;
+
+       if ((error = load_submodule_names(&names, git_index_owner(idx), cfg)))
+               goto done;
+
+       if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
+               goto done;
+
+       while (!(error = git_iterator_advance(&entry, i))) {
+               git_submodule *sm;
+
+               if ((sm = git_strmap_get(map, entry->path)) != NULL) {
+                       if (S_ISGITLINK(entry->mode))
+                               submodule_update_from_index_entry(sm, entry);
+                       else
+                               sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+               } else if (S_ISGITLINK(entry->mode)) {
+                       const char *name;
+
+                       if ((name = git_strmap_get(names, entry->path)) == NULL)
+                               name = entry->path;
+
+                       if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) {
+                               submodule_update_from_index_entry(sm, entry);
+                               git_submodule_free(sm);
+                       }
+               }
+       }
+
+       if (error == GIT_ITEROVER)
+               error = 0;
+
+done:
+       git_iterator_free(i);
+       free_submodule_names(names);
+
+       return error;
+}
+
+static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg)
+{
+       int error;
+       git_iterator *i = NULL;
+       const git_index_entry *entry;
+       git_strmap *names;
+
+       if ((error = load_submodule_names(&names, git_tree_owner(head), cfg)))
+               goto done;
+
+       if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
+               goto done;
+
+       while (!(error = git_iterator_advance(&entry, i))) {
+               git_submodule *sm;
+
+               if ((sm = git_strmap_get(map, entry->path)) != NULL) {
+                       if (S_ISGITLINK(entry->mode))
+                               submodule_update_from_head_data(sm, entry->mode, &entry->id);
+                       else
+                               sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+               } else if (S_ISGITLINK(entry->mode)) {
+                       const char *name;
+
+                       if ((name = git_strmap_get(names, entry->path)) == NULL)
+                               name = entry->path;
+
+                       if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) {
+                               submodule_update_from_head_data(
+                                       sm, entry->mode, &entry->id);
+                               git_submodule_free(sm);
+                       }
+               }
+       }
+
+       if (error == GIT_ITEROVER)
+               error = 0;
+
+done:
+       git_iterator_free(i);
+       free_submodule_names(names);
+
+       return error;
+}
+
+/* If have_sm is true, sm is populated, otherwise map an repo are. */
+typedef struct {
+       git_config *mods;
+       git_strmap *map;
+       git_repository *repo;
+} lfc_data;
+
+int git_submodule__map(git_repository *repo, git_strmap *map)
+{
+       int error = 0;
+       git_index *idx = NULL;
+       git_tree *head = NULL;
+       git_str path = GIT_STR_INIT;
+       git_submodule *sm;
+       git_config *mods = NULL;
+       bool has_workdir;
+
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(map);
+
+       /* get sources that we will need to check */
+       if (git_repository_index(&idx, repo) < 0)
+               git_error_clear();
+       if (git_repository_head_tree(&head, repo) < 0)
+               git_error_clear();
+
+       has_workdir = git_repository_workdir(repo) != NULL;
+
+       if (has_workdir &&
+           (error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0)
+               goto cleanup;
+
+       /* add submodule information from .gitmodules */
+       if (has_workdir) {
+               lfc_data data = { 0 };
+               data.map = map;
+               data.repo = repo;
+
+               if ((error = gitmodules_snapshot(&mods, repo)) < 0) {
+                       if (error == GIT_ENOTFOUND)
+                               error = 0;
+                       goto cleanup;
+               }
+
+               data.mods = mods;
+               if ((error = git_config_foreach(
+                           mods, submodule_load_each, &data)) < 0)
+                       goto cleanup;
+       }
+       /* add back submodule information from index */
+       if (mods && idx) {
+               if ((error = submodules_from_index(map, idx, mods)) < 0)
+                       goto cleanup;
+       }
+       /* add submodule information from HEAD */
+       if (mods && head) {
+               if ((error = submodules_from_head(map, head, mods)) < 0)
+                       goto cleanup;
+       }
+       /* shallow scan submodules in work tree as needed */
+       if (has_workdir) {
+               git_strmap_foreach_value(map, sm, {
+                               submodule_load_from_wd_lite(sm);
+                       });
+       }
+
+cleanup:
+       git_config_free(mods);
+       /* TODO: if we got an error, mark submodule config as invalid? */
+       git_index_free(idx);
+       git_tree_free(head);
+       git_str_dispose(&path);
+       return error;
+}
+
+int git_submodule_foreach(
+       git_repository *repo,
+       git_submodule_cb callback,
+       void *payload)
+{
+       git_vector snapshot = GIT_VECTOR_INIT;
+       git_strmap *submodules;
+       git_submodule *sm;
+       int error;
+       size_t i;
+
+       if (repo->is_bare) {
+               git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree");
+               return -1;
+       }
+
+       if ((error = git_strmap_new(&submodules)) < 0)
+               return error;
+
+       if ((error = git_submodule__map(repo, submodules)) < 0)
+               goto done;
+
+       if (!(error = git_vector_init(
+                       &snapshot, git_strmap_size(submodules), submodule_cmp))) {
+
+               git_strmap_foreach_value(submodules, sm, {
+                       if ((error = git_vector_insert(&snapshot, sm)) < 0)
+                               break;
+                       GIT_REFCOUNT_INC(sm);
+               });
+       }
+
+       if (error < 0)
+               goto done;
+
+       git_vector_uniq(&snapshot, submodule_free_dup);
+
+       git_vector_foreach(&snapshot, i, sm) {
+               if ((error = callback(sm, sm->name, payload)) != 0) {
+                       git_error_set_after_callback(error);
+                       break;
+               }
+       }
+
+done:
+       git_vector_foreach(&snapshot, i, sm)
+               git_submodule_free(sm);
+       git_vector_free(&snapshot);
+
+       git_strmap_foreach_value(submodules, sm, {
+               git_submodule_free(sm);
+       });
+       git_strmap_free(submodules);
+
+       return error;
+}
+
+static int submodule_repo_init(
+       git_repository **out,
+       git_repository *parent_repo,
+       const char *path,
+       const char *url,
+       bool use_gitlink)
+{
+       int error = 0;
+       git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT;
+       git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+       git_repository *subrepo = NULL;
+
+       error = git_repository_workdir_path(&workdir, parent_repo, path);
+       if (error < 0)
+               goto cleanup;
+
+       initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
+       initopt.origin_url = url;
+
+       /* init submodule repository and add origin remote as needed */
+
+       /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
+        * gitlink in the sub-repo workdir directory to that repository
+        *
+        * Old style: sub-repo goes directly into repo/<name>/.git/
+        */
+        if (use_gitlink) {
+               error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
+               if (error < 0)
+                       goto cleanup;
+               error = git_str_joinpath(&repodir, repodir.ptr, path);
+               if (error < 0)
+                       goto cleanup;
+
+               initopt.workdir_path = workdir.ptr;
+               initopt.flags |=
+                       GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
+                       GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
+
+               error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+       } else
+               error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt);
+
+cleanup:
+       git_str_dispose(&workdir);
+       git_str_dispose(&repodir);
+
+       *out = subrepo;
+
+       return error;
+}
+
+static int git_submodule__resolve_url(
+       git_str *out,
+       git_repository *repo,
+       const char *url)
+{
+       int error = 0;
+       git_str normalized = GIT_STR_INIT;
+
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(url);
+
+       /* We do this in all platforms in case someone on Windows created the .gitmodules */
+       if (strchr(url, '\\')) {
+               if ((error = git_fs_path_normalize_slashes(&normalized, url)) < 0)
+                       return error;
+
+               url = normalized.ptr;
+       }
+
+
+       if (git_fs_path_is_relative(url)) {
+               if (!(error = get_url_base(out, repo)))
+                       error = git_fs_path_apply_relative(out, url);
+       } else if (strchr(url, ':') != NULL || url[0] == '/') {
+               error = git_str_sets(out, url);
+       } else {
+               git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL");
+               error = -1;
+       }
+
+       git_str_dispose(&normalized);
+       return error;
+}
+
+int git_submodule_resolve_url(
+       git_buf *out,
+       git_repository *repo,
+       const char *url)
+{
+       GIT_BUF_WRAP_PRIVATE(out, git_submodule__resolve_url, repo, url);
+}
+
+int git_submodule_add_setup(
+       git_submodule **out,
+       git_repository *repo,
+       const char *url,
+       const char *path,
+       int use_gitlink)
+{
+       int error = 0;
+       git_config_backend *mods = NULL;
+       git_submodule *sm = NULL;
+       git_str name = GIT_STR_INIT, real_url = GIT_STR_INIT;
+       git_repository *subrepo = NULL;
+       bool path_occupied;
+
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(url);
+       GIT_ASSERT_ARG(path);
+
+       /* see if there is already an entry for this submodule */
+
+       if (git_submodule_lookup(NULL, repo, path) < 0)
+               git_error_clear();
+       else {
+               git_error_set(GIT_ERROR_SUBMODULE,
+                       "attempt to add submodule '%s' that already exists", path);
+               return GIT_EEXISTS;
+       }
+
+       /* validate and normalize path */
+
+       if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
+               path += strlen(git_repository_workdir(repo));
+
+       if (git_fs_path_root(path) >= 0) {
+               git_error_set(GIT_ERROR_SUBMODULE, "submodule path must be a relative path");
+               error = -1;
+               goto cleanup;
+       }
+
+       if ((error = is_path_occupied(&path_occupied, repo, path)) < 0)
+               goto cleanup;
+
+       if (path_occupied) {
+               error = GIT_EEXISTS;
+               goto cleanup;
+       }
+
+       /* update .gitmodules */
+
+       if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) {
+               git_error_set(GIT_ERROR_SUBMODULE,
+                       "adding submodules to a bare repository is not supported");
+               return -1;
+       }
+
+       if ((error = git_str_printf(&name, "submodule.%s.path", path)) < 0 ||
+               (error = git_config_backend_set_string(mods, name.ptr, path)) < 0)
+               goto cleanup;
+
+       if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
+               (error = git_config_backend_set_string(mods, name.ptr, url)) < 0)
+               goto cleanup;
+
+       git_str_clear(&name);
+
+       /* init submodule repository and add origin remote as needed */
+
+       error = git_repository_workdir_path(&name, repo, path);
+       if (error < 0)
+               goto cleanup;
+
+       /* if the repo does not already exist, then init a new repo and add it.
+        * Otherwise, just add the existing repo.
+        */
+       if (!(git_fs_path_exists(name.ptr) &&
+               git_fs_path_contains(&name, DOT_GIT))) {
+
+               /* resolve the actual URL to use */
+               if ((error = git_submodule__resolve_url(&real_url, repo, url)) < 0)
+                       goto cleanup;
+
+                if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0)
+                       goto cleanup;
+       }
+
+       if ((error = git_submodule_lookup(&sm, repo, path)) < 0)
+               goto cleanup;
+
+       error = git_submodule_init(sm, false);
+
+cleanup:
+       if (error && sm) {
+               git_submodule_free(sm);
+               sm = NULL;
+       }
+       if (out != NULL)
+               *out = sm;
+
+       git_config_backend_free(mods);
+       git_repository_free(subrepo);
+       git_str_dispose(&real_url);
+       git_str_dispose(&name);
+
+       return error;
+}
+
+int git_submodule_repo_init(
+       git_repository **out,
+       const git_submodule *sm,
+       int use_gitlink)
+{
+       int error;
+       git_repository *sub_repo = NULL;
+       const char *configured_url;
+       git_config *cfg = NULL;
+       git_str buf = GIT_STR_INIT;
+
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(sm);
+
+       /* get the configured remote url of the submodule */
+       if ((error = git_str_printf(&buf, "submodule.%s.url", sm->name)) < 0 ||
+               (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 ||
+               (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 ||
+               (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0)
+               goto done;
+
+       *out = sub_repo;
+
+done:
+       git_config_free(cfg);
+       git_str_dispose(&buf);
+       return error;
+}
+
+static int clone_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
+{
+       GIT_UNUSED(url);
+       GIT_UNUSED(payload);
+       return git_remote_lookup(out, repo, name);
+}
+
+static int clone_return_repo(git_repository **out, const char *path, int bare, void *payload)
+{
+       git_submodule *sm = payload;
+
+       GIT_UNUSED(path);
+       GIT_UNUSED(bare);
+       return git_submodule_open(out, sm);
+}
+
+int git_submodule_clone(git_repository **out, git_submodule *submodule, const git_submodule_update_options *given_opts)
+{
+       int error;
+       git_repository *clone;
+       git_str rel_path = GIT_STR_INIT;
+       git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
+       git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+
+       GIT_ASSERT_ARG(submodule);
+
+       if (given_opts)
+               memcpy(&sub_opts, given_opts, sizeof(sub_opts));
+
+       GIT_ERROR_CHECK_VERSION(&sub_opts, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
+
+       memcpy(&opts.checkout_opts, &sub_opts.checkout_opts, sizeof(sub_opts.checkout_opts));
+       memcpy(&opts.fetch_opts, &sub_opts.fetch_opts, sizeof(sub_opts.fetch_opts));
+       opts.repository_cb = clone_return_repo;
+       opts.repository_cb_payload = submodule;
+       opts.remote_cb = clone_return_origin;
+       opts.remote_cb_payload = submodule;
+
+       error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule));
+       if (error < 0)
+               goto cleanup;
+
+       error = git_clone__submodule(&clone, git_submodule_url(submodule), git_str_cstr(&rel_path), &opts);
+       if (error < 0)
+               goto cleanup;
+
+       if (!out)
+               git_repository_free(clone);
+       else
+               *out = clone;
+
+cleanup:
+       git_str_dispose(&rel_path);
+
+       return error;
+}
+
+int git_submodule_add_finalize(git_submodule *sm)
+{
+       int error;
+       git_index *index;
+
+       GIT_ASSERT_ARG(sm);
+
+       if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+               (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
+               return error;
+
+       return git_submodule_add_to_index(sm, true);
+}
+
+int git_submodule_add_to_index(git_submodule *sm, int write_index)
+{
+       int error;
+       git_repository *sm_repo = NULL;
+       git_index *index;
+       git_str path = GIT_STR_INIT;
+       git_commit *head;
+       git_index_entry entry;
+       struct stat st;
+
+       GIT_ASSERT_ARG(sm);
+
+       /* force reload of wd OID by git_submodule_open */
+       sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
+
+       if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+           (error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 ||
+           (error = git_submodule_open(&sm_repo, sm)) < 0)
+               goto cleanup;
+
+       /* read stat information for submodule working directory */
+       if (p_stat(path.ptr, &st) < 0) {
+               git_error_set(GIT_ERROR_SUBMODULE,
+                       "cannot add submodule without working directory");
+               error = -1;
+               goto cleanup;
+       }
+
+       memset(&entry, 0, sizeof(entry));
+       entry.path = sm->path;
+       git_index_entry__init_from_stat(
+               &entry, &st, !(git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE));
+
+       /* calling git_submodule_open will have set sm->wd_oid if possible */
+       if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
+               git_error_set(GIT_ERROR_SUBMODULE,
+                       "cannot add submodule without HEAD to index");
+               error = -1;
+               goto cleanup;
+       }
+       git_oid_cpy(&entry.id, &sm->wd_oid);
+
+       if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
+               goto cleanup;
+
+       entry.ctime.seconds = (int32_t)git_commit_time(head);
+       entry.ctime.nanoseconds = 0;
+       entry.mtime.seconds = (int32_t)git_commit_time(head);
+       entry.mtime.nanoseconds = 0;
+
+       git_commit_free(head);
+
+       /* add it */
+       error = git_index_add(index, &entry);
+
+       /* write it, if requested */
+       if (!error && write_index) {
+               error = git_index_write(index);
+
+               if (!error)
+                       git_oid_cpy(&sm->index_oid, &sm->wd_oid);
+       }
+
+cleanup:
+       git_repository_free(sm_repo);
+       git_str_dispose(&path);
+       return error;
+}
+
+static const char *submodule_update_to_str(git_submodule_update_t update)
+{
+       int i;
+       for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i)
+               if (_sm_update_map[i].map_value == (int)update)
+                       return _sm_update_map[i].str_match;
+       return NULL;
+}
+
+git_repository *git_submodule_owner(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+       return submodule->repo;
+}
+
+const char *git_submodule_name(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+       return submodule->name;
+}
+
+const char *git_submodule_path(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+       return submodule->path;
+}
+
+const char *git_submodule_url(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+       return submodule->url;
+}
+
+static int write_var(git_repository *repo, const char *name, const char *var, const char *val)
+{
+       git_str key = GIT_STR_INIT;
+       git_config_backend *mods;
+       int error;
+
+       mods = open_gitmodules(repo, GITMODULES_CREATE);
+       if (!mods)
+               return -1;
+
+       if ((error = git_str_printf(&key, "submodule.%s.%s", name, var)) < 0)
+               goto cleanup;
+
+       if (val)
+               error = git_config_backend_set_string(mods, key.ptr, val);
+       else
+               error = git_config_backend_delete(mods, key.ptr);
+
+       git_str_dispose(&key);
+
+cleanup:
+       git_config_backend_free(mods);
+       return error;
+}
+
+static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival)
+{
+       git_configmap_t type;
+       const char *val;
+
+       if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) {
+               git_error_set(GIT_ERROR_SUBMODULE, "invalid value for %s", var);
+               return -1;
+       }
+
+       if (type == GIT_CONFIGMAP_TRUE)
+               val = "true";
+
+       return write_var(repo, name, var, val);
+}
+
+const char *git_submodule_branch(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+       return submodule->branch;
+}
+
+int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch)
+{
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       return write_var(repo, name, "branch", branch);
+}
+
+int git_submodule_set_url(git_repository *repo, const char *name, const char *url)
+{
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+       GIT_ASSERT_ARG(url);
+
+       return write_var(repo, name, "url", url);
+}
+
+const git_oid *git_submodule_index_id(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+
+       if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
+               return &submodule->index_oid;
+       else
+               return NULL;
+}
+
+const git_oid *git_submodule_head_id(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+
+       if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
+               return &submodule->head_oid;
+       else
+               return NULL;
+}
+
+const git_oid *git_submodule_wd_id(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL);
+
+       /* load unless we think we have a valid oid */
+       if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
+               git_repository *subrepo;
+
+               /* calling submodule open grabs the HEAD OID if possible */
+               if (!git_submodule_open_bare(&subrepo, submodule))
+                       git_repository_free(subrepo);
+               else
+                       git_error_clear();
+       }
+
+       if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
+               return &submodule->wd_oid;
+       else
+               return NULL;
+}
+
+git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_IGNORE_UNSPECIFIED);
+
+       return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ?
+               GIT_SUBMODULE_IGNORE_NONE : submodule->ignore;
+}
+
+int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore)
+{
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore);
+}
+
+git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_UPDATE_NONE);
+
+       return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+               GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update;
+}
+
+int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update)
+{
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update);
+}
+
+git_submodule_recurse_t git_submodule_fetch_recurse_submodules(
+       git_submodule *submodule)
+{
+       GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_RECURSE_NO);
+       return submodule->fetch_recurse;
+}
+
+int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse)
+{
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse);
+}
+
+static int submodule_repo_create(
+       git_repository **out,
+       git_repository *parent_repo,
+       const char *path)
+{
+       int error = 0;
+       git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT;
+       git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+       git_repository *subrepo = NULL;
+
+       initopt.flags =
+               GIT_REPOSITORY_INIT_MKPATH |
+               GIT_REPOSITORY_INIT_NO_REINIT |
+               GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
+               GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
+
+       /* Workdir: path to sub-repo working directory */
+       error = git_repository_workdir_path(&workdir, parent_repo, path);
+       if (error < 0)
+               goto cleanup;
+
+       initopt.workdir_path = workdir.ptr;
+
+       /**
+        * Repodir: path to the sub-repo. sub-repo goes in:
+        * <repo-dir>/modules/<name>/ with a gitlink in the
+        * sub-repo workdir directory to that repository.
+        */
+       error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
+       if (error < 0)
+               goto cleanup;
+       error = git_str_joinpath(&repodir, repodir.ptr, path);
+       if (error < 0)
+               goto cleanup;
+
+       error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+
+cleanup:
+       git_str_dispose(&workdir);
+       git_str_dispose(&repodir);
+
+       *out = subrepo;
+
+       return error;
+}
+
+/**
+ * Callback to override sub-repository creation when
+ * cloning a sub-repository.
+ */
+static int git_submodule_update_repo_init_cb(
+       git_repository **out,
+       const char *path,
+       int bare,
+       void *payload)
+{
+       git_submodule *sm;
+
+       GIT_UNUSED(bare);
+
+       sm = payload;
+
+       return submodule_repo_create(out, sm->repo, path);
+}
+
+int git_submodule_update_options_init(git_submodule_update_options *opts, unsigned int version)
+{
+       GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+               opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT);
+       return 0;
+}
+
+#ifndef GIT_DEPRECATE_HARD
+int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version)
+{
+       return git_submodule_update_options_init(opts, version);
+}
+#endif
+
+int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options)
+{
+       int error;
+       unsigned int submodule_status;
+       git_config *config = NULL;
+       const char *submodule_url;
+       git_repository *sub_repo = NULL;
+       git_remote *remote = NULL;
+       git_object *target_commit = NULL;
+       git_str buf = GIT_STR_INIT;
+       git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
+       git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT;
+
+       GIT_ASSERT_ARG(sm);
+
+       if (_update_options)
+               memcpy(&update_options, _update_options, sizeof(git_submodule_update_options));
+
+       GIT_ERROR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
+
+       /* Copy over the remote callbacks */
+       memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options));
+
+       /* Get the status of the submodule to determine if it is already initialized  */
+       if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0)
+               goto done;
+
+       /*
+        * If submodule work dir is not already initialized, check to see
+        * what we need to do (initialize, clone, return error...)
+        */
+       if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) {
+               /*
+                * Work dir is not initialized, check to see if the submodule
+                * info has been copied into .git/config
+                */
+               if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
+                       (error = git_str_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0)
+                       goto done;
+
+               if ((error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) {
+                       /*
+                        * If the error is not "not found" or if it is "not found" and we are not
+                        * initializing the submodule, then return error.
+                        */
+                       if (error != GIT_ENOTFOUND)
+                               goto done;
+
+                       if (!init) {
+                               git_error_set(GIT_ERROR_SUBMODULE, "submodule is not initialized");
+                               error = GIT_ERROR;
+                               goto done;
+                       }
+
+                       /* The submodule has not been initialized yet - initialize it now.*/
+                       if ((error = git_submodule_init(sm, 0)) < 0)
+                               goto done;
+
+                       git_config_free(config);
+                       config = NULL;
+
+                       if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
+                               (error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0)
+                               goto done;
+               }
+
+               /** submodule is initialized - now clone it **/
+               /* override repo creation */
+               clone_options.repository_cb = git_submodule_update_repo_init_cb;
+               clone_options.repository_cb_payload = sm;
+
+               /*
+                * Do not perform checkout as part of clone, instead we
+                * will checkout the specific commit manually.
+                */
+               clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;
+
+               if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 ||
+                       (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 ||
+                       (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0)
+                       goto done;
+       } else {
+               const git_oid *oid;
+
+               /**
+                * Work dir is initialized - look up the commit in the parent repository's index,
+                * update the workdir contents of the subrepository, and set the subrepository's
+                * head to the new commit.
+                */
+               if ((error = git_submodule_open(&sub_repo, sm)) < 0)
+                       goto done;
+
+               if ((oid = git_submodule_index_id(sm)) == NULL) {
+                       git_error_set(GIT_ERROR_SUBMODULE, "could not get ID of submodule in index");
+                       error = -1;
+                       goto done;
+               }
+
+               /* Look up the target commit in the submodule. */
+               if ((error = git_object_lookup(&target_commit, sub_repo, oid, GIT_OBJECT_COMMIT)) < 0) {
+                       /* If it isn't found then fetch and try again. */
+                       if (error != GIT_ENOTFOUND || !update_options.allow_fetch ||
+                               (error = lookup_default_remote(&remote, sub_repo)) < 0 ||
+                               (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 ||
+                               (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJECT_COMMIT)) < 0)
+                               goto done;
+               }
+
+               if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
+                       (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0)
+                       goto done;
+
+               /* Invalidate the wd flags as the workdir has been updated. */
+               sm->flags = sm->flags &
+                       ~(GIT_SUBMODULE_STATUS_IN_WD |
+                       GIT_SUBMODULE_STATUS__WD_OID_VALID |
+                       GIT_SUBMODULE_STATUS__WD_SCANNED);
+       }
+
+done:
+       git_str_dispose(&buf);
+       git_config_free(config);
+       git_object_free(target_commit);
+       git_remote_free(remote);
+       git_repository_free(sub_repo);
+
+       return error;
+}
+
+int git_submodule_init(git_submodule *sm, int overwrite)
+{
+       int error;
+       const char *val;
+       git_str key = GIT_STR_INIT, effective_submodule_url = GIT_STR_INIT;
+       git_config *cfg = NULL;
+
+       if (!sm->url) {
+               git_error_set(GIT_ERROR_SUBMODULE,
+                       "no URL configured for submodule '%s'", sm->name);
+               return -1;
+       }
+
+       if ((error = git_repository_config(&cfg, sm->repo)) < 0)
+               return error;
+
+       /* write "submodule.NAME.url" */
+
+       if ((error = git_submodule__resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 ||
+               (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
+               (error = git_config__update_entry(
+                       cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0)
+               goto cleanup;
+
+       /* write "submodule.NAME.update" if not default */
+
+       val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+               NULL : submodule_update_to_str(sm->update);
+
+       if ((error = git_str_printf(&key, "submodule.%s.update", sm->name)) < 0 ||
+               (error = git_config__update_entry(
+                       cfg, key.ptr, val, overwrite != 0, false)) < 0)
+               goto cleanup;
+
+       /* success */
+
+cleanup:
+       git_config_free(cfg);
+       git_str_dispose(&key);
+       git_str_dispose(&effective_submodule_url);
+
+       return error;
+}
+
+int git_submodule_sync(git_submodule *sm)
+{
+       git_str key = GIT_STR_INIT, url = GIT_STR_INIT, remote_name = GIT_STR_INIT;
+       git_repository *smrepo = NULL;
+       git_config *cfg = NULL;
+       int error = 0;
+
+       if (!sm->url) {
+               git_error_set(GIT_ERROR_SUBMODULE, "no URL configured for submodule '%s'", sm->name);
+               return -1;
+       }
+
+       /* copy URL over to config only if it already exists */
+       if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 ||
+           (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
+           (error = git_submodule__resolve_url(&url, sm->repo, sm->url)) < 0 ||
+           (error = git_config__update_entry(cfg, key.ptr, url.ptr, true, true)) < 0)
+               goto out;
+
+       if (!(sm->flags & GIT_SUBMODULE_STATUS_IN_WD))
+               goto out;
+
+       /* if submodule exists in the working directory, update remote url */
+       if ((error = git_submodule_open(&smrepo, sm)) < 0 ||
+           (error = git_repository_config__weakptr(&cfg, smrepo)) < 0)
+               goto out;
+
+       if (lookup_head_remote_key(&remote_name, smrepo) == 0) {
+               if ((error = git_str_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0)
+                       goto out;
+       } else if ((error = git_str_sets(&key, "remote.origin.url")) < 0) {
+               goto out;
+       }
+
+       if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0)
+               goto out;
+
+out:
+       git_repository_free(smrepo);
+       git_str_dispose(&remote_name);
+       git_str_dispose(&key);
+       git_str_dispose(&url);
+       return error;
+}
+
+static int git_submodule__open(
+       git_repository **subrepo, git_submodule *sm, bool bare)
+{
+       int error;
+       git_str path = GIT_STR_INIT;
+       unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH;
+       const char *wd;
+
+       GIT_ASSERT_ARG(sm);
+       GIT_ASSERT_ARG(subrepo);
+
+       if (git_repository__ensure_not_bare(
+                       sm->repo, "open submodule repository") < 0)
+               return GIT_EBAREREPO;
+
+       wd = git_repository_workdir(sm->repo);
+
+       if (git_str_join3(&path, '/', wd, sm->path, DOT_GIT) < 0)
+               return -1;
+
+       sm->flags = sm->flags &
+               ~(GIT_SUBMODULE_STATUS_IN_WD |
+                 GIT_SUBMODULE_STATUS__WD_OID_VALID |
+                 GIT_SUBMODULE_STATUS__WD_SCANNED);
+
+       if (bare)
+               flags |= GIT_REPOSITORY_OPEN_BARE;
+
+       error = git_repository_open_ext(subrepo, path.ptr, flags, wd);
+
+       /* if we opened the submodule successfully, grab HEAD OID, etc. */
+       if (!error) {
+               sm->flags |= GIT_SUBMODULE_STATUS_IN_WD |
+                       GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+               if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE))
+                       sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
+               else
+                       git_error_clear();
+       } else if (git_fs_path_exists(path.ptr)) {
+               sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED |
+                       GIT_SUBMODULE_STATUS_IN_WD;
+       } else {
+               git_str_rtruncate_at_char(&path, '/'); /* remove "/.git" */
+
+               if (git_fs_path_isdir(path.ptr))
+                       sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+       }
+
+       git_str_dispose(&path);
+
+       return error;
+}
+
+int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm)
+{
+       return git_submodule__open(subrepo, sm, true);
+}
+
+int git_submodule_open(git_repository **subrepo, git_submodule *sm)
+{
+       return git_submodule__open(subrepo, sm, false);
+}
+
+static void submodule_update_from_index_entry(
+       git_submodule *sm, const git_index_entry *ie)
+{
+       bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0;
+
+       if (!S_ISGITLINK(ie->mode)) {
+               if (!already_found)
+                       sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+       } else {
+               if (already_found)
+                       sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+               else
+                       git_oid_cpy(&sm->index_oid, &ie->id);
+
+               sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX |
+                       GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
+       }
+}
+
+static int submodule_update_index(git_submodule *sm)
+{
+       git_index *index;
+       const git_index_entry *ie;
+
+       if (git_repository_index__weakptr(&index, sm->repo) < 0)
+               return -1;
+
+       sm->flags = sm->flags &
+               ~(GIT_SUBMODULE_STATUS_IN_INDEX |
+                 GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
+
+       if (!(ie = git_index_get_bypath(index, sm->path, 0)))
+               return 0;
+
+       submodule_update_from_index_entry(sm, ie);
+
+       return 0;
+}
+
+static void submodule_update_from_head_data(
+       git_submodule *sm, mode_t mode, const git_oid *id)
+{
+       if (!S_ISGITLINK(mode))
+               sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+       else {
+               git_oid_cpy(&sm->head_oid, id);
+
+               sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD |
+                       GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+       }
+}
+
+static int submodule_update_head(git_submodule *submodule)
+{
+       git_tree *head = NULL;
+       git_tree_entry *te = NULL;
+
+       submodule->flags = submodule->flags &
+               ~(GIT_SUBMODULE_STATUS_IN_HEAD |
+                 GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
+
+       /* if we can't look up file in current head, then done */
+       if (git_repository_head_tree(&head, submodule->repo) < 0 ||
+               git_tree_entry_bypath(&te, head, submodule->path) < 0)
+               git_error_clear();
+       else
+               submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te));
+
+       git_tree_entry_free(te);
+       git_tree_free(head);
+       return 0;
+}
+
+int git_submodule_reload(git_submodule *sm, int force)
+{
+       git_config *mods = NULL;
+       int error;
+
+       GIT_UNUSED(force);
+
+       GIT_ASSERT_ARG(sm);
+
+       if ((error = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0)
+               /* This should come with a warning, but we've no API for that */
+               goto out;
+
+       if (git_repository_is_bare(sm->repo))
+               goto out;
+
+       /* refresh config data */
+       if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND)
+               goto out;
+
+       if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0)
+               goto out;
+
+       /* refresh wd data */
+       sm->flags &=
+               ~(GIT_SUBMODULE_STATUS_IN_WD |
+                 GIT_SUBMODULE_STATUS__WD_OID_VALID |
+                 GIT_SUBMODULE_STATUS__WD_FLAGS);
+
+       if ((error = submodule_load_from_wd_lite(sm)) < 0 ||
+           (error = submodule_update_index(sm)) < 0 ||
+           (error = submodule_update_head(sm)) < 0)
+               goto out;
+
+out:
+       git_config_free(mods);
+       return error;
+}
+
+static void submodule_copy_oid_maybe(
+       git_oid *tgt, const git_oid *src, bool valid)
+{
+       if (tgt) {
+               if (valid)
+                       memcpy(tgt, src, sizeof(*tgt));
+               else
+                       memset(tgt, 0, sizeof(*tgt));
+       }
+}
+
+int git_submodule__status(
+       unsigned int *out_status,
+       git_oid *out_head_id,
+       git_oid *out_index_id,
+       git_oid *out_wd_id,
+       git_submodule *sm,
+       git_submodule_ignore_t ign)
+{
+       unsigned int status;
+       git_repository *smrepo = NULL;
+
+       if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED)
+               ign = sm->ignore;
+
+       /* only return location info if ignore == all */
+       if (ign == GIT_SUBMODULE_IGNORE_ALL) {
+               *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS);
+               return 0;
+       }
+
+       /* If the user has requested caching submodule state, performing these
+        * expensive operations (especially `submodule_update_head`, which is
+        * bottlenecked on `git_repository_head_tree`) eliminates much of the
+        * advantage.  We will, therefore, interpret the request for caching to
+        * apply here to and skip them.
+        */
+
+       if (sm->repo->submodule_cache == NULL) {
+               /* refresh the index OID */
+               if (submodule_update_index(sm) < 0)
+                       return -1;
+
+               /* refresh the HEAD OID */
+               if (submodule_update_head(sm) < 0)
+                       return -1;
+       }
+
+       /* for ignore == dirty, don't scan the working directory */
+       if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
+               /* git_submodule_open_bare will load WD OID data */
+               if (git_submodule_open_bare(&smrepo, sm) < 0)
+                       git_error_clear();
+               else
+                       git_repository_free(smrepo);
+               smrepo = NULL;
+       } else if (git_submodule_open(&smrepo, sm) < 0) {
+               git_error_clear();
+               smrepo = NULL;
+       }
+
+       status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags);
+
+       submodule_get_index_status(&status, sm);
+       submodule_get_wd_status(&status, sm, smrepo, ign);
+
+       git_repository_free(smrepo);
+
+       *out_status = status;
+
+       submodule_copy_oid_maybe(out_head_id, &sm->head_oid,
+               (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0);
+       submodule_copy_oid_maybe(out_index_id, &sm->index_oid,
+               (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0);
+       submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid,
+               (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0);
+
+       return 0;
+}
+
+int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore)
+{
+       git_submodule *sm;
+       int error;
+
+       GIT_ASSERT_ARG(status);
+       GIT_ASSERT_ARG(repo);
+       GIT_ASSERT_ARG(name);
+
+       if ((error = git_submodule_lookup(&sm, repo, name)) < 0)
+               return error;
+
+       error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore);
+       git_submodule_free(sm);
+
+       return error;
+}
+
+int git_submodule_location(unsigned int *location, git_submodule *sm)
+{
+       GIT_ASSERT_ARG(location);
+       GIT_ASSERT_ARG(sm);
+
+       return git_submodule__status(
+               location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
+}
+
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+static int submodule_alloc(
+       git_submodule **out, git_repository *repo, const char *name)
+{
+       size_t namelen;
+       git_submodule *sm;
+
+       if (!name || !(namelen = strlen(name))) {
+               git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name");
+               return -1;
+       }
+
+       sm = git__calloc(1, sizeof(git_submodule));
+       GIT_ERROR_CHECK_ALLOC(sm);
+
+       sm->name = sm->path = git__strdup(name);
+       if (!sm->name) {
+               git__free(sm);
+               return -1;
+       }
+
+       GIT_REFCOUNT_INC(sm);
+       sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE;
+       sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT;
+       sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO;
+       sm->repo   = repo;
+       sm->branch = NULL;
+
+       *out = sm;
+       return 0;
+}
+
+static void submodule_release(git_submodule *sm)
+{
+       if (!sm)
+               return;
+
+       if (sm->repo) {
+               sm->repo = NULL;
+       }
+
+       if (sm->path != sm->name)
+               git__free(sm->path);
+       git__free(sm->name);
+       git__free(sm->url);
+       git__free(sm->branch);
+       git__memzero(sm, sizeof(*sm));
+       git__free(sm);
+}
+
+int git_submodule_dup(git_submodule **out, git_submodule *source)
+{
+       GIT_ASSERT_ARG(out);
+       GIT_ASSERT_ARG(source);
+
+       GIT_REFCOUNT_INC(source);
+
+       *out = source;
+       return 0;
+}
+
+void git_submodule_free(git_submodule *sm)
+{
+       if (!sm)
+               return;
+       GIT_REFCOUNT_DEC(sm, submodule_release);
+}
+
+static int submodule_config_error(const char *property, const char *value)
+{
+       git_error_set(GIT_ERROR_INVALID,
+               "invalid value for submodule '%s' property: '%s'", property, value);
+       return -1;
+}
+
+int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
+{
+       int val;
+
+       if (git_config_lookup_map_value(
+                       &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) {
+               *out = GIT_SUBMODULE_IGNORE_NONE;
+               return submodule_config_error("ignore", value);
+       }
+
+       *out = (git_submodule_ignore_t)val;
+       return 0;
+}
+
+int git_submodule_parse_update(git_submodule_update_t *out, const char *value)
+{
+       int val;
+
+       if (git_config_lookup_map_value(
+                       &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) {
+               *out = GIT_SUBMODULE_UPDATE_CHECKOUT;
+               return submodule_config_error("update", value);
+       }
+
+       *out = (git_submodule_update_t)val;
+       return 0;
+}
+
+static int submodule_parse_recurse(git_submodule_recurse_t *out, const char *value)
+{
+       int val;
+
+       if (git_config_lookup_map_value(
+                       &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) {
+               *out = GIT_SUBMODULE_RECURSE_YES;
+               return submodule_config_error("recurse", value);
+       }
+
+       *out = (git_submodule_recurse_t)val;
+       return 0;
+}
+
+static int get_value(const char **out, git_config *cfg, git_str *buf, const char *name, const char *field)
+{
+       int error;
+
+       git_str_clear(buf);
+
+       if ((error = git_str_printf(buf, "submodule.%s.%s", name, field)) < 0 ||
+           (error = git_config_get_string(out, cfg, buf->ptr)) < 0)
+               return error;
+
+       return error;
+}
+
+static bool looks_like_command_line_option(const char *s)
+{
+       if (s && s[0] == '-')
+               return true;
+
+       return false;
+}
+
+static int submodule_read_config(git_submodule *sm, git_config *cfg)
+{
+       git_str key = GIT_STR_INIT;
+       const char *value;
+       int error, in_config = 0;
+
+       /*
+        * TODO: Look up path in index and if it is present but not a GITLINK
+        * then this should be deleted (at least to match git's behavior)
+        */
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) {
+               in_config = 1;
+               /* We would warn here if we had that API */
+               if (!looks_like_command_line_option(value)) {
+       /*
+        * TODO: if case insensitive filesystem, then the following strcmp
+        * should be strcasecmp
+        */
+                       if (strcmp(sm->name, value) != 0) {
+                               if (sm->path != sm->name)
+                                       git__free(sm->path);
+                               sm->path = git__strdup(value);
+                               GIT_ERROR_CHECK_ALLOC(sm->path);
+                       }
+
+               }
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) {
+               /* We would warn here if we had that API */
+               if (!looks_like_command_line_option(value)) {
+                       in_config = 1;
+                       sm->url = git__strdup(value);
+                       GIT_ERROR_CHECK_ALLOC(sm->url);
+               }
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) {
+               in_config = 1;
+               sm->branch = git__strdup(value);
+               GIT_ERROR_CHECK_ALLOC(sm->branch);
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) {
+               in_config = 1;
+               if ((error = git_submodule_parse_update(&sm->update, value)) < 0)
+                       goto cleanup;
+               sm->update_default = sm->update;
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) {
+               in_config = 1;
+               if ((error = submodule_parse_recurse(&sm->fetch_recurse, value)) < 0)
+                       goto cleanup;
+               sm->fetch_recurse_default = sm->fetch_recurse;
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) {
+               in_config = 1;
+               if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0)
+                       goto cleanup;
+               sm->ignore_default = sm->ignore;
+       } else if (error != GIT_ENOTFOUND) {
+               goto cleanup;
+       }
+
+       if (in_config)
+               sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+
+       error = 0;
+
+cleanup:
+       git_str_dispose(&key);
+       return error;
+}
+
+static int submodule_load_each(const git_config_entry *entry, void *payload)
+{
+       lfc_data *data = payload;
+       const char *namestart, *property;
+       git_strmap *map = data->map;
+       git_str name = GIT_STR_INIT;
+       git_submodule *sm;
+       int error, isvalid;
+
+       if (git__prefixcmp(entry->name, "submodule.") != 0)
+               return 0;
+
+       namestart = entry->name + strlen("submodule.");
+       property  = strrchr(namestart, '.');
+
+       if (!property || (property == namestart))
+               return 0;
+
+       property++;
+
+       if ((error = git_str_set(&name, namestart, property - namestart -1)) < 0)
+               return error;
+
+       isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0);
+       if (isvalid <= 0) {
+               error = isvalid;
+               goto done;
+       }
+
+       /*
+        * Now that we have the submodule's name, we can use that to
+        * figure out whether it's in the map. If it's not, we create
+        * a new submodule, load the config and insert it. If it's
+        * already inserted, we've already loaded it, so we skip.
+        */
+       if (git_strmap_exists(map, name.ptr)) {
+               error = 0;
+               goto done;
+       }
+
+       if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0)
+               goto done;
+
+       if ((error = submodule_read_config(sm, data->mods)) < 0) {
+               git_submodule_free(sm);
+               goto done;
+       }
+
+       if ((error = git_strmap_set(map, sm->name, sm)) < 0)
+               goto done;
+
+       error = 0;
+
+done:
+       git_str_dispose(&name);
+       return error;
+}
+
+static int submodule_load_from_wd_lite(git_submodule *sm)
+{
+       git_str path = GIT_STR_INIT;
+
+       if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0)
+               return -1;
+
+       if (git_fs_path_isdir(path.ptr))
+               sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+       if (git_fs_path_contains(&path, DOT_GIT))
+               sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
+
+       git_str_dispose(&path);
+       return 0;
+}
+
+/**
+ * Requests a snapshot of $WORK_TREE/.gitmodules.
+ *
+ * Returns GIT_ENOTFOUND in case no .gitmodules file exist
+ */
+static int gitmodules_snapshot(git_config **snap, git_repository *repo)
+{
+       git_config *mods = NULL;
+       git_str path = GIT_STR_INIT;
+       int error;
+
+       if (git_repository_workdir(repo) == NULL)
+               return GIT_ENOTFOUND;
+
+       if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0)
+               return error;
+
+       if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0)
+               goto cleanup;
+       git_str_dispose(&path);
+
+       if ((error = git_config_snapshot(snap, mods)) < 0)
+               goto cleanup;
+
+       error = 0;
+
+cleanup:
+       if (mods)
+               git_config_free(mods);
+       git_str_dispose(&path);
+
+       return error;
+}
+
+static git_config_backend *open_gitmodules(
+       git_repository *repo,
+       int okay_to_create)
+{
+       git_str path = GIT_STR_INIT;
+       git_config_backend *mods = NULL;
+
+       if (git_repository_workdir(repo) != NULL) {
+               if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0)
+                       return NULL;
+
+               if (okay_to_create || git_fs_path_isfile(path.ptr)) {
+                       /* git_config_backend_from_file should only fail if OOM */
+                       if (git_config_backend_from_file(&mods, path.ptr) < 0)
+                               mods = NULL;
+                       /* open should only fail here if the file is malformed */
+                       else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) {
+                               git_config_backend_free(mods);
+                               mods = NULL;
+                       }
+               }
+       }
+
+       git_str_dispose(&path);
+
+       return mods;
+}
+
+/* Lookup name of remote of the local tracking branch HEAD points to */
+static int lookup_head_remote_key(git_str *remote_name, git_repository *repo)
+{
+       int error;
+       git_reference *head = NULL;
+       git_str upstream_name = GIT_STR_INIT;
+
+       /* lookup and dereference HEAD */
+       if ((error = git_repository_head(&head, repo)) < 0)
+               return error;
+
+       /**
+        * If head does not refer to a branch, then return
+        * GIT_ENOTFOUND to indicate that we could not find
+        * a remote key for the local tracking branch HEAD points to.
+        **/
+       if (!git_reference_is_branch(head)) {
+               git_error_set(GIT_ERROR_INVALID,
+                       "HEAD does not refer to a branch.");
+               error = GIT_ENOTFOUND;
+               goto done;
+       }
+
+       /* lookup remote tracking branch of HEAD */
+       if ((error = git_branch__upstream_name(
+               &upstream_name,
+               repo,
+               git_reference_name(head))) < 0)
+               goto done;
+
+       /* lookup remote of remote tracking branch */
+       if ((error = git_branch__remote_name(remote_name, repo, upstream_name.ptr)) < 0)
+               goto done;
+
+done:
+       git_str_dispose(&upstream_name);
+       git_reference_free(head);
+
+       return error;
+}
+
+/* Lookup the remote of the local tracking branch HEAD points to */
+static int lookup_head_remote(git_remote **remote, git_repository *repo)
+{
+       int error;
+       git_str remote_name = GIT_STR_INIT;
+
+       /* lookup remote of remote tracking branch name */
+       if (!(error = lookup_head_remote_key(&remote_name, repo)))
+               error = git_remote_lookup(remote, repo, remote_name.ptr);
+
+       git_str_dispose(&remote_name);
+
+       return error;
+}
+
+/* Lookup remote, either from HEAD or fall back on origin */
+static int lookup_default_remote(git_remote **remote, git_repository *repo)
+{
+       int error = lookup_head_remote(remote, repo);
+
+       /* if that failed, use 'origin' instead */
+       if (error == GIT_ENOTFOUND || error == GIT_EUNBORNBRANCH)
+               error = git_remote_lookup(remote, repo, "origin");
+
+       if (error == GIT_ENOTFOUND)
+               git_error_set(
+                       GIT_ERROR_SUBMODULE,
+                       "cannot get default remote for submodule - no local tracking "
+                       "branch for HEAD and origin does not exist");
+
+       return error;
+}
+
+static int get_url_base(git_str *url, git_repository *repo)
+{
+       int error;
+       git_worktree *wt = NULL;
+       git_remote *remote = NULL;
+
+       if ((error = lookup_default_remote(&remote, repo)) == 0) {
+               error = git_str_sets(url, git_remote_url(remote));
+               goto out;
+       } else if (error != GIT_ENOTFOUND)
+               goto out;
+       else
+               git_error_clear();
+
+       /* if repository does not have a default remote, use workdir instead */
+       if (git_repository_is_worktree(repo)) {
+               if ((error = git_worktree_open_from_repository(&wt, repo)) < 0)
+                       goto out;
+               error = git_str_sets(url, wt->parent_path);
+       } else {
+               error = git_str_sets(url, git_repository_workdir(repo));
+       }
+
+out:
+       git_remote_free(remote);
+       git_worktree_free(wt);
+
+       return error;
+}
+
+static void submodule_get_index_status(unsigned int *status, git_submodule *sm)
+{
+       const git_oid *head_oid  = git_submodule_head_id(sm);
+       const git_oid *index_oid = git_submodule_index_id(sm);
+
+       *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS;
+
+       if (!head_oid) {
+               if (index_oid)
+                       *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
+       }
+       else if (!index_oid)
+               *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
+       else if (!git_oid_equal(head_oid, index_oid))
+               *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
+}
+
+
+static void submodule_get_wd_status(
+       unsigned int *status,
+       git_submodule *sm,
+       git_repository *sm_repo,
+       git_submodule_ignore_t ign)
+{
+       const git_oid *index_oid = git_submodule_index_id(sm);
+       const git_oid *wd_oid =
+               (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL;
+       git_tree *sm_head = NULL;
+       git_index *index = NULL;
+       git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+       git_diff *diff;
+
+       *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS;
+
+       if (!index_oid) {
+               if (wd_oid)
+                       *status |= GIT_SUBMODULE_STATUS_WD_ADDED;
+       }
+       else if (!wd_oid) {
+               if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
+                       (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
+                       *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
+               else
+                       *status |= GIT_SUBMODULE_STATUS_WD_DELETED;
+       }
+       else if (!git_oid_equal(index_oid, wd_oid))
+               *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
+
+       /* if we have no repo, then we're done */
+       if (!sm_repo)
+               return;
+
+       /* the diffs below could be optimized with an early termination
+        * option to the git_diff functions, but for now this is sufficient
+        * (and certainly no worse that what core git does).
+        */
+
+       if (ign == GIT_SUBMODULE_IGNORE_NONE)
+               opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+       (void)git_repository_index__weakptr(&index, sm_repo);
+
+       /* if we don't have an unborn head, check diff with index */
+       if (git_repository_head_tree(&sm_head, sm_repo) < 0)
+               git_error_clear();
+       else {
+               /* perform head to index diff on submodule */
+               if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0)
+                       git_error_clear();
+               else {
+                       if (git_diff_num_deltas(diff) > 0)
+                               *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
+                       git_diff_free(diff);
+                       diff = NULL;
+               }
+
+               git_tree_free(sm_head);
+       }
+
+       /* perform index-to-workdir diff on submodule */
+       if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0)
+               git_error_clear();
+       else {
+               size_t untracked =
+                       git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
+
+               if (untracked > 0)
+                       *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+
+               if (git_diff_num_deltas(diff) != untracked)
+                       *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+
+               git_diff_free(diff);
+               diff = NULL;
+       }
+}