diff options
| author | Russell Belfer <rb@github.com> | 2012-08-09 19:43:25 -0700 |
|---|---|---|
| committer | Russell Belfer <rb@github.com> | 2012-08-24 11:00:27 -0700 |
| commit | 5f4a61aea834fe25ce1596bc9c0e0b5e563aa98b (patch) | |
| tree | da0237ee649e009b5f914dfdace54d26e819aaaf | |
| parent | 0c8858de8c82bae3fd88513724689a07d231da7e (diff) | |
| download | libgit2-5f4a61aea834fe25ce1596bc9c0e0b5e563aa98b.tar.gz | |
Working implementation of git_submodule_status
This is a big redesign of the git_submodule_status API and the
implementation of the redesigned API. It also fixes a number of
bugs that I found in other parts of the submodule API while
writing the tests for the status part.
This also fixes a couple of bugs in the iterators that had not
been noticed before - one with iterating when there is a gitlink
(i.e. separate-work-dir) and one where I was treating anything
even vaguely submodule-like as a submodule, more aggressively
than core git does.
| -rw-r--r-- | include/git2/diff.h | 15 | ||||
| -rw-r--r-- | include/git2/oid.h | 2 | ||||
| -rw-r--r-- | include/git2/submodule.h | 177 | ||||
| -rw-r--r-- | src/diff_output.c | 19 | ||||
| -rw-r--r-- | src/iterator.c | 21 | ||||
| -rw-r--r-- | src/repository.c | 9 | ||||
| -rw-r--r-- | src/submodule.c | 367 | ||||
| -rw-r--r-- | src/submodule.h | 18 | ||||
| -rw-r--r-- | tests-clar/status/submodules.c | 8 | ||||
| -rw-r--r-- | tests-clar/submodule/status.c | 286 |
10 files changed, 606 insertions, 316 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h index 79ef7a49b..088e1ecfa 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -391,6 +391,21 @@ GIT_EXTERN(int) git_diff_print_patch( void *cb_data, git_diff_data_fn print_cb); +/** + * Query how many diff records are there in a diff list. + * + * You can optionally pass in a `git_delta_t` value if you want a count + * of just entries that match that delta type, or pass -1 for all delta + * records. + * + * @param diff A git_diff_list generated by one of the above functions + * @param delta_t A git_delta_t value to filter the count, or -1 for all records + * @return Count of number of deltas matching delta_t type + */ +GIT_EXTERN(int) git_diff_entrycount( + git_diff_list *diff, + int delta_t); + /**@}*/ diff --git a/include/git2/oid.h b/include/git2/oid.h index 887b33e50..9e54a9f96 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -185,6 +185,8 @@ GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str); /** * Check is an oid is all zeros. + * + * @return 1 if all zeros, 0 otherwise. */ GIT_EXTERN(int) git_oid_iszero(const git_oid *a); diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 6cd66465e..fe7f26cfe 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -61,83 +61,67 @@ typedef enum { } git_submodule_ignore_t; /** - * Status values for submodules. - * - * One of these values will be returned for the submodule in the index - * relative to the HEAD tree, and one will be returned for the submodule in - * the working directory relative to the index. The value can be extracted - * from the actual submodule status return value using one of the macros - * below (see GIT_SUBMODULE_INDEX_STATUS and GIT_SUBMODULE_WD_STATUS). - */ -enum { - GIT_SUBMODULE_STATUS_CLEAN = 0, - GIT_SUBMODULE_STATUS_ADDED = 1, - GIT_SUBMODULE_STATUS_REMOVED = 2, - GIT_SUBMODULE_STATUS_REMOVED_TYPE_CHANGE = 3, - GIT_SUBMODULE_STATUS_MODIFIED = 4, - GIT_SUBMODULE_STATUS_MODIFIED_AHEAD = 5, - GIT_SUBMODULE_STATUS_MODIFIED_BEHIND = 6 -}; - -/** * Return codes for submodule status. * - * A combination of these flags (and shifted values of the - * GIT_SUBMODULE_STATUS codes above) will be returned to describe the status - * of a submodule. + * A combination of these flags will be returned to describe the status of a + * submodule. Depending on the "ignore" property of the submodule, some of + * the flags may never be returned because they indicate changes that are + * supposed to be ignored. * * Submodule info is contained in 4 places: the HEAD tree, the index, config * files (both .git/config and .gitmodules), and the working directory. Any * or all of those places might be missing information about the submodule - * depending on what state the repo is in. - * - * When you ask for submodule status, we consider all four places and return - * a combination of the flags below. Also, we also compare HEAD to index to - * workdir, and return a relative status code (see above) for the - * comparisons. Use the GIT_SUBMODULE_INDEX_STATUS() and - * GIT_SUBMODULE_WD_STATUS() macros to extract these status codes from the - * results. As an example, if the submodule exists in the HEAD and does not - * exist in the index, then using GIT_SUBMODULE_INDEX_STATUS(st) will return - * GIT_SUBMODULE_STATUS_REMOVED. - * - * The ignore settings for the submodule will control how much status info - * you get about the working directory. For example, with ignore ALL, the - * workdir will always show as clean. With any ignore level below NONE, - * you will never get the WD_HAS_UNTRACKED value back. - * - * The other SUBMODULE_STATUS values you might see are: - * - * - IN_HEAD means submodule exists in HEAD tree - * - IN_INDEX means submodule exists in index - * - IN_CONFIG means submodule exists in config - * - IN_WD means submodule exists in workdir and looks like a submodule - * - WD_CHECKED_OUT means submodule in workdir has .git content - * - WD_HAS_UNTRACKED means workdir contains untracked files. This would - * only ever be returned for ignore value GIT_SUBMODULE_IGNORE_NONE. - * - WD_MISSING_COMMITS means workdir repo is out of date and does not - * contain the SHAs from either the index or the HEAD tree - */ -#define GIT_SUBMODULE_STATUS_IN_HEAD (1u << 0) -#define GIT_SUBMODULE_STATUS_IN_INDEX (1u << 1) -#define GIT_SUBMODULE_STATUS_IN_CONFIG (1u << 2) -#define GIT_SUBMODULE_STATUS_IN_WD (1u << 3) -#define GIT_SUBMODULE_STATUS_INDEX_DATA_OFFSET (4) -#define GIT_SUBMODULE_STATUS_WD_DATA_OFFSET (7) -#define GIT_SUBMODULE_STATUS_WD_CHECKED_OUT (1u << 10) -#define GIT_SUBMODULE_STATUS_WD_HAS_UNTRACKED (1u << 11) -#define GIT_SUBMODULE_STATUS_WD_MISSING_COMMITS (1u << 12) - -/** - * Extract submodule status value for index from status mask. - */ -#define GIT_SUBMODULE_INDEX_STATUS(s) \ - (((s) >> GIT_SUBMODULE_STATUS_INDEX_DATA_OFFSET) & 0x07) - -/** - * Extract submodule status value for working directory from status mask. + * depending on what state the repo is in. We consider all four places to + * build the combination of status flags. + * + * There are four values that are not really status, but give basic info + * about what sources of submodule data are available. These will be + * returned even if ignore is set to "ALL". + * + * * IN_HEAD - superproject head contains submodule + * * IN_INDEX - superproject index contains submodule + * * IN_CONFIG - superproject gitmodules has submodule + * * IN_WD - superproject workdir has submodule + * + * The following values will be returned so long as ignore is not "ALL". + * + * * INDEX_ADDED - in index, not in head + * * INDEX_DELETED - in head, not in index + * * INDEX_MODIFIED - index and head don't match + * * WD_UNINITIALIZED - workdir contains empty directory + * * WD_ADDED - in workdir, not index + * * WD_DELETED - in index, not workdir + * * WD_MODIFIED - index and workdir head don't match + * + * The following can only be returned if ignore is "NONE" or "UNTRACKED". + * + * * WD_INDEX_MODIFIED - submodule workdir index is dirty + * * WD_WD_MODIFIED - submodule workdir has modified files + * + * Lastly, the following will only be returned for ignore "NONE". + * + * * WD_UNTRACKED - wd contains untracked files */ -#define GIT_SUBMODULE_WD_STATUS(s) \ - (((s) >> GIT_SUBMODULE_STATUS_WD_DATA_OFFSET) & 0x07) +typedef enum { + GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0), + GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1), + GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2), + GIT_SUBMODULE_STATUS_IN_WD = (1u << 3), + GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4), + GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5), + GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6), + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7), + GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8), + GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9), + GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10), + GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11), + GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12), + GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13), +} git_submodule_status_t; + +#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ + (((S) & ~(GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX | \ + GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD)) == 0) /** * Lookup submodule information by name or path. @@ -206,7 +190,7 @@ GIT_EXTERN(int) git_submodule_foreach( * * To fully emulate "git submodule add" call this function, then open the * submodule repo and perform the clone step as needed. Lastly, call - * `git_submodule_add_finalize` to wrap up adding the new submodule and + * `git_submodule_add_finalize()` to wrap up adding the new submodule and * .gitmodules to the index to be ready to commit. * * @param submodule The newly created submodule ready to open for clone @@ -232,22 +216,33 @@ GIT_EXTERN(int) git_submodule_add_setup( * and done the clone of the submodule. This adds the .gitmodules file * and the newly cloned submodule to the index to be ready to be committed * (but doesn't actually do the commit). + * + * @param submodule The submodule to finish adding. */ GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule); /** * Add current submodule HEAD commit to index of superproject. + * + * @param submodule The submodule to add to the index + * @param write_index Boolean if this should immediately write the index + * file. If you pass this as false, you will have to get the + * git_index and explicitly call `git_index_write()` on it to + * save the change. + * @return 0 on success, <0 on failure */ -GIT_EXTERN(int) git_submodule_add_to_index(git_submodule *submodule); +GIT_EXTERN(int) git_submodule_add_to_index( + git_submodule *submodule, + int write_index); /** * Write submodule settings to .gitmodules file. * * This commits any in-memory changes to the submodule to the gitmodules - * file on disk. You may also be interested in `git_submodule_init` which + * file on disk. You may also be interested in `git_submodule_init()` which * writes submodule info to ".git/config" (which is better for local changes - * to submodule settings) and/or `git_submodule_sync` which writes settings - * about remotes to the actual submodule repository. + * to submodule settings) and/or `git_submodule_sync()` which writes + * settings about remotes to the actual submodule repository. * * @param submodule The submodule to write. * @return 0 on success, <0 on failure. @@ -259,7 +254,7 @@ GIT_EXTERN(int) git_submodule_save(git_submodule *submodule); * * This returns a pointer to the repository that contains the submodule. * This is a just a reference to the repository that was passed to the - * original `git_submodule_lookup` call, so if that repository has been + * original `git_submodule_lookup()` call, so if that repository has been * freed, then this may be a dangling reference. * * @param submodule Pointer to submodule object @@ -300,8 +295,8 @@ GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule); * This sets the URL in memory for the submodule. This will be used for * any following submodule actions while this submodule data is in memory. * - * After calling this, you may wish to call `git_submodule_save` to write - * the changes back to the ".gitmodules" file and `git_submodule_sync` to + * After calling this, you may wish to call `git_submodule_save()` to write + * the changes back to the ".gitmodules" file and `git_submodule_sync()` to * write the changes to the checked out submodule repository. * * @param submodule Pointer to the submodule object @@ -331,8 +326,8 @@ GIT_EXTERN(const git_oid *) git_submodule_head_oid(git_submodule *submodule); * * This returns the OID that corresponds to looking up 'HEAD' in the checked * out submodule. If there are pending changes in the index or anything - * else, this won't notice that. You should call `git_submodule_status` for - * a more complete picture about the state of the working directory. + * else, this won't notice that. You should call `git_submodule_status()` + * for a more complete picture about the state of the working directory. * * @param submodule Pointer to submodule object * @return Pointer to git_oid or NULL if submodule is not checked out. @@ -348,7 +343,7 @@ GIT_EXTERN(const git_oid *) git_submodule_wd_oid(git_submodule *submodule); * of the submodule from a clean checkout to be dirty, including the * addition of untracked files. This is the default if unspecified. * - **GIT_SUBMODULE_IGNORE_UNTRACKED** examines the contents of the - * working tree (i.e. call `git_status_foreach` on the submodule) but + * working tree (i.e. call `git_status_foreach()` on the submodule) but * UNTRACKED files will not count as making the submodule dirty. * - **GIT_SUBMODULE_IGNORE_DIRTY** means to only check if the HEAD of the * submodule has moved for status. This is fast since it does not need to @@ -364,12 +359,12 @@ GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( * Set the ignore rule for the submodule. * * This sets the ignore rule in memory for the submodule. This will be used - * for any following actions (such as `git_submodule_status`) while the - * submodule is in memory. You should call `git_submodule_save` if you want - * to persist the new ignore role. + * for any following actions (such as `git_submodule_status()`) while the + * submodule is in memory. You should call `git_submodule_save()` if you + * want to persist the new ignore role. * * Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling - * `git_submodule_reload` will revert the rule to the value that was in the + * `git_submodule_reload()` will revert the rule to the value that was in the * original config. * * @return old value for ignore @@ -388,10 +383,10 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_update( * Set the update rule for the submodule. * * This sets the update rule in memory for the submodule. You should call - * `git_submodule_save` if you want to persist the new update rule. + * `git_submodule_save()` if you want to persist the new update rule. * * Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling - * `git_submodule_reload` will revert the rule to the value that was in the + * `git_submodule_reload()` will revert the rule to the value that was in the * original config. * * @return old value for update @@ -429,7 +424,7 @@ GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule); * Open the repository for a submodule. * * This is a newly opened repository object. The caller is responsible for - * calling `git_repository_free` on it when done. Multiple calls to this + * calling `git_repository_free()` on it when done. Multiple calls to this * function will return distinct `git_repository` objects. This will only * work if the submodule is checked out into the working directory. * @@ -462,10 +457,10 @@ GIT_EXTERN(int) git_submodule_reload_all(git_repository *repo); * This looks at a submodule and tries to determine the status. It * will return a combination of the `GIT_SUBMODULE_STATUS` values above. * How deeply it examines the working directory to do this will depend - * on the `git_submodule_ignore_t` value for the submodule (which can be - * overridden with `git_submodule_set_ignore()`). + * on the `git_submodule_ignore_t` value for the submodule - which can be + * set either temporarily or permanently with `git_submodule_set_ignore()`. * - * @param status Combination of GIT_SUBMODULE_STATUS values from above. + * @param status Combination of `GIT_SUBMODULE_STATUS` flags * @param submodule Submodule for which to get status * @return 0 on success, <0 on error */ diff --git a/src/diff_output.c b/src/diff_output.c index d269a4cee..2bf939f33 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/iterator.c b/src/iterator.c index 819b0e22a..92fe67134 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 18788d187..c12df25c3 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 3ebb362a4..15501a1dd 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -42,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; @@ -53,9 +53,9 @@ 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); @@ -65,24 +65,19 @@ __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_update_config( - git_submodule *, const char *, const char *, bool, bool); +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 int submodule_cmp(const void *a, const void *b) { @@ -167,8 +162,10 @@ int git_submodule_foreach( break; } - if ((error = callback(sm, sm->name, payload)) < 0) + if (callback(sm, sm->name, payload)) { + error = GIT_EUSER; break; + } }); git_vector_free(&seen); @@ -337,10 +334,10 @@ int git_submodule_add_finalize(git_submodule *sm) (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0) return error; - return git_submodule_add_to_index(sm); + return git_submodule_add_to_index(sm, true); } -int git_submodule_add_to_index(git_submodule *sm) +int git_submodule_add_to_index(git_submodule *sm, int write_index) { int error; git_repository *repo, *sm_repo; @@ -354,6 +351,9 @@ int git_submodule_add_to_index(git_submodule *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 || @@ -367,6 +367,7 @@ int git_submodule_add_to_index(git_submodule *sm) error = -1; goto cleanup; } + entry.path = sm->path; git_index__init_entry_from_stat(&st, &entry); /* calling git_submodule_open will have set sm->wd_oid if possible */ @@ -388,9 +389,17 @@ int git_submodule_add_to_index(git_submodule *sm) git_commit_free(head); - /* now add it */ + /* 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); @@ -501,7 +510,7 @@ int git_submodule_set_url(git_submodule *submodule, const char *url) return 0; } - const git_oid *git_submodule_index_oid(git_submodule *submodule) +const git_oid *git_submodule_index_oid(git_submodule *submodule) { assert(submodule); @@ -531,6 +540,8 @@ const git_oid *git_submodule_wd_oid(git_submodule *submodule) /* 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) @@ -693,16 +704,21 @@ int git_submodule_reload(git_submodule *submodule) 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); - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID); - - if ((error = submodule_load_from_index(repo, entry)) < 0) - return error; + 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 */ @@ -715,7 +731,14 @@ int git_submodule_reload(git_submodule *submodule) GIT_SUBMODULE_STATUS__HEAD_OID_VALID); if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { - error = submodule_load_from_head(repo, submodule->path, &te->oid); + + 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); } @@ -749,6 +772,16 @@ int git_submodule_reload(git_submodule *submodule) 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; } @@ -756,16 +789,21 @@ int git_submodule_status( unsigned int *status, git_submodule *submodule) { + int error = 0; + unsigned int status_val; + assert(status && submodule); - GIT_UNUSED(status); - GIT_UNUSED(submodule); + status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags); - /* TODO: move status code from below and update */ + if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) { + if (!(error = submodule_index_status(&status_val, submodule))) + error = submodule_wd_status(&status_val, submodule); + } - *status = 0; + *status = status_val; - return 0; + return error; } /* @@ -848,7 +886,7 @@ static int submodule_get( /* insert value at name - if another thread beats us to it, then use * their record and release our own. */ - pos = kh_put(str, smcfg, name, &error); + pos = kh_put(str, smcfg, sm->name, &error); if (error < 0) { submodule_release(sm, 1); @@ -1037,6 +1075,18 @@ static int submodule_load_from_wd_lite( 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_from_index( git_repository *repo, git_oid *gitmodules_oid) { @@ -1055,8 +1105,13 @@ static int load_submodule_config_from_index( error = submodule_load_from_index(repo, entry); if (error < 0) break; - } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0) - git_oid_cpy(gitmodules_oid, &entry->oid); + } 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); } @@ -1090,9 +1145,14 @@ static int load_submodule_config_from_head( error = submodule_load_from_head(repo, entry->path, &entry->oid); if (error < 0) break; - } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && - git_oid_iszero(gitmodules_oid)) - git_oid_cpy(gitmodules_oid, &entry->oid); + } 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); + } error = git_iterator_advance(i, &entry); } @@ -1303,183 +1363,108 @@ cleanup: return error; } -#if 0 - -static int head_oid_for_submodule( - git_oid *oid, - git_repository *owner, - const char *path) +static int submodule_index_status(unsigned int *status, git_submodule *sm) { - int error = 0; - git_oid head_oid; - git_tree *head_tree = NULL, *container_tree = NULL; - unsigned int pos; - const git_tree_entry *entry; - - if (git_reference_name_to_oid(&head_oid, owner, GIT_HEAD_FILE) < 0 || - git_tree_lookup(&head_tree, owner, &head_oid) < 0 || - git_tree_resolve_path(&container_tree, &pos, head_tree, path) < 0 || - (entry = git_tree_entry_byindex(container_tree, pos)) == NULL) - { - memset(oid, 0, sizeof(*oid)); - error = GIT_ENOTFOUND; - } - else { - git_oid_cpy(oid, &entry->oid); - } + const git_oid *head_oid = git_submodule_head_oid(sm); + const git_oid *index_oid = git_submodule_index_oid(sm); - git_tree_free(head_tree); - git_tree_free(container_tree); + 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; - return error; + return 0; } -int git_submodule_status( - unsigned int *status, - git_oid *head, - git_submodule *sm, - git_submodule_ignore_t ignore) +static int submodule_wd_status(unsigned int *status, git_submodule *sm) { - int error; - const char *workdir; - git_repository *owner, *sm_repo = NULL; - git_oid owner_head, sm_head; - - assert(submodule && status); - - if (head == NULL) - head = &sm_head; - - owner = submodule->owner; - workdir = git_repository_workdir(owner); - - if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) - ignore = sm->ignore; - - /* if this is a bare repo or the submodule dir has no .git yet, - * then it is not checked out and we'll just return index data. - */ - if (!workdir || (sm->flags & GIT_SUBMODULE_FLAG__HAS_DOTGIT) == 0) { - *status = GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT; - - if (sm->index_oid_valid) - git_oid_cpy(head, &sm->index_oid); - else - memset(head, 0, sizeof(git_oid)); - - if (git_oid_iszero(head)) { - if (sm->url) - *status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE; - } else if (!sm->url) { - *status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE; - } + int error = 0; + const git_oid *wd_oid, *index_oid; + git_repository *sm_repo = NULL; - return 0; + /* 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; } - /* look up submodule path in repo head to find if new or deleted */ - if ((error = head_oid_for_submodule(&owner_head, owner, sm->path)) < 0) { - *status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE; - /* ??? */ - } + index_oid = git_submodule_index_oid(sm); + wd_oid = git_submodule_wd_oid(sm); - if (ignore == GIT_SUBMODULE_IGNORE_ALL) { - *status = GIT_SUBMODULE_STATUS_CLEAN; - git_oid_cpy(head, &sm->oid); - return 0; + if (!index_oid) { + if (wd_oid) + *status |= GIT_SUBMODULE_STATUS_WD_ADDED; } - - if ((error = git_submodule_open(&sm_repo, sm)) < 0) - return error; - - if ((error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE)) < 0) - goto cleanup; - - if (ignore == GIT_SUBMODULE_IGNORE_DIRTY && - git_oid_cmp(head, &sm->oid) == 0) - { - *status = GIT_SUBMODULE_STATUS_CLEAN; - return 0; + 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; - /* look up submodule oid from index in repo to find if new commits or missing commits */ + if (sm_repo != NULL) { + git_tree *sm_head; + git_diff_options opt; + git_diff_list *diff; - /* run a short status to find if modified or untracked content */ + /* 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). + */ -#define GIT_SUBMODULE_STATUS_NEW_SUBMODULE (1u << 2) -#define GIT_SUBMODULE_STATUS_DELETED_SUBMODULE (1u << 3) -#define GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT (1u << 4) -#define GIT_SUBMODULE_STATUS_NEW_COMMITS (1u << 5) -#define GIT_SUBMODULE_STATUS_MISSING_COMMITS (1u << 6) -#define GIT_SUBMODULE_STATUS_MODIFIED_CONTENT (1u << 7) -#define GIT_SUBMODULE_STATUS_UNTRACKED_CONTENT (1u << 8) + /* perform head-to-index diff on submodule */ -cleanup: - git_repository_free(sm_repo); - git_tree_free(owner_tree); + if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0) + return error; - return error; -} + memset(&opt, 0, sizeof(opt)); + if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE) + opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; -int git_submodule_status_for_path( - unsigned int *status, - git_oid *head, - git_repository *repo, - const char *submodule_path, - git_submodule_ignore_t ignore) -{ - int error; - git_submodule *sm; - const char *workdir; - git_buf path = GIT_BUF_INIT; - git_oid owner_head; + error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff); - assert(repo && submodule_path && status); + if (!error) { + if (git_diff_entrycount(diff, -1) > 0) + *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - if ((error = git_submodule_lookup(&sm, repo, submodule_path)) == 0) - return git_submodule_status(status, head, sm, ignore); + git_diff_list_free(diff); + diff = NULL; + } - /* if submodule still exists in HEAD, then it is DELETED */ - if (!(error = head_oid_for_submodule(&owner_head, repo, submodule_path))) { - *status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE; - if (head) - git_oid_cmp(head, &owner_head); - return 0; - } + git_tree_free(sm_head); - /* submodule was not found - let's see what we can determine about it */ - workdir = git_repository_workdir(repo); + if (error < 0) + return error; - if (error != GIT_ENOTFOUND || !workdir) { - *status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE; - return error; - } + /* perform index-to-workdir diff on submodule */ - giterr_clear(); - error = 0; + error = git_diff_workdir_to_index(sm_repo, &opt, &diff); - /* figure out if this is NEW, NOT_CHECKED_OUT, or what */ - if (git_buf_joinpath(&path, workdir, submodule_path) < 0) - return -1; + if (!error) { + int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED); - if (git_path_contains(&path, DOT_GIT)) { - git_repository *sm_repo; + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - *status = GIT_SUBMODULE_STATUS_UNTRACKED_SUBMODULE; + if (git_diff_entrycount(diff, -1) - untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - /* only bother look up head if it was non-NULL */ - if (head != NULL && - !(error = git_repository_open(&sm_repo, path.ptr))) - { - error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE); - git_repository_free(sm_repo); + git_diff_list_free(diff); + diff = NULL; } - } else - *status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE; - git_buf_free(&path); + git_repository_free(sm_repo); + } return error; } - -#endif diff --git a/src/submodule.h b/src/submodule.h index 83bc7dfe9..c7a6aaf76 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -85,10 +85,18 @@ struct git_submodule { }; /* Additional flags on top of public GIT_SUBMODULE_STATUS values */ -#define GIT_SUBMODULE_STATUS__WD_SCANNED (1u << 15) -#define GIT_SUBMODULE_STATUS__HEAD_OID_VALID (1u << 16) -#define GIT_SUBMODULE_STATUS__INDEX_OID_VALID (1u << 17) -#define GIT_SUBMODULE_STATUS__WD_OID_VALID (1u << 18) -#define GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES (1u << 19) +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 diff --git a/tests-clar/status/submodules.c b/tests-clar/status/submodules.c index 3a69e0c47..24dd660ab 100644 --- a/tests-clar/status/submodules.c +++ b/tests-clar/status/submodules.c @@ -50,7 +50,7 @@ void test_status_submodules__0(void) git_status_foreach(g_repo, cb_status__count, &counts) ); - cl_assert(counts == 6); + cl_assert_equal_i(6, counts); } static const char *expected_files[] = { @@ -95,12 +95,12 @@ void test_status_submodules__1(void) git_status_foreach(g_repo, cb_status__match, &index) ); - cl_assert(index == 6); + cl_assert_equal_i(6, index); } void test_status_submodules__single_file(void) { - unsigned int status; + unsigned int status = 0; cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); - cl_assert(status == 0); + cl_assert(!status); } diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index e0c1e4c7a..d3a39235a 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -2,6 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" +#include "fileops.h" static git_repository *g_repo = NULL; @@ -25,20 +26,283 @@ void test_submodule_status__cleanup(void) void test_submodule_status__unchanged(void) { - /* make sure it really looks unchanged */ + unsigned int status, expected; + git_submodule *sm; + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + expected = GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD; + + cl_assert(status == expected); } -void test_submodule_status__changed(void) +/* 4 values of GIT_SUBMODULE_IGNORE to check */ + +void test_submodule_status__ignore_none(void) { - /* 4 values of GIT_SUBMODULE_IGNORE to check */ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + /* remove sm_changed_head from index */ + { + git_index *index; + int pos; + + cl_git_pass(git_repository_index(&index, g_repo)); + pos = git_index_find(index, "sm_changed_head"); + cl_assert(pos >= 0); + cl_git_pass(git_index_remove(index, pos)); + cl_git_pass(git_index_write(index)); + + git_index_free(index); + } - /* 6 states of change: - * - none, (handled in __unchanged above) - * - dirty workdir file, - * - dirty index, - * - moved head, - * - untracked file, - * - missing commits (i.e. superproject commit is ahead of submodule) - */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0); + + git_buf_free(&path); } +static int set_sm_ignore(git_submodule *sm, const char *name, void *payload) +{ + git_submodule_ignore_t ignore = *(git_submodule_ignore_t *)payload; + GIT_UNUSED(name); + git_submodule_set_ignore(sm, ignore); + return 0; +} + +void test_submodule_status__ignore_untracked(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED; + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + git_buf_free(&path); +} + +void test_submodule_status__ignore_dirty(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY; + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + git_buf_free(&path); +} + +void test_submodule_status__ignore_all(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL; + + cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + git_buf_free(&path); +} |
