summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Martí <vicent@github.com>2012-08-24 15:45:13 -0700
committerVicent Martí <vicent@github.com>2012-08-24 15:45:13 -0700
commit09fad5069636fb2e8cacf15817834e3d32ff6b8e (patch)
tree8bef464c7723bde80f850f90784d3c38e78596ce /src
parent091c742af985cc78711727ca06a24ae42b376fae (diff)
parent97a17e4e9fa5cafa531ff79cb88a9ee5c224a613 (diff)
downloadlibgit2-09fad5069636fb2e8cacf15817834e3d32ff6b8e.tar.gz
Merge pull request #852 from arrbee/submodule-extensions
Submodule extensions
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c37
-rw-r--r--src/checkout.c15
-rw-r--r--src/config_file.c12
-rw-r--r--src/config_file.h12
-rw-r--r--src/diff.c2
-rw-r--r--src/diff_output.c19
-rw-r--r--src/errors.c5
-rw-r--r--src/filebuf.c1
-rw-r--r--src/iterator.c21
-rw-r--r--src/repository.c9
-rw-r--r--src/submodule.c1491
-rw-r--r--src/submodule.h102
12 files changed, 1485 insertions, 241 deletions
diff --git a/src/buffer.c b/src/buffer.c
index b57998e1..61cfaf9e 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -144,31 +144,40 @@ int git_buf_puts(git_buf *buf, const char *string)
int git_buf_puts_escaped(
git_buf *buf, const char *string, const char *esc_chars, const char *esc_with)
{
- const char *scan = string;
- size_t total = 0, esc_with_len = strlen(esc_with);
+ const char *scan;
+ size_t total = 0, esc_len = strlen(esc_with), count;
- while (*scan) {
- size_t count = strcspn(scan, esc_chars);
- total += count + 1 + esc_with_len;
- scan += count + 1;
+ if (!string)
+ return 0;
+
+ for (scan = string; *scan; ) {
+ /* count run of non-escaped characters */
+ count = strcspn(scan, esc_chars);
+ total += count;
+ scan += count;
+ /* count run of escaped characters */
+ count = strspn(scan, esc_chars);
+ total += count * (esc_len + 1);
+ scan += count;
}
ENSURE_SIZE(buf, buf->size + total + 1);
for (scan = string; *scan; ) {
- size_t count = strcspn(scan, esc_chars);
+ count = strcspn(scan, esc_chars);
memmove(buf->ptr + buf->size, scan, count);
scan += count;
buf->size += count;
- if (*scan) {
- memmove(buf->ptr + buf->size, esc_with, esc_with_len);
- buf->size += esc_with_len;
-
- memmove(buf->ptr + buf->size, scan, 1);
- scan += 1;
- buf->size += 1;
+ for (count = strspn(scan, esc_chars); count > 0; --count) {
+ /* copy escape sequence */
+ memmove(buf->ptr + buf->size, esc_with, esc_len);
+ buf->size += esc_len;
+ /* copy character to be escaped */
+ buf->ptr[buf->size] = *scan;
+ buf->size++;
+ scan++;
}
}
diff --git a/src/checkout.c b/src/checkout.c
index ac540391..88df2128 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -31,7 +31,7 @@ typedef struct tree_walk_data
git_checkout_opts *opts;
git_repository *repo;
git_odb *odb;
- bool do_symlinks;
+ bool no_symlinks;
} tree_walk_data;
@@ -48,9 +48,9 @@ static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf,
/* Create the link */
const char *new = git_buf_cstr(&linktarget),
*old = git_buf_cstr(fnbuf);
- retcode = data->do_symlinks
- ? p_symlink(new, old)
- : git_futils_fake_symlink(new, old);
+ retcode = data->no_symlinks
+ ? git_futils_fake_symlink(new, old)
+ : p_symlink(new, old);
}
git_buf_free(&linktarget);
git_blob_free(blob);
@@ -176,13 +176,14 @@ int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer
return GIT_ERROR;
}
+ memset(&payload, 0, sizeof(payload));
+
/* Determine if symlinks should be handled */
- if (!git_repository_config(&cfg, repo)) {
+ if (!git_repository_config__weakptr(&cfg, repo)) {
int temp = true;
if (!git_config_get_bool(&temp, cfg, "core.symlinks")) {
- payload.do_symlinks = !!temp;
+ payload.no_symlinks = !temp;
}
- git_config_free(cfg);
}
stats->total = stats->processed = 0;
diff --git a/src/config_file.c b/src/config_file.c
index 547509b9..d3fb56aa 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -195,7 +195,7 @@ static int file_foreach(
void *data)
{
diskfile_backend *b = (diskfile_backend *)backend;
- cvar_t *var;
+ cvar_t *var, *next_var;
const char *key;
regex_t regex;
int result = 0;
@@ -212,7 +212,9 @@ static int file_foreach(
}
git_strmap_foreach(b->values, key, var,
- for (; var != NULL; var = CVAR_LIST_NEXT(var)) {
+ for (; var != NULL; var = next_var) {
+ next_var = CVAR_LIST_NEXT(var);
+
/* skip non-matching keys if regexp was provided */
if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
continue;
@@ -253,11 +255,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 c3129288..bf687b51 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 9abf8b9f..430f52e0 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/diff_output.c b/src/diff_output.c
index d269a4ce..2bf939f3 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -718,6 +718,25 @@ int git_diff_print_patch(
return error;
}
+int git_diff_entrycount(git_diff_list *diff, int delta_t)
+{
+ int count = 0;
+ unsigned int i;
+ git_diff_delta *delta;
+
+ assert(diff);
+
+ if (delta_t < 0)
+ return diff->deltas.length;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (delta->status == (git_delta_t)delta_t)
+ count++;
+ }
+
+ return count;
+}
+
int git_diff_blobs(
git_blob *old_blob,
git_blob *new_blob,
diff --git a/src/errors.c b/src/errors.c
index d43d7d9b..802ad364 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -110,6 +110,11 @@ void giterr_set_regex(const regex_t *regex, int error_code)
void giterr_clear(void)
{
GIT_GLOBAL->last_error = NULL;
+
+ errno = 0;
+#ifdef GIT_WIN32
+ SetLastError(0);
+#endif
}
const git_error *giterr_last(void)
diff --git a/src/filebuf.c b/src/filebuf.c
index 8b3ebb3e..cfc8528e 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -50,6 +50,7 @@ static int lock_file(git_filebuf *file, int flags)
if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock);
else {
+ giterr_clear(); /* actual OS error code just confuses */
giterr_set(GITERR_OS,
"Failed to lock file '%s' for writing", file->path_lock);
return -1;
diff --git a/src/iterator.c b/src/iterator.c
index 819b0e22..92fe6713 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -525,7 +525,9 @@ static int workdir_iterator__advance(
while ((wf = wi->stack) != NULL) {
next = git_vector_get(&wf->entries, ++wf->index);
if (next != NULL) {
- if (strcmp(next->path, DOT_GIT "/") == 0)
+ /* match git's behavior of ignoring anything named ".git" */
+ if (strcmp(next->path, DOT_GIT "/") == 0 ||
+ strcmp(next->path, DOT_GIT) == 0)
continue;
/* else found a good entry */
break;
@@ -607,8 +609,8 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
wi->entry.path = ps->path;
- /* skip over .git directory */
- if (strcmp(ps->path, DOT_GIT "/") == 0)
+ /* skip over .git entry */
+ if (strcmp(ps->path, DOT_GIT "/") == 0 || strcmp(ps->path, DOT_GIT) == 0)
return workdir_iterator__advance((git_iterator *)wi, NULL);
/* if there is an error processing the entry, treat as ignored */
@@ -629,15 +631,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
/* detect submodules */
if (S_ISDIR(wi->entry.mode)) {
- bool is_submodule = git_path_contains(&wi->path, DOT_GIT);
-
- /* if there is no .git, still check submodules data */
- if (!is_submodule) {
- int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
- is_submodule = (res == 0);
- if (res == GIT_ENOTFOUND)
- giterr_clear();
- }
+ int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
+ bool is_submodule = (res == 0);
+ if (res == GIT_ENOTFOUND)
+ giterr_clear();
/* if submodule, mark as GITLINK and remove trailing slash */
if (is_submodule) {
diff --git a/src/repository.c b/src/repository.c
index 18788d18..c12df25c 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -146,8 +146,13 @@ static int load_workdir(git_repository *repo, git_buf *parent_path)
return -1;
error = git_config_get_string(&worktree, config, "core.worktree");
- if (!error && worktree != NULL)
- repo->workdir = git__strdup(worktree);
+ if (!error && worktree != NULL) {
+ error = git_path_prettify_dir(
+ &worktree_buf, worktree, repo->path_repository);
+ if (error < 0)
+ return error;
+ repo->workdir = git_buf_detach(&worktree_buf);
+ }
else if (error != GIT_ENOTFOUND)
return error;
else {
diff --git a/src/submodule.c b/src/submodule.c
index b8537cb8..a9de9ee6 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)
@@ -36,7 +42,7 @@ static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
khint_t h;
for (h = 0; *s; ++s)
- if (s[1] || *s != '/')
+ if (s[1] != '\0' || *s != '/')
h = (h << 5) - h + *s;
return h;
@@ -47,29 +53,790 @@ static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
size_t alen = a ? strlen(a) : 0;
size_t blen = b ? strlen(b) : 0;
- if (alen && a[alen] == '/')
+ if (alen > 0 && a[alen - 1] == '/')
alen--;
- if (blen && b[blen] == '/')
+ if (blen > 0 && b[blen - 1] == '/')
blen--;
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 int submodule_get(git_submodule **, git_repository *, const char *, const char *);
+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_load_from_wd_lite(git_submodule *, const char *, void *);
+static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
+static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);
+static int submodule_index_status(unsigned int *status, git_submodule *sm);
+static int submodule_wd_status(unsigned int *status, git_submodule *sm);
-static git_submodule *submodule_alloc(const char *name)
+static int submodule_cmp(const void *a, const void *b)
{
- git_submodule *sm = git__calloc(1, sizeof(git_submodule));
- if (sm == NULL)
- return sm;
+ return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+}
- sm->path = sm->name = git__strdup(name);
- if (!sm->name) {
- git__free(sm);
+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 (callback(sm, sm->name, payload)) {
+ error = GIT_EUSER;
+ 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 (!(error = submodule_get(&sm, repo, path, NULL)) &&
+ !(error = git_submodule_reload(sm)))
+ 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, true);
+}
+
+int git_submodule_add_to_index(git_submodule *sm, int write_index)
+{
+ 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;
+
+ /* 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, 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;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = sm->path;
+ 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;
+
+ git_commit_free(head);
+
+ /* add it */
+ error = git_index_add2(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_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);
+ else
+ giterr_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_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;
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
+
+ pos = git_index_find(index, submodule->path);
+ if (pos >= 0) {
+ git_index_entry *entry = git_index_get(index, pos);
+
+ if (S_ISGITLINK(entry->mode)) {
+ if ((error = submodule_load_from_index(repo, entry)) < 0)
+ return error;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
+ }
+ }
+
+ /* 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))) {
+
+ if (S_ISGITLINK(te->attr)) {
+ error = submodule_load_from_head(repo, submodule->path, &te->oid);
+ } else {
+ submodule_mode_mismatch(
+ repo, submodule->path,
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
+ }
+
+ 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);
+ git_config_file_free(mods);
+ }
+
+ if (error < 0)
+ return error;
+
+ /* refresh wd data */
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID);
+
+ error = submodule_load_from_wd_lite(submodule, submodule->path, NULL);
+
+ return error;
+}
+
+int git_submodule_status(
+ unsigned int *status,
+ git_submodule *submodule)
+{
+ int error = 0;
+ unsigned int status_val;
+
+ assert(status && submodule);
+
+ status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);
+
+ if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {
+ if (!(error = submodule_index_status(&status_val, submodule)))
+ error = submodule_wd_status(&status_val, submodule);
+ }
+
+ *status = status_val;
+
+ return error;
+}
+
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+static git_submodule *submodule_alloc(git_repository *repo, const char *name)
+{
+ git_submodule *sm;
+
+ if (!name || !strlen(name)) {
+ giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
+ return NULL;
+ }
+
+ sm = git__calloc(1, sizeof(git_submodule));
+ if (sm == NULL)
+ goto fail;
+
+ sm->path = sm->name = git__strdup(name);
+ if (!sm->name)
+ goto fail;
+
+ sm->owner = repo;
+ sm->refcount = 1;
+
return sm;
+
+fail:
+ submodule_release(sm, 0);
+ return NULL;
}
static void submodule_release(git_submodule *sm, int decr)
@@ -80,70 +847,121 @@ 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 int submodule_get(
+ git_submodule **sm_ptr,
+ git_repository *repo,
+ const char *name,
+ const char *alternate)
{
- git_submodule *sm;
- void *old_sm;
+ git_strmap *smcfg = repo->submodules;
khiter_t pos;
+ git_submodule *sm;
int error;
- pos = git_strmap_lookup_index(smcfg, entry->path);
+ assert(repo && name);
- if (git_strmap_valid_index(smcfg, pos))
- sm = git_strmap_value_at(smcfg, pos);
- else
- sm = submodule_alloc(entry->path);
+ pos = git_strmap_lookup_index(smcfg, name);
- git_oid_cpy(&sm->oid, &entry->oid);
+ if (!git_strmap_valid_index(smcfg, pos) && alternate)
+ pos = git_strmap_lookup_index(smcfg, alternate);
- if (strcmp(sm->path, entry->path) != 0) {
- if (sm->path != sm->name) {
- git__free(sm->path);
- sm->path = sm->name;
+ if (!git_strmap_valid_index(smcfg, pos)) {
+ sm = submodule_alloc(repo, name);
+
+ /* insert value at name - if another thread beats us to it, then use
+ * their record and release our own.
+ */
+ pos = kh_put(str, smcfg, sm->name, &error);
+
+ if (error < 0) {
+ submodule_release(sm, 1);
+ sm = NULL;
+ } else if (error == 0) {
+ submodule_release(sm, 1);
+ sm = git_strmap_value_at(smcfg, pos);
+ } else {
+ git_strmap_set_value_at(smcfg, pos, sm);
}
- sm->path = git__strdup(entry->path);
- if (!sm->path)
- goto fail;
+ } else {
+ sm = git_strmap_value_at(smcfg, pos);
}
- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ *sm_ptr = sm;
- if (old_sm && ((git_submodule *)old_sm) != sm) {
- /* TODO: log warning about multiple entrys for same submodule path */
- submodule_release(old_sm, 1);
+ return (sm != NULL) ? 0 : -1;
+}
+
+static int submodule_load_from_index(
+ git_repository *repo, const git_index_entry *entry)
+{
+ git_submodule *sm;
+
+ if (submodule_get(&sm, repo, entry->path, NULL) < 0)
+ 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 0;
+}
-fail:
- submodule_release(sm, 0);
+static int submodule_load_from_head(
+ git_repository *repo, const char *path, const git_oid *oid)
+{
+ git_submodule *sm;
+
+ if (submodule_get(&sm, repo, path, NULL) < 0)
+ 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 0;
+}
+
+static int submodule_config_error(const char *property, const char *value)
+{
+ giterr_set(GITERR_INVALID,
+ "Invalid value for submodule '%s' property: '%s'", property, value);
return -1;
}
-static int submodule_from_config(
+static int submodule_load_from_config(
const char *key, const char *value, void *data)
{
- git_strmap *smcfg = data;
- const char *namestart;
- const char *property;
+ git_repository *repo = data;
+ git_strmap *smcfg = repo->submodules;
+ const char *namestart, *property, *alternate = NULL;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
- void *old_sm = NULL;
bool is_path;
- khiter_t pos;
- int error;
+ int error = 0;
if (git__prefixcmp(key, "submodule.") != 0)
return 0;
@@ -153,235 +971,502 @@ 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);
- if (!sm)
- goto fail;
+ if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) {
+ git_buf_free(&name);
+ return -1;
+ }
- if (strcmp(sm->name, name.ptr) != 0) {
- assert(sm->path == sm->name);
- sm->name = git_buf_detach(&name);
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
- git_strmap_insert2(smcfg, sm->name, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ /* Only from config might we get differing names & paths. If so, then
+ * update the submodule and insert under the alternative key.
+ */
+
+ /* TODO: if case insensitive filesystem, then the following strcmps
+ * should be strcasecmp
+ */
+
+ if (strcmp(sm->name, name.ptr) != 0) {
+ alternate = sm->name = git_buf_detach(&name);
+ } else if (is_path && value && strcmp(sm->path, value) != 0) {
+ alternate = sm->path = git__strdup(value);
+ if (!sm->path)
+ error = -1;
}
- else if (is_path && strcmp(sm->path, value) != 0) {
- assert(sm->path == sm->name);
- sm->path = git__strdup(value);
- if (sm->path == NULL)
- goto fail;
+ if (alternate) {
+ void *old_sm = NULL;
+ git_strmap_insert2(smcfg, alternate, sm, old_sm, error);
- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
- if (error < 0)
- goto fail;
- sm->refcount++;
+ if (error >= 0)
+ sm->refcount++; /* inserted under a new key */
+
+ /* if we replaced an old module under this key, release the old one */
+ if (old_sm && ((git_submodule *)old_sm) != sm) {
+ submodule_release(old_sm, 1);
+ /* TODO: log warning about multiple submodules with same path */
+ }
}
+
git_buf_free(&name);
+ if (error < 0)
+ return error;
- if (old_sm && ((git_submodule *)old_sm) != sm) {
- /* TODO: log warning about multiple submodules with same path */
- 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 (sm->url) {
- git__free(sm->url);
- sm->url = NULL;
- }
- if ((sm->url = git__strdup(value)) == NULL)
- goto fail;
+ if (strcasecmp(property, "url") == 0) {
+ git__free(sm->url);
+ sm->url = NULL;
+
+ if (value != NULL && (sm->url = git__strdup(value)) == NULL)
+ return -1;
}
- 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) {
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule update property: '%s'", value);
- goto fail;
- }
- sm->update = (git_submodule_update_t)val;
+ _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0)
+ return submodule_config_error("update", value);
+ sm->update_default = sm->update = (git_submodule_update_t)val;
}
- else if (strcmp(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 (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
+ if (git__parse_bool(&sm->fetch_recurse, value) < 0)
+ return submodule_config_error("fetchRecurseSubmodules", value);
}
- 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) {
- giterr_set(GITERR_INVALID,
- "Invalid value for submodule ignore property: '%s'", value);
- goto fail;
- }
- sm->ignore = (git_submodule_ignore_t)val;
+ _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0)
+ return submodule_config_error("ignore", value);
+ sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
return 0;
+}
-fail:
- submodule_release(sm, 0);
- git_buf_free(&name);
- return -1;
+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 void submodule_mode_mismatch(
+ git_repository *repo, const char *path, unsigned int flag)
+{
+ khiter_t pos = git_strmap_lookup_index(repo->submodules, path);
+
+ if (git_strmap_valid_index(repo->submodules, pos)) {
+ git_submodule *sm = git_strmap_value_at(repo->submodules, pos);
+
+ sm->flags |= flag;
+ }
}
-static int load_submodule_config(git_repository *repo)
+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) {
+
+ if (S_ISGITLINK(entry->mode)) {
+ error = submodule_load_from_index(repo, entry);
+ if (error < 0)
+ break;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
+
+ if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
+ git_oid_cpy(gitmodules_oid, &entry->oid);
+ }
+
+ error = git_iterator_advance(i, &entry);
+ }
+
+ 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) {
- 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;
+ error = submodule_load_from_head(repo, entry->path, &entry->oid);
+ if (error < 0)
+ break;
+ } else {
+ submodule_mode_mismatch(
+ repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
+
+ if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
+ git_oid_iszero(gitmodules_oid))
+ git_oid_cpy(gitmodules_oid, &entry->oid);
}
- else if (strcmp(entry->path, ".gitmodules") == 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);
+ 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)
+ mods = NULL;
+ /* open should only fail here if the file is malformed */
+ else 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.
+ */
+ }
+
+ git_buf_free(&path);
+
+ 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;
+
+ /* add submodule information from HEAD */
+
+ if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)
+ goto cleanup;
+
+ /* add submodule information from .gitmodules */
- /* store submodule config in repo */
- if (!error)
- repo->submodules = smcfg;
+ 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;
+ }
-static int submodule_cmp(const void *a, const void *b)
-{
- return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+ 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;
}
-int git_submodule_foreach(
- git_repository *repo,
- int (*callback)(const char *name, void *payload),
- void *payload)
+static int submodule_update_config(
+ git_submodule *submodule,
+ const char *attr,
+ const char *value,
+ bool overwrite,
+ bool only_existing)
{
int error;
- git_submodule *sm;
- git_vector seen = GIT_VECTOR_INIT;
- seen._cmp = submodule_cmp;
+ git_config *config;
+ git_buf key = GIT_BUF_INIT;
+ const char *old = NULL;
+
+ assert(submodule);
- if ((error = load_submodule_config(repo)) < 0)
+ error = git_repository_config__weakptr(&config, submodule->owner);
+ if (error < 0)
return error;
- 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;
- }
+ error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);
+ if (error < 0)
+ goto cleanup;
- if ((error = callback(sm->name, payload)) < 0)
- break;
- });
+ if (git_config_get_string(&old, config, key.ptr) < 0)
+ giterr_clear();
- git_vector_free(&seen);
+ 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_lookup(
- git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
- git_repository *repo,
- const char *name) /* trailing slash is allowed */
+static int submodule_index_status(unsigned int *status, git_submodule *sm)
{
- khiter_t pos;
+ const git_oid *head_oid = git_submodule_head_oid(sm);
+ const git_oid *index_oid = git_submodule_index_oid(sm);
- if (load_submodule_config(repo) < 0)
- return -1;
+ 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;
- pos = git_strmap_lookup_index(repo->submodules, name);
- if (!git_strmap_valid_index(repo->submodules, pos))
- return GIT_ENOTFOUND;
+ return 0;
+}
- if (sm_ptr)
- *sm_ptr = git_strmap_value_at(repo->submodules, pos);
+static int submodule_wd_status(unsigned int *status, git_submodule *sm)
+{
+ int error = 0;
+ const git_oid *wd_oid, *index_oid;
+ git_repository *sm_repo = NULL;
+
+ /* open repo now if we need it (so wd_oid() call won't reopen) */
+ if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||
+ sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)
+ {
+ if ((error = git_submodule_open(&sm_repo, sm)) < 0)
+ return error;
+ }
- return 0;
+ index_oid = git_submodule_index_oid(sm);
+ wd_oid = git_submodule_wd_oid(sm);
+
+ 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 (sm_repo != NULL) {
+ git_tree *sm_head;
+ git_diff_options opt;
+ git_diff_list *diff;
+
+ /* 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).
+ */
+
+ /* perform head-to-index diff on submodule */
+
+ if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)
+ return error;
+
+ memset(&opt, 0, sizeof(opt));
+ if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
+
+ if (!error) {
+ if (git_diff_entrycount(diff, -1) > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
+
+ git_diff_list_free(diff);
+ diff = NULL;
+ }
+
+ git_tree_free(sm_head);
+
+ if (error < 0)
+ return error;
+
+ /* perform index-to-workdir diff on submodule */
+
+ error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
+
+ if (!error) {
+ int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED);
+
+ if (untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+
+ if (git_diff_entrycount(diff, -1) - untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+
+ git_diff_list_free(diff);
+ diff = NULL;
+ }
+
+ git_repository_free(sm_repo);
+ }
+
+ return error;
}
diff --git a/src/submodule.h b/src/submodule.h
new file mode 100644
index 00000000..c7a6aaf7
--- /dev/null
+++ b/src/submodule.h
@@ -0,0 +1,102 @@
+/*
+ * 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 */
+enum {
+ GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),
+ GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),
+ GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),
+ GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
+};
+
+#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
+ ((S) & ~(0xFFFFFFFFu << 20))
+
+#endif