diff options
author | Russell Belfer <rb@github.com> | 2012-08-03 00:00:58 +0400 |
---|---|---|
committer | Russell Belfer <rb@github.com> | 2012-08-24 22:00:26 +0400 |
commit | aa13bf05c84f10f364ce35c5d4f989337b36e043 (patch) | |
tree | ca5877bd006a6bfdcd9aad5587e36e89523d6cd2 /src | |
parent | decff7b4c13939e5f00d51aea4176fc543d73ede (diff) |
Major submodule rewrite
This replaces the old submodule API with a new extended API that
supports most of the things that can be done with `git submodule`.
Diffstat (limited to 'src')
-rw-r--r-- | src/config_file.c | 6 | ||||
-rw-r--r-- | src/config_file.h | 12 | ||||
-rw-r--r-- | src/diff.c | 2 | ||||
-rw-r--r-- | src/submodule.c | 1381 | ||||
-rw-r--r-- | src/submodule.h | 94 |
5 files changed, 1351 insertions, 144 deletions
diff --git a/src/config_file.c b/src/config_file.c index 547509b9f..aabb21f16 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -253,11 +253,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) char *tmp = NULL; git__free(key); + if (existing->next != NULL) { giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); return -1; } + /* don't update if old and new values already match */ + if ((!existing->value && !value) || + (existing->value && value && !strcmp(existing->value, value))) + return 0; + if (value) { tmp = git__strdup(value); GITERR_CHECK_ALLOC(tmp); diff --git a/src/config_file.h b/src/config_file.h index c31292881..bf687b516 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -19,12 +19,24 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) cfg->free(cfg); } +GIT_INLINE(int) git_config_file_get_string( + const char **out, git_config_file *cfg, const char *name) +{ + return cfg->get(cfg, name, out); +} + GIT_INLINE(int) git_config_file_set_string( git_config_file *cfg, const char *name, const char *value) { return cfg->set(cfg, name, value); } +GIT_INLINE(int) git_config_file_delete( + git_config_file *cfg, const char *name) +{ + return cfg->del(cfg, name); +} + GIT_INLINE(int) git_config_file_foreach( git_config_file *cfg, int (*fn)(const char *key, const char *value, void *data), diff --git a/src/diff.c b/src/diff.c index 9abf8b9f5..430f52e0a 100644 --- a/src/diff.c +++ b/src/diff.c @@ -530,7 +530,7 @@ static int maybe_modified( status = GIT_DELTA_UNMODIFIED; else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) return -1; - else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) + else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) status = GIT_DELTA_UNMODIFIED; else { /* TODO: support other GIT_SUBMODULE_IGNORE values */ diff --git a/src/submodule.c b/src/submodule.c index b8537cb8c..9a852041a 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -17,18 +17,24 @@ #include "config_file.h" #include "config.h" #include "repository.h" +#include "submodule.h" +#include "tree.h" +#include "iterator.h" + +#define GIT_MODULES_FILE ".gitmodules" static git_cvar_map _sm_update_map[] = { {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, - {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE} + {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, }; static git_cvar_map _sm_ignore_map[] = { - {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, - {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, - {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE} + {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, }; static kh_inline khint_t str_hash_no_trailing_slash(const char *s) @@ -55,9 +61,725 @@ static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b) return (alen == blen && strncmp(a, b, alen) == 0); } -__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash); +__KHASH_IMPL( + str, static kh_inline, const char *, void *, 1, + str_hash_no_trailing_slash, str_equal_no_trailing_slash); + +static int load_submodule_config( + git_repository *repo, bool force); +static git_config_file *open_gitmodules( + git_repository *, bool, const git_oid *); +static int lookup_head_remote( + git_buf *url, git_repository *repo); +static git_submodule *submodule_lookup_or_create( + git_repository *repo, const char *n1, const char *n2); +static int submodule_update_map( + git_repository *repo, git_submodule *sm, const char *key); +static void submodule_release( + git_submodule *sm, int decr); +static int submodule_load_from_index( + git_repository *, const git_index_entry *); +static int submodule_load_from_head( + git_repository *, const char *, const git_oid *); +static int submodule_load_from_config( + const char *, const char *, void *); +static int submodule_update_config( + git_submodule *, const char *, const char *, bool, bool); + +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_buf *key, const char *suffix) +{ + ssize_t idx = git_buf_rfind(key, '.'); + git_buf_truncate(key, (size_t)(idx + 1)); + return git_buf_puts(key, suffix); +} + +/* + * PUBLIC APIS + */ + +int git_submodule_lookup( + git_submodule **sm_ptr, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + int error; + khiter_t pos; + + assert(repo && name); + + if ((error = load_submodule_config(repo, false)) < 0) + return error; + + pos = git_strmap_lookup_index(repo->submodules, name); + + if (!git_strmap_valid_index(repo->submodules, pos)) { + error = GIT_ENOTFOUND; + + /* check if a plausible submodule exists at path */ + if (git_repository_workdir(repo)) { + git_buf path = GIT_BUF_INIT; + + if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0) + return -1; + + if (git_path_contains_dir(&path, DOT_GIT)) + error = GIT_EEXISTS; + + git_buf_free(&path); + } + + return error; + } + + if (sm_ptr) + *sm_ptr = git_strmap_value_at(repo->submodules, pos); + + return 0; +} + +int git_submodule_foreach( + git_repository *repo, + int (*callback)(git_submodule *sm, const char *name, void *payload), + void *payload) +{ + int error; + git_submodule *sm; + git_vector seen = GIT_VECTOR_INIT; + seen._cmp = submodule_cmp; + + assert(repo && callback); + + if ((error = load_submodule_config(repo, false)) < 0) + return error; + + git_strmap_foreach_value(repo->submodules, sm, { + /* Usually the following will not come into play - it just prevents + * us from issuing a callback twice for a submodule where the name + * and path are not the same. + */ + if (sm->refcount > 1) { + if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) + continue; + if ((error = git_vector_insert(&seen, sm)) < 0) + break; + } + + if ((error = callback(sm, sm->name, payload)) < 0) + break; + }); + + git_vector_free(&seen); + + return error; +} + +void git_submodule_config_free(git_repository *repo) +{ + git_strmap *smcfg; + git_submodule *sm; + + assert(repo); + + smcfg = repo->submodules; + repo->submodules = NULL; + + if (smcfg == NULL) + return; + + git_strmap_foreach_value(smcfg, sm, { + submodule_release(sm,1); + }); + git_strmap_free(smcfg); +} + +int git_submodule_add_setup( + git_submodule **submodule, + git_repository *repo, + const char *url, + const char *path, + int use_gitlink) +{ + int error = 0; + git_config_file *mods = NULL; + git_submodule *sm; + git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT; + git_repository_init_options initopt; + git_repository *subrepo = NULL; + + assert(repo && url && path); + + /* see if there is already an entry for this submodule */ + + if (git_submodule_lookup(&sm, repo, path) < 0) + giterr_clear(); + else { + giterr_set(GITERR_SUBMODULE, + "Attempt to add a submodule that already exists"); + return GIT_EEXISTS; + } + + /* resolve parameters */ + + if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) { + if (!(error = lookup_head_remote(&real_url, repo))) + error = git_path_apply_relative(&real_url, url); + } else if (strchr(url, ':') != NULL || url[0] == '/') { + error = git_buf_sets(&real_url, url); + } else { + giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL"); + error = -1; + } + if (error) + goto cleanup; + + /* validate and normalize path */ + + if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) + path += strlen(git_repository_workdir(repo)); + + if (git_path_root(path) >= 0) { + giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path"); + error = -1; + goto cleanup; + } + + /* update .gitmodules */ + + if ((mods = open_gitmodules(repo, true, NULL)) == NULL) { + giterr_set(GITERR_SUBMODULE, + "Adding submodules to a bare repository is not supported (for now)"); + return -1; + } + + if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 || + (error = git_config_file_set_string(mods, name.ptr, path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || + (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0) + goto cleanup; + + git_buf_clear(&name); + + /* init submodule repository and add origin remote as needed */ + + error = git_buf_joinpath(&name, git_repository_workdir(repo), path); + if (error < 0) + goto cleanup; + + /* 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/ + */ + + memset(&initopt, 0, sizeof(initopt)); + initopt.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_REINIT; + initopt.origin_url = real_url.ptr; + + if (git_path_exists(name.ptr) && + git_path_contains(&name, DOT_GIT)) + { + /* repo appears to already exist - reinit? */ + } + else if (use_gitlink) { + git_buf repodir = GIT_BUF_INIT; + + error = git_buf_join_n( + &repodir, '/', 3, git_repository_path(repo), "modules", path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = name.ptr; + initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + + git_buf_free(&repodir); + } + else { + error = git_repository_init_ext(&subrepo, name.ptr, &initopt); + } + if (error < 0) + goto cleanup; + + /* add submodule to hash and "reload" it */ + + if ((sm = submodule_lookup_or_create(repo, path, NULL)) == NULL) { + error = -1; + goto cleanup; + } + + if ((error = submodule_update_map(repo, sm, sm->path)) < 0) + goto cleanup; + + if ((error = git_submodule_reload(sm)) < 0) + goto cleanup; + + error = git_submodule_init(sm, false); + +cleanup: + if (submodule != NULL) + *submodule = !error ? sm : NULL; + + if (mods != NULL) + git_config_file_free(mods); + git_repository_free(subrepo); + git_buf_free(&real_url); + git_buf_free(&name); + + return error; +} + +int git_submodule_add_finalize(git_submodule *sm) +{ + int error; + git_index *index; + + assert(sm); + + if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || + (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0) + return error; + + return git_submodule_add_to_index(sm); +} + +int git_submodule_add_to_index(git_submodule *sm) +{ + int error; + git_repository *repo, *sm_repo; + git_index *index; + git_buf path = GIT_BUF_INIT; + git_commit *head; + git_index_entry entry; + struct stat st; + + assert(sm); + + repo = sm->owner; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_buf_joinpath( + &path, git_repository_workdir(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) { + giterr_set(GITERR_SUBMODULE, + "Cannot add submodule without working directory"); + error = -1; + goto cleanup; + } + git_index__init_entry_from_stat(&st, &entry); + + /* calling git_submodule_open will have set sm->wd_oid if possible */ + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { + giterr_set(GITERR_SUBMODULE, + "Cannot add submodule without HEAD to index"); + error = -1; + goto cleanup; + } + git_oid_cpy(&entry.oid, &sm->wd_oid); + + if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) + goto cleanup; + + entry.ctime.seconds = git_commit_time(head); + entry.ctime.nanoseconds = 0; + entry.mtime.seconds = git_commit_time(head); + entry.mtime.nanoseconds = 0; -static git_submodule *submodule_alloc(const char *name) + git_commit_free(head); + + /* now add it */ + error = git_index_add2(index, &entry); + +cleanup: + git_repository_free(sm_repo); + git_buf_free(&path); + return error; +} + +int git_submodule_save(git_submodule *submodule) +{ + int error = 0; + git_config_file *mods; + git_buf key = GIT_BUF_INIT; + + assert(submodule); + + mods = open_gitmodules(submodule->owner, true, NULL); + if (!mods) { + giterr_set(GITERR_SUBMODULE, + "Adding submodules to a bare repository is not supported (for now)"); + return -1; + } + + if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0) + goto cleanup; + + /* save values for path, url, update, ignore, fetchRecurseSubmodules */ + + if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 || + (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 || + (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0) + goto cleanup; + + if (!(error = submodule_config_key_trunc_puts(&key, "update")) && + submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) + { + const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : _sm_update_map[submodule->update].str_match; + error = git_config_file_set_string(mods, key.ptr, val); + } + if (error < 0) + goto cleanup; + + if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) && + submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT) + { + const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ? + NULL : _sm_ignore_map[submodule->ignore].str_match; + error = git_config_file_set_string(mods, key.ptr, val); + } + if (error < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts( + &key, "fetchRecurseSubmodules")) < 0 || + (error = git_config_file_set_string( + mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0) + goto cleanup; + + /* update internal defaults */ + + submodule->ignore_default = submodule->ignore; + submodule->update_default = submodule->update; + submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + +cleanup: + if (mods != NULL) + git_config_file_free(mods); + git_buf_free(&key); + + return error; +} + +git_repository *git_submodule_owner(git_submodule *submodule) +{ + assert(submodule); + return submodule->owner; +} + +const char *git_submodule_name(git_submodule *submodule) +{ + assert(submodule); + return submodule->name; +} + +const char *git_submodule_path(git_submodule *submodule) +{ + assert(submodule); + return submodule->path; +} + +const char *git_submodule_url(git_submodule *submodule) +{ + assert(submodule); + return submodule->url; +} + +int git_submodule_set_url(git_submodule *submodule, const char *url) +{ + assert(submodule && url); + + git__free(submodule->url); + + submodule->url = git__strdup(url); + GITERR_CHECK_ALLOC(submodule->url); + + return 0; +} + + const git_oid *git_submodule_index_oid(git_submodule *submodule) +{ + assert(submodule); + + if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) + return &submodule->index_oid; + else + return NULL; +} + +const git_oid *git_submodule_head_oid(git_submodule *submodule) +{ + assert(submodule); + + if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) + return &submodule->head_oid; + else + return NULL; +} + +const git_oid *git_submodule_wd_oid(git_submodule *submodule) +{ + assert(submodule); + + 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(&subrepo, submodule)) + git_repository_free(subrepo); + } + + 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) +{ + assert(submodule); + return submodule->ignore; +} + +git_submodule_ignore_t git_submodule_set_ignore( + git_submodule *submodule, git_submodule_ignore_t ignore) +{ + git_submodule_ignore_t old; + + assert(submodule); + + if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) + ignore = submodule->ignore_default; + + old = submodule->ignore; + submodule->ignore = ignore; + return old; +} + +git_submodule_update_t git_submodule_update(git_submodule *submodule) +{ + assert(submodule); + return submodule->update; +} + +git_submodule_update_t git_submodule_set_update( + git_submodule *submodule, git_submodule_update_t update) +{ + git_submodule_update_t old; + + assert(submodule); + + if (update == GIT_SUBMODULE_UPDATE_DEFAULT) + update = submodule->update_default; + + old = submodule->update; + submodule->update = update; + return old; +} + +int git_submodule_init(git_submodule *submodule, int overwrite) +{ + int error; + + /* write "submodule.NAME.url" */ + + if (!submodule->url) { + giterr_set(GITERR_SUBMODULE, + "No URL configured for submodule '%s'", submodule->name); + return -1; + } + + error = submodule_update_config( + submodule, "url", submodule->url, overwrite != 0, false); + if (error < 0) + return error; + + /* write "submodule.NAME.update" if not default */ + + if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) + error = submodule_update_config( + submodule, "update", NULL, (overwrite != 0), false); + else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) + error = submodule_update_config( + submodule, "update", + _sm_update_map[submodule->update].str_match, + (overwrite != 0), false); + + return error; +} + +int git_submodule_sync(git_submodule *submodule) +{ + if (!submodule->url) { + giterr_set(GITERR_SUBMODULE, + "No URL configured for submodule '%s'", submodule->name); + return -1; + } + + /* copy URL over to config only if it already exists */ + + return submodule_update_config( + submodule, "url", submodule->url, true, true); +} + +int git_submodule_open( + git_repository **subrepo, + git_submodule *submodule) +{ + int error; + git_buf path = GIT_BUF_INIT; + git_repository *repo; + const char *workdir; + + assert(submodule && subrepo); + + repo = submodule->owner; + workdir = git_repository_workdir(repo); + + if (!workdir) { + giterr_set(GITERR_REPOSITORY, + "Cannot open submodule repository in a bare repo"); + return GIT_ENOTFOUND; + } + + if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) { + giterr_set(GITERR_REPOSITORY, + "Cannot open submodule repository that is not checked out"); + return GIT_ENOTFOUND; + } + + if (git_buf_joinpath(&path, workdir, submodule->path) < 0) + return -1; + + error = git_repository_open(subrepo, path.ptr); + + git_buf_free(&path); + + /* if we have opened the submodule successfully, let's grab the HEAD OID */ + if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + if (!git_reference_name_to_oid( + &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) + submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + giterr_clear(); + } + + return error; +} + +int git_submodule_reload_all(git_repository *repo) +{ + assert(repo); + return load_submodule_config(repo, true); +} + +int git_submodule_reload(git_submodule *submodule) +{ + git_repository *repo; + git_index *index; + int pos, error; + git_tree *head; + git_config_file *mods; + + assert(submodule); + + /* refresh index data */ + + repo = submodule->owner; + if (git_repository_index__weakptr(&index, repo) < 0) + return -1; + + pos = git_index_find(index, submodule->path); + if (pos >= 0) { + git_index_entry *entry = git_index_get(index, pos); + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + if ((error = submodule_load_from_index(repo, entry)) < 0) + return error; + } + + /* refresh HEAD tree data */ + + if (!(error = git_repository_head_tree(&head, repo))) { + git_tree_entry *te; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { + error = submodule_load_from_head(repo, submodule->path, &te->oid); + + git_tree_entry_free(te); + } + else if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + git_tree_free(head); + } + + if (error < 0) + return error; + + /* refresh config data */ + + if ((mods = open_gitmodules(repo, false, NULL)) != NULL) { + git_buf path = GIT_BUF_INIT; + + git_buf_sets(&path, "submodule\\."); + git_buf_puts_escape_regex(&path, submodule->name); + git_buf_puts(&path, ".*"); + + if (git_buf_oom(&path)) + error = -1; + else + error = git_config_file_foreach_match( + mods, path.ptr, submodule_load_from_config, repo); + + git_buf_free(&path); + } + + return error; +} + +int git_submodule_status( + unsigned int *status, + git_submodule *submodule) +{ + assert(status && submodule); + + /* TODO: move status code from below and update */ + + *status = 0; + + return 0; +} + +/* + * INTERNAL FUNCTIONS + */ + +static git_submodule *submodule_alloc(git_repository *repo, const char *name) { git_submodule *sm = git__calloc(1, sizeof(git_submodule)); if (sm == NULL) @@ -69,6 +791,8 @@ static git_submodule *submodule_alloc(const char *name) return NULL; } + sm->owner = repo; + return sm; } @@ -80,69 +804,113 @@ static void submodule_release(git_submodule *sm, int decr) sm->refcount -= decr; if (sm->refcount == 0) { - if (sm->name != sm->path) + if (sm->name != sm->path) { git__free(sm->path); + sm->path = NULL; + } + git__free(sm->name); + sm->name = NULL; + git__free(sm->url); + sm->url = NULL; + + sm->owner = NULL; + git__free(sm); } } -static int submodule_from_entry( - git_strmap *smcfg, git_index_entry *entry) +static git_submodule *submodule_lookup_or_create( + git_repository *repo, const char *n1, const char *n2) { - git_submodule *sm; - void *old_sm; + git_strmap *smcfg = repo->submodules; khiter_t pos; - int error; + git_submodule *sm; - pos = git_strmap_lookup_index(smcfg, entry->path); + assert(n1); - if (git_strmap_valid_index(smcfg, pos)) - sm = git_strmap_value_at(smcfg, pos); + pos = git_strmap_lookup_index(smcfg, n1); + + if (!git_strmap_valid_index(smcfg, pos) && n2) + pos = git_strmap_lookup_index(smcfg, n2); + + if (!git_strmap_valid_index(smcfg, pos)) + sm = submodule_alloc(repo, n1); else - sm = submodule_alloc(entry->path); + sm = git_strmap_value_at(smcfg, pos); - git_oid_cpy(&sm->oid, &entry->oid); + return sm; +} - if (strcmp(sm->path, entry->path) != 0) { - if (sm->path != sm->name) { - git__free(sm->path); - sm->path = sm->name; - } - sm->path = git__strdup(entry->path); - if (!sm->path) - goto fail; +static int submodule_update_map( + git_repository *repo, git_submodule *sm, const char *key) +{ + void *old_sm; + int error; + + git_strmap_insert2(repo->submodules, key, sm, old_sm, error); + if (error < 0) { + submodule_release(sm, 0); + return -1; } - git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); - if (error < 0) - goto fail; sm->refcount++; - if (old_sm && ((git_submodule *)old_sm) != sm) { - /* TODO: log warning about multiple entrys for same submodule path */ + if (old_sm && ((git_submodule *)old_sm) != sm) submodule_release(old_sm, 1); - } return 0; +} -fail: - submodule_release(sm, 0); - return -1; +static int submodule_load_from_index( + git_repository *repo, const git_index_entry *entry) +{ + git_submodule *sm = submodule_lookup_or_create(repo, entry->path, NULL); + + if (!sm) + return -1; + + if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) { + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + return 0; + } + + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX; + + git_oid_cpy(&sm->index_oid, &entry->oid); + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID; + + return submodule_update_map(repo, sm, sm->path); } -static int submodule_from_config( +static int submodule_load_from_head( + git_repository *repo, const char *path, const git_oid *oid) +{ + git_submodule *sm = submodule_lookup_or_create(repo, path, NULL); + + if (!sm) + return -1; + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD; + + git_oid_cpy(&sm->head_oid, oid); + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID; + + return submodule_update_map(repo, sm, sm->path); +} + +static int submodule_load_from_config( const char *key, const char *value, void *data) { - git_strmap *smcfg = data; + git_repository *repo = data; + git_strmap *smcfg = repo->submodules; const char *namestart; const char *property; git_buf name = GIT_BUF_INIT; git_submodule *sm; void *old_sm = NULL; bool is_path; - khiter_t pos; int error; if (git__prefixcmp(key, "submodule.") != 0) @@ -153,21 +921,17 @@ static int submodule_from_config( if (property == NULL) return 0; property++; - is_path = (strcmp(property, "path") == 0); + is_path = (strcasecmp(property, "path") == 0); if (git_buf_set(&name, namestart, property - namestart - 1) < 0) return -1; - pos = git_strmap_lookup_index(smcfg, name.ptr); - if (!git_strmap_valid_index(smcfg, pos) && is_path) - pos = git_strmap_lookup_index(smcfg, value); - if (!git_strmap_valid_index(smcfg, pos)) - sm = submodule_alloc(name.ptr); - else - sm = git_strmap_value_at(smcfg, pos); + sm = submodule_lookup_or_create(repo, name.ptr, is_path ? value : NULL); if (!sm) goto fail; + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + if (strcmp(sm->name, name.ptr) != 0) { assert(sm->path == sm->name); sm->name = git_buf_detach(&name); @@ -177,7 +941,7 @@ static int submodule_from_config( goto fail; sm->refcount++; } - else if (is_path && strcmp(sm->path, value) != 0) { + else if (is_path && value && strcmp(sm->path, value) != 0) { assert(sm->path == sm->name); sm->path = git__strdup(value); if (sm->path == NULL) @@ -195,19 +959,23 @@ static int submodule_from_config( submodule_release(old_sm, 1); } + /* 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 (is_path) return 0; /* copy other properties into submodule entry */ - if (strcmp(property, "url") == 0) { + if (strcasecmp(property, "url") == 0) { if (sm->url) { git__free(sm->url); sm->url = NULL; } - if ((sm->url = git__strdup(value)) == NULL) + if (value != NULL && (sm->url = git__strdup(value)) == NULL) goto fail; } - else if (strcmp(property, "update") == 0) { + else if (strcasecmp(property, "update") == 0) { int val; if (git_config_lookup_map_value( _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { @@ -215,16 +983,16 @@ static int submodule_from_config( "Invalid value for submodule update property: '%s'", value); goto fail; } - sm->update = (git_submodule_update_t)val; + sm->update_default = sm->update = (git_submodule_update_t)val; } - else if (strcmp(property, "fetchRecurseSubmodules") == 0) { + else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { if (git__parse_bool(&sm->fetch_recurse, value) < 0) { giterr_set(GITERR_INVALID, "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); goto fail; } } - else if (strcmp(property, "ignore") == 0) { + else if (strcasecmp(property, "ignore") == 0) { int val; if (git_config_lookup_map_value( _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { @@ -232,7 +1000,7 @@ static int submodule_from_config( "Invalid value for submodule ignore property: '%s'", value); goto fail; } - sm->ignore = (git_submodule_ignore_t)val; + sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; } /* ignore other unknown submodule properties */ @@ -244,144 +1012,471 @@ fail: return -1; } -static int load_submodule_config(git_repository *repo) +static int submodule_load_from_wd_lite( + git_submodule *sm, const char *name, void *payload) +{ + git_repository *repo = git_submodule_owner(sm); + git_buf path = GIT_BUF_INIT; + + GIT_UNUSED(name); + GIT_UNUSED(payload); + + if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0) + return -1; + + if (git_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (git_path_contains(&path, DOT_GIT)) + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; + + git_buf_free(&path); + + return 0; +} + +static int load_submodule_config_from_index( + git_repository *repo, git_oid *gitmodules_oid) { int error; - git_index *index; - unsigned int i, max_i; - git_oid gitmodules_oid; - git_strmap *smcfg; - struct git_config_file *mods = NULL; + git_iterator *i; + const git_index_entry *entry; - if (repo->submodules) - return 0; + if ((error = git_iterator_for_index(&i, repo)) < 0) + return error; - /* submodule data is kept in a hashtable with each submodule stored - * under both its name and its path. These are usually the same, but - * that is not guaranteed. - */ - smcfg = git_strmap_alloc(); - GITERR_CHECK_ALLOC(smcfg); + error = git_iterator_current(i, &entry); - /* scan index for gitmodules (and .gitmodules entry) */ - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - goto cleanup; - memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); - max_i = git_index_entrycount(index); + while (!error && entry != NULL) { - for (i = 0; i < max_i; i++) { - git_index_entry *entry = git_index_get(index, i); if (S_ISGITLINK(entry->mode)) { - if ((error = submodule_from_entry(smcfg, entry)) < 0) - goto cleanup; - } - else if (strcmp(entry->path, ".gitmodules") == 0) - git_oid_cpy(&gitmodules_oid, &entry->oid); + error = submodule_load_from_index(repo, entry); + if (error < 0) + break; + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0) + git_oid_cpy(gitmodules_oid, &entry->oid); + + error = git_iterator_advance(i, &entry); } - /* load .gitmodules from workdir if it exists */ - if (git_repository_workdir(repo) != NULL) { - /* look in workdir for .gitmodules */ - git_buf path = GIT_BUF_INIT; - if (!git_buf_joinpath( - &path, git_repository_workdir(repo), ".gitmodules") && - git_path_isfile(path.ptr)) - { - if (!(error = git_config_file__ondisk(&mods, path.ptr))) - error = git_config_file_open(mods); + git_iterator_free(i); + + return error; +} + +static int load_submodule_config_from_head( + git_repository *repo, git_oid *gitmodules_oid) +{ + int error; + git_tree *head; + git_iterator *i; + const git_index_entry *entry; + + if ((error = git_repository_head_tree(&head, repo)) < 0) + return error; + + if ((error = git_iterator_for_tree(&i, repo, head)) < 0) { + git_tree_free(head); + return error; + } + + error = git_iterator_current(i, &entry); + + while (!error && entry != NULL) { + + if (S_ISGITLINK(entry->mode)) { + error = submodule_load_from_head(repo, entry->path, &entry->oid); + if (error < 0) + break; + } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && + git_oid_iszero(gitmodules_oid)) + git_oid_cpy(gitmodules_oid, &entry->oid); + + error = git_iterator_advance(i, &entry); + } + + git_iterator_free(i); + git_tree_free(head); + + return error; +} + +static git_config_file *open_gitmodules( + git_repository *repo, + bool okay_to_create, + const git_oid *gitmodules_oid) +{ + const char *workdir = git_repository_workdir(repo); + git_buf path = GIT_BUF_INIT; + git_config_file *mods = NULL; + + if (workdir != NULL) { + if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) + return NULL; + + if (okay_to_create || git_path_isfile(path.ptr)) { + /* git_config_file__ondisk should only fail if OOM */ + if (git_config_file__ondisk(&mods, path.ptr) < 0) + return NULL; + + /* open should only fail here if the file is malformed */ + if (git_config_file_open(mods) < 0) { + git_config_file_free(mods); + mods = NULL; + } } - git_buf_free(&path); } - /* load .gitmodules from object cache if not in workdir */ - if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { - /* TODO: is it worth loading gitmodules from object cache? */ + if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) { + /* TODO: Retrieve .gitmodules content from ODB */ + + /* Should we actually do this? Core git does not, but it means you + * can't really get much information about submodules on bare repos. + */ + } + + return mods; +} + +static int load_submodule_config(git_repository *repo, bool force) +{ + int error; + git_oid gitmodules_oid; + git_buf path = GIT_BUF_INIT; + git_config_file *mods = NULL; + + if (repo->submodules && !force) + return 0; + + memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); + + /* Submodule data is kept in a hashtable keyed by both name and path. + * These are usually the same, but that is not guaranteed. + */ + if (!repo->submodules) { + repo->submodules = git_strmap_alloc(); + GITERR_CHECK_ALLOC(repo->submodules); } - /* process .gitmodules info */ - if (!error && mods != NULL) - error = git_config_file_foreach(mods, submodule_from_config, smcfg); + /* add submodule information from index */ + + if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0) + goto cleanup; - /* store submodule config in repo */ - if (!error) - repo->submodules = smcfg; + /* add submodule information from HEAD */ + + if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0) + goto cleanup; + + /* add submodule information from .gitmodules */ + + if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL) + error = git_config_file_foreach(mods, submodule_load_from_config, repo); + + if (error != 0) + goto cleanup; + + /* shallow scan submodules in work tree */ + + if (!git_repository_is_bare(repo)) + error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL); cleanup: + git_buf_free(&path); + if (mods != NULL) git_config_file_free(mods); + if (error) - git_strmap_free(smcfg); + git_submodule_config_free(repo); + return error; } -void git_submodule_config_free(git_repository *repo) +static int lookup_head_remote(git_buf *url, git_repository *repo) { - git_strmap *smcfg = repo->submodules; - git_submodule *sm; + int error; + git_config *cfg; + git_reference *head = NULL, *remote = NULL; + const char *tgt, *scan; + git_buf key = GIT_BUF_INIT; + + /* 1. resolve HEAD -> refs/heads/BRANCH + * 2. lookup config branch.BRANCH.remote -> ORIGIN + * 3. lookup remote.ORIGIN.url + */ - repo->submodules = NULL; + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; - if (smcfg == NULL) - return; + if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD cannot be resolved"); + error = GIT_ENOTFOUND; + goto cleanup; + } - git_strmap_foreach_value(smcfg, sm, { - submodule_release(sm,1); - }); - git_strmap_free(smcfg); + if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD is not symbolic"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + if ((error = git_branch_tracking(&remote, head)) < 0) + goto cleanup; + + /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */ + + if (git_reference_type(remote) != GIT_REF_SYMBOLIC || + git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0) + { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD is not symbolic"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + scan = tgt = git_reference_target(remote) + strlen("refs/remotes/"); + while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\'))) + scan++; /* find non-escaped slash to end ORIGIN name */ + + error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt); + if (error < 0) + goto cleanup; + + if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0) + goto cleanup; + + error = git_buf_sets(url, tgt); + +cleanup: + git_buf_free(&key); + git_reference_free(head); + git_reference_free(remote); + + return error; } -static int submodule_cmp(const void *a, const void *b) +static int submodule_update_config( + git_submodule *submodule, + const char *attr, + const char *value, + bool overwrite, + bool only_existing) { - return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); + int error; + git_config *config; + git_buf key = GIT_BUF_INIT; + const char *old = NULL; + + assert(submodule); + + error = git_repository_config__weakptr(&config, submodule->owner); + if (error < 0) + return error; + + error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr); + if (error < 0) + goto cleanup; + + if (git_config_get_string(&old, config, key.ptr) < 0) + giterr_clear(); + + if (!old && only_existing) + goto cleanup; + if (old && !overwrite) + goto cleanup; + if ((!old && !value) || (old && value && strcmp(old, value) == 0)) + goto cleanup; + + if (!value) + error = git_config_delete(config, key.ptr); + else + error = git_config_set_string(config, key.ptr, value); + +cleanup: + git_buf_free(&key); + return error; } -int git_submodule_foreach( - git_repository *repo, - int (*callback)(const char *name, void *payload), - void *payload) +#if 0 + +static int head_oid_for_submodule( + git_oid *oid, + git_repository *owner, + const char *path) +{ + int error = 0; + git_oid head_oid; + git_tree *head_tree = NULL, *container_tree = NULL; + unsigned int pos; + const git_tree_entry *entry; + + if (git_reference_name_to_oid(&head_oid, owner, GIT_HEAD_FILE) < 0 || + git_tree_lookup(&head_tree, owner, &head_oid) < 0 || + git_tree_resolve_path(&container_tree, &pos, head_tree, path) < 0 || + (entry = git_tree_entry_byindex(container_tree, pos)) == NULL) + { + memset(oid, 0, sizeof(*oid)); + error = GIT_ENOTFOUND; + } + else { + git_oid_cpy(oid, &entry->oid); + } + + git_tree_free(head_tree); + git_tree_free(container_tree); + + return error; +} + +int git_submodule_status( + unsigned int *status, + git_oid *head, + git_submodule *sm, + git_submodule_ignore_t ignore) { int error; - git_submodule *sm; - git_vector seen = GIT_VECTOR_INIT; - seen._cmp = submodule_cmp; + const char *workdir; + git_repository *owner, *sm_repo = NULL; + git_oid owner_head, sm_head; - if ((error = load_submodule_config(repo)) < 0) - return error; + assert(submodule && status); - git_strmap_foreach_value(repo->submodules, sm, { - /* usually the following will not come into play */ - if (sm->refcount > 1) { - if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) - continue; - if ((error = git_vector_insert(&seen, sm)) < 0) - break; + if (head == NULL) + head = &sm_head; + + owner = submodule->owner; + workdir = git_repository_workdir(owner); + + if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) + ignore = sm->ignore; + + /* if this is a bare repo or the submodule dir has no .git yet, + * then it is not checked out and we'll just return index data. + */ + if (!workdir || (sm->flags & GIT_SUBMODULE_FLAG__HAS_DOTGIT) == 0) { + *status = GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT; + + if (sm->index_oid_valid) + git_oid_cpy(head, &sm->index_oid); + else + memset(head, 0, sizeof(git_oid)); + + if (git_oid_iszero(head)) { + if (sm->url) + *status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE; + } else if (!sm->url) { + *status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE; } - if ((error = callback(sm->name, payload)) < 0) - break; - }); + return 0; + } - git_vector_free(&seen); + /* look up submodule path in repo head to find if new or deleted */ + if ((error = head_oid_for_submodule(&owner_head, owner, sm->path)) < 0) { + *status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE; + /* ??? */ + } + + if (ignore == GIT_SUBMODULE_IGNORE_ALL) { + *status = GIT_SUBMODULE_STATUS_CLEAN; + git_oid_cpy(head, &sm->oid); + return 0; + } + + if ((error = git_submodule_open(&sm_repo, sm)) < 0) + return error; + + if ((error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE)) < 0) + goto cleanup; + + if (ignore == GIT_SUBMODULE_IGNORE_DIRTY && + git_oid_cmp(head, &sm->oid) == 0) + { + *status = GIT_SUBMODULE_STATUS_CLEAN; + return 0; + } + + /* look up submodule oid from index in repo to find if new commits or missing commits */ + + /* run a short status to find if modified or untracked content */ + +#define GIT_SUBMODULE_STATUS_NEW_SUBMODULE (1u << 2) +#define GIT_SUBMODULE_STATUS_DELETED_SUBMODULE (1u << 3) +#define GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT (1u << 4) +#define GIT_SUBMODULE_STATUS_NEW_COMMITS (1u << 5) +#define GIT_SUBMODULE_STATUS_MISSING_COMMITS (1u << 6) +#define GIT_SUBMODULE_STATUS_MODIFIED_CONTENT (1u << 7) +#define GIT_SUBMODULE_STATUS_UNTRACKED_CONTENT (1u << 8) + +cleanup: + git_repository_free(sm_repo); + git_tree_free(owner_tree); return error; } -int git_submodule_lookup( - git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ +int git_submodule_status_for_path( + unsigned int *status, + git_oid *head, git_repository *repo, - const char *name) /* trailing slash is allowed */ + const char *submodule_path, + git_submodule_ignore_t ignore) { - khiter_t pos; + int error; + git_submodule *sm; + const char *workdir; + git_buf path = GIT_BUF_INIT; + git_oid owner_head; + + assert(repo && submodule_path && status); + + if ((error = git_submodule_lookup(&sm, repo, submodule_path)) == 0) + return git_submodule_status(status, head, sm, ignore); - if (load_submodule_config(repo) < 0) + /* if submodule still exists in HEAD, then it is DELETED */ + if (!(error = head_oid_for_submodule(&owner_head, repo, submodule_path))) { + *status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE; + if (head) + git_oid_cmp(head, &owner_head); + return 0; + } + + /* submodule was not found - let's see what we can determine about it */ + workdir = git_repository_workdir(repo); + + if (error != GIT_ENOTFOUND || !workdir) { + *status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE; + return error; + } + + giterr_clear(); + error = 0; + + /* figure out if this is NEW, NOT_CHECKED_OUT, or what */ + if (git_buf_joinpath(&path, workdir, submodule_path) < 0) return -1; - pos = git_strmap_lookup_index(repo->submodules, name); - if (!git_strmap_valid_index(repo->submodules, pos)) - return GIT_ENOTFOUND; + if (git_path_contains(&path, DOT_GIT)) { + git_repository *sm_repo; - if (sm_ptr) - *sm_ptr = git_strmap_value_at(repo->submodules, pos); + *status = GIT_SUBMODULE_STATUS_UNTRACKED_SUBMODULE; - return 0; + /* only bother look up head if it was non-NULL */ + if (head != NULL && + !(error = git_repository_open(&sm_repo, path.ptr))) + { + error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE); + git_repository_free(sm_repo); + } + } else + *status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE; + + git_buf_free(&path); + + return error; } + +#endif diff --git a/src/submodule.h b/src/submodule.h new file mode 100644 index 000000000..83bc7dfe9 --- /dev/null +++ b/src/submodule.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_submodule_h__ +#define INCLUDE_submodule_h__ + +/* Notes: + * + * Submodule information can be in four places: the index, the config files + * (both .git/config and .gitmodules), the HEAD tree, and the working + * directory. + * + * In the index: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the HEAD tree: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the config files: + * - submodule is found by submodule "name" which is usually the path + * - may be missing or present + * - will have a name, path, url, and other properties + * + * In the working directory: + * - submodule is found by path + * - may be missing, an empty directory, a checked out directory, + * or of the wrong type + * - if checked out, will have a HEAD oid + * - if checked out, will have git history that can be used to compare oids + * - if checked out, may have modified files and/or untracked files + */ + +/** + * Description of submodule + * + * This record describes a submodule found in a repository. There should be + * an entry for every submodule found in the HEAD and index, and for every + * submodule described in .gitmodules. The fields are as follows: + * + * - `owner` is the git_repository containing this submodule + * - `name` is the name of the submodule from .gitmodules. + * - `path` is the path to the submodule from the repo root. It is almost + * always the same as `name`. + * - `url` is the url for the submodule. + * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule. + * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. + * - `refcount` tracks how many hashmap entries there are for this submodule. + * It only comes into play if the name and path of the submodule differ. + * - `flags` is for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and other misc info about it. + * + * If the submodule has been added to .gitmodules but not yet git added, + * then the `index_oid` will be valid and zero. If the submodule has been + * deleted, but the delete has not been committed yet, then the `index_oid` + * will be set, but the `url` will be NULL. + */ +struct git_submodule { + git_repository *owner; + char *name; + char *path; /* important: may point to same string data as "name" */ + char *url; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; + /* information from config */ + git_submodule_update_t update; + git_submodule_update_t update_default; + git_submodule_ignore_t ignore; + git_submodule_ignore_t ignore_default; + int fetch_recurse; + /* internal information */ + int refcount; +}; + +/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ +#define GIT_SUBMODULE_STATUS__WD_SCANNED (1u << 15) +#define GIT_SUBMODULE_STATUS__HEAD_OID_VALID (1u << 16) +#define GIT_SUBMODULE_STATUS__INDEX_OID_VALID (1u << 17) +#define GIT_SUBMODULE_STATUS__WD_OID_VALID (1u << 18) +#define GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES (1u << 19) + +#endif |