| /* |
| * 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 "git2/config.h" |
| #include "git2/sys/config.h" |
| #include "git2/types.h" |
| #include "git2/index.h" |
| #include "buffer.h" |
| #include "buf_text.h" |
| #include "vector.h" |
| #include "posix.h" |
| #include "config_backend.h" |
| #include "config.h" |
| #include "repository.h" |
| #include "tree.h" |
| #include "iterator.h" |
| #include "path.h" |
| #include "index.h" |
| #include "worktree.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_buf *url, git_repository *repo); |
| static int lookup_head_remote_key(git_buf *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_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 |
| */ |
| |
| 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_buf dir = GIT_BUF_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_buf_sets(&dir, path)) < 0) |
| goto out; |
| |
| if ((error = git_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_buf_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_buf buf = GIT_BUF_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_buf_clear(&buf); |
| git_buf_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_buf_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_buf_dispose(&buf); |
| git_config_iterator_free(iter); |
| return error; |
| } |
| |
| 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 */ |
| { |
| int error; |
| unsigned int location; |
| git_submodule *sm; |
| |
| assert(repo && name); |
| |
| if (repo->is_bare) { |
| git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); |
| return -1; |
| } |
| |
| if (repo->submodule_cache != NULL) { |
| if ((sm = git_strmap_get(repo->submodule_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_buf path = GIT_BUF_INIT; |
| fbp_data data = { NULL, NULL }; |
| |
| git_buf_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_buf_dispose(&path); |
| return error; |
| } |
| |
| if (data.name) { |
| git__free(sm->name); |
| sm->name = data.name; |
| sm->path = git_buf_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_buf_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_buf path = GIT_BUF_INIT; |
| if (git_buf_join3(&path, |
| '/', git_repository_workdir(repo), name, DOT_GIT) < 0) |
| return -1; |
| |
| if (git_path_exists(path.ptr)) |
| error = GIT_EEXISTS; |
| |
| git_buf_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_buf buf = GIT_BUF_INIT; |
| int error, isvalid; |
| |
| if (flags == 0) |
| flags = GIT_PATH_REJECT_FILESYSTEM_DEFAULTS; |
| |
| /* Avoid allocating a new string if we can avoid it */ |
| if (strchr(name, '\\') != NULL) { |
| if ((error = git_path_normalize_slashes(&buf, name)) < 0) |
| return error; |
| } else { |
| git_buf_attach_notowned(&buf, name, strlen(name)); |
| } |
| |
| isvalid = git_path_isvalid(repo, buf.ptr, 0, flags); |
| git_buf_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; |
| const char *wd = NULL; |
| git_buf path = GIT_BUF_INIT; |
| git_submodule *sm; |
| git_config *mods = NULL; |
| |
| assert(repo && 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(); |
| |
| wd = git_repository_workdir(repo); |
| if (wd && (error = git_buf_joinpath(&path, wd, GIT_MODULES_FILE)) < 0) |
| goto cleanup; |
| |
| /* add submodule information from .gitmodules */ |
| if (wd) { |
| 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 (wd) { |
| 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_buf_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_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT; |
| git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; |
| git_repository *subrepo = NULL; |
| |
| error = git_buf_joinpath(&workdir, git_repository_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_buf_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_buf_dispose(&workdir); |
| git_buf_dispose(&repodir); |
| |
| *out = subrepo; |
| |
| return error; |
| } |
| |
| 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_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT; |
| git_repository *subrepo = NULL; |
| bool path_occupied; |
| |
| assert(repo && url && 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_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_buf_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_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; |
| |
| /* if the repo does not already exist, then init a new repo and add it. |
| * Otherwise, just add the existing repo. |
| */ |
| if (!(git_path_exists(name.ptr) && |
| git_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_buf_dispose(&real_url); |
| git_buf_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_buf buf = GIT_BUF_INIT; |
| |
| assert(out && sm); |
| |
| /* get the configured remote url of the submodule */ |
| if ((error = git_buf_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_buf_dispose(&buf); |
| 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->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_buf path = GIT_BUF_INIT; |
| git_commit *head; |
| git_index_entry entry; |
| struct stat st; |
| |
| assert(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_buf_joinpath( |
| &path, git_repository_workdir(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_buf_dispose(&path); |
| return error; |
| } |
| |
| const char *git_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) |
| { |
| assert(submodule); |
| return submodule->repo; |
| } |
| |
| 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_resolve_url(git_buf *out, git_repository *repo, const char *url) |
| { |
| int error = 0; |
| git_buf normalized = GIT_BUF_INIT; |
| |
| assert(out && repo && url); |
| |
| git_buf_sanitize(out); |
| |
| /* We do this in all platforms in case someone on Windows created the .gitmodules */ |
| if (strchr(url, '\\')) { |
| if ((error = git_path_normalize_slashes(&normalized, url)) < 0) |
| return error; |
| |
| url = normalized.ptr; |
| } |
| |
| |
| if (git_path_is_relative(url)) { |
| if (!(error = get_url_base(out, repo))) |
| error = git_path_apply_relative(out, url); |
| } else if (strchr(url, ':') != NULL || url[0] == '/') { |
| error = git_buf_sets(out, url); |
| } else { |
| git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL"); |
| error = -1; |
| } |
| |
| git_buf_dispose(&normalized); |
| return error; |
| } |
| |
| static int write_var(git_repository *repo, const char *name, const char *var, const char *val) |
| { |
| git_buf key = GIT_BUF_INIT; |
| git_config_backend *mods; |
| int error; |
| |
| mods = open_gitmodules(repo, GITMODULES_CREATE); |
| if (!mods) |
| return -1; |
| |
| if ((error = git_buf_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_buf_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) |
| { |
| assert(submodule); |
| return submodule->branch; |
| } |
| |
| int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch) |
| { |
| |
| assert(repo && name); |
| |
| return write_var(repo, name, "branch", branch); |
| } |
| |
| int git_submodule_set_url(git_repository *repo, const char *name, const char *url) |
| { |
| assert(repo && name && url); |
| |
| return write_var(repo, name, "url", url); |
| } |
| |
| const git_oid *git_submodule_index_id(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_id(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_id(git_submodule *submodule) |
| { |
| assert(submodule); |
| |
| /* 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) |
| { |
| assert(submodule); |
| 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) |
| { |
| assert(repo && 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) |
| { |
| assert(submodule); |
| 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) |
| { |
| assert(repo && 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) |
| { |
| assert(submodule); |
| return submodule->fetch_recurse; |
| } |
| |
| int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse) |
| { |
| assert(repo && 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_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_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_buf_joinpath(&workdir, git_repository_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_buf_joinpath(&repodir, repodir.ptr, path); |
| if (error < 0) |
| goto cleanup; |
| |
| error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); |
| |
| cleanup: |
| git_buf_dispose(&workdir); |
| git_buf_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; |
| } |
| |
| int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) |
| { |
| return git_submodule_update_options_init(opts, version); |
| } |
| |
| 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_buf buf = GIT_BUF_INIT; |
| git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; |
| git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT; |
| |
| assert(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_buf_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0) |
| goto done; |
| |
| if ((error = git_config_get_string(&submodule_url, config, git_buf_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_buf_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_buf_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_buf key = GIT_BUF_INIT, effective_submodule_url = GIT_BUF_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_buf_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 : git_submodule_update_to_str(sm->update); |
| |
| if ((error = git_buf_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_buf_dispose(&key); |
| git_buf_dispose(&effective_submodule_url); |
| |
| return error; |
| } |
| |
| int git_submodule_sync(git_submodule *sm) |
| { |
| int error = 0; |
| git_config *cfg = NULL; |
| git_buf key = GIT_BUF_INIT; |
| git_repository *smrepo = NULL; |
| |
| 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)) && |
| !(error = git_buf_printf(&key, "submodule.%s.url", sm->name))) |
| error = git_config__update_entry(cfg, key.ptr, sm->url, true, true); |
| |
| /* if submodule exists in the working directory, update remote url */ |
| |
| if (!error && |
| (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0 && |
| !(error = git_submodule_open(&smrepo, sm))) |
| { |
| git_buf remote_name = GIT_BUF_INIT; |
| |
| if ((error = git_repository_config__weakptr(&cfg, smrepo)) < 0) |
| /* return error from reading submodule config */; |
| else if ((error = lookup_head_remote_key(&remote_name, smrepo)) < 0) { |
| git_error_clear(); |
| error = git_buf_sets(&key, "remote.origin.url"); |
| } else { |
| error = git_buf_join3( |
| &key, '.', "remote", remote_name.ptr, "url"); |
| git_buf_dispose(&remote_name); |
| } |
| |
| if (!error) |
| error = git_config__update_entry(cfg, key.ptr, sm->url, true, false); |
| |
| git_repository_free(smrepo); |
| } |
| |
| git_buf_dispose(&key); |
| |
| return error; |
| } |
| |
| static int git_submodule__open( |
| git_repository **subrepo, git_submodule *sm, bool bare) |
| { |
| int error; |
| git_buf path = GIT_BUF_INIT; |
| unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; |
| const char *wd; |
| |
| assert(sm && subrepo); |
| |
| if (git_repository__ensure_not_bare( |
| sm->repo, "open submodule repository") < 0) |
| return GIT_EBAREREPO; |
| |
| wd = git_repository_workdir(sm->repo); |
| |
| if (git_buf_joinpath(&path, wd, sm->path) < 0 || |
| git_buf_joinpath(&path, path.ptr, 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_path_exists(path.ptr)) { |
| sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | |
| GIT_SUBMODULE_STATUS_IN_WD; |
| } else { |
| git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */ |
| |
| if (git_path_isdir(path.ptr)) |
| sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; |
| } |
| |
| git_buf_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) |
| { |
| int error = 0, isvalid; |
| git_config *mods; |
| |
| GIT_UNUSED(force); |
| |
| assert(sm); |
| |
| isvalid = git_submodule_name_is_valid(sm->repo, sm->name, 0); |
| if (isvalid <= 0) { |
| /* This should come with a warning, but we've no API for that */ |
| return isvalid; |
| } |
| |
| if (!git_repository_is_bare(sm->repo)) { |
| /* refresh config data */ |
| if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) |
| return error; |
| if (mods != NULL) { |
| error = submodule_read_config(sm, mods); |
| git_config_free(mods); |
| |
| if (error < 0) |
| return error; |
| } |
| |
| /* refresh wd data */ |
| sm->flags &= |
| ~(GIT_SUBMODULE_STATUS_IN_WD | |
| GIT_SUBMODULE_STATUS__WD_OID_VALID | |
| GIT_SUBMODULE_STATUS__WD_FLAGS); |
| |
| error = submodule_load_from_wd_lite(sm); |
| } |
| |
| if (error == 0 && (error = submodule_update_index(sm)) == 0) |
| error = submodule_update_head(sm); |
| |
| 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; |
| |
| assert(status && repo && 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) |
| { |
| assert(location && 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); |
| } |
| |
| 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; |
| } |
| |
| int git_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_buf *buf, const char *name, const char *field) |
| { |
| int error; |
| |
| git_buf_clear(buf); |
| |
| if ((error = git_buf_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_buf key = GIT_BUF_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 = git_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_buf_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_buf name = GIT_BUF_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_buf_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_buf_dispose(&name); |
| return error; |
| } |
| |
| static int submodule_load_from_wd_lite(git_submodule *sm) |
| { |
| git_buf path = GIT_BUF_INIT; |
| |
| if (git_buf_joinpath(&path, git_repository_workdir(sm->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_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) |
| { |
| const char *workdir = git_repository_workdir(repo); |
| git_config *mods = NULL; |
| git_buf path = GIT_BUF_INIT; |
| int error; |
| |
| if (!workdir) |
| return GIT_ENOTFOUND; |
| |
| if ((error = git_buf_joinpath(&path, workdir, GIT_MODULES_FILE)) < 0) |
| return error; |
| |
| if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0) |
| goto cleanup; |
| git_buf_dispose(&path); |
| |
| if ((error = git_config_snapshot(snap, mods)) < 0) |
| goto cleanup; |
| |
| error = 0; |
| |
| cleanup: |
| if (mods) |
| git_config_free(mods); |
| git_buf_dispose(&path); |
| |
| return error; |
| } |
| |
| static git_config_backend *open_gitmodules( |
| git_repository *repo, |
| int okay_to_create) |
| { |
| const char *workdir = git_repository_workdir(repo); |
| git_buf path = GIT_BUF_INIT; |
| git_config_backend *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_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_buf_dispose(&path); |
| |
| return mods; |
| } |
| |
| /* Lookup name of remote of the local tracking branch HEAD points to */ |
| static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo) |
| { |
| int error; |
| git_reference *head = NULL; |
| git_buf upstream_name = GIT_BUF_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_buf_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_buf remote_name = GIT_BUF_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_buf_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_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_buf *url, git_repository *repo) |
| { |
| int error; |
| git_worktree *wt = NULL; |
| git_remote *remote = NULL; |
| |
| if ((error = lookup_default_remote(&remote, repo)) == 0) { |
| error = git_buf_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_buf_sets(url, wt->parent_path); |
| } else |
| error = git_buf_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; |
| } |
| } |