mirror of
https://git.proxmox.com/git/libgit2
synced 2026-01-04 03:14:41 +00:00
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.
This commit is contained in:
parent
0c8858de8c
commit
5f4a61aea8
@ -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);
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -60,84 +60,68 @@ typedef enum {
|
||||
GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */
|
||||
} 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.
|
||||
* depending on what state the repo is in. We consider all four places to
|
||||
* build the combination of status flags.
|
||||
*
|
||||
* 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.
|
||||
* 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".
|
||||
*
|
||||
* 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.
|
||||
* * 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 other SUBMODULE_STATUS values you might see are:
|
||||
* The following values will be returned so long as ignore is not "ALL".
|
||||
*
|
||||
* - 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
|
||||
* * 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_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)
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
#define GIT_SUBMODULE_WD_STATUS(s) \
|
||||
(((s) >> GIT_SUBMODULE_STATUS_WD_DATA_OFFSET) & 0x07)
|
||||
#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
|
||||
*/
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
401
src/submodule.c
401
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 submodule_index_status(unsigned int *status, git_submodule *sm)
|
||||
{
|
||||
const git_oid *head_oid = git_submodule_head_oid(sm);
|
||||
const git_oid *index_oid = git_submodule_index_oid(sm);
|
||||
|
||||
static int head_oid_for_submodule(
|
||||
git_oid *oid,
|
||||
git_repository *owner,
|
||||
const char *path)
|
||||
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 0;
|
||||
}
|
||||
|
||||
static int submodule_wd_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;
|
||||
const git_oid *wd_oid, *index_oid;
|
||||
git_repository *sm_repo = NULL;
|
||||
|
||||
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)
|
||||
/* 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)
|
||||
{
|
||||
memset(oid, 0, sizeof(*oid));
|
||||
error = GIT_ENOTFOUND;
|
||||
}
|
||||
else {
|
||||
git_oid_cpy(oid, &entry->oid);
|
||||
if ((error = git_submodule_open(&sm_repo, sm)) < 0)
|
||||
return error;
|
||||
}
|
||||
|
||||
git_tree_free(head_tree);
|
||||
git_tree_free(container_tree);
|
||||
index_oid = git_submodule_index_oid(sm);
|
||||
wd_oid = git_submodule_wd_oid(sm);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_submodule_status(
|
||||
unsigned int *status,
|
||||
git_oid *head,
|
||||
git_submodule *sm,
|
||||
git_submodule_ignore_t ignore)
|
||||
{
|
||||
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);
|
||||
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
|
||||
memset(head, 0, sizeof(git_oid));
|
||||
*status |= GIT_SUBMODULE_STATUS_WD_DELETED;
|
||||
}
|
||||
else if (!git_oid_equal(index_oid, wd_oid))
|
||||
*status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
|
||||
|
||||
if (git_oid_iszero(head)) {
|
||||
if (sm->url)
|
||||
*status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE;
|
||||
} else if (!sm->url) {
|
||||
*status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE;
|
||||
if (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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
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);
|
||||
}
|
||||
|
||||
/* look up submodule path in repo head to find if new or deleted */
|
||||
if ((error = head_oid_for_submodule(&owner_head, owner, sm->path)) < 0) {
|
||||
*status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE;
|
||||
/* ??? */
|
||||
}
|
||||
|
||||
if (ignore == GIT_SUBMODULE_IGNORE_ALL) {
|
||||
*status = GIT_SUBMODULE_STATUS_CLEAN;
|
||||
git_oid_cpy(head, &sm->oid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((error = git_submodule_open(&sm_repo, sm)) < 0)
|
||||
return error;
|
||||
|
||||
if ((error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (ignore == GIT_SUBMODULE_IGNORE_DIRTY &&
|
||||
git_oid_cmp(head, &sm->oid) == 0)
|
||||
{
|
||||
*status = GIT_SUBMODULE_STATUS_CLEAN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* look up submodule oid from index in repo to find if new commits or missing commits */
|
||||
|
||||
/* run a short status to find if modified or untracked content */
|
||||
|
||||
#define GIT_SUBMODULE_STATUS_NEW_SUBMODULE (1u << 2)
|
||||
#define GIT_SUBMODULE_STATUS_DELETED_SUBMODULE (1u << 3)
|
||||
#define GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT (1u << 4)
|
||||
#define GIT_SUBMODULE_STATUS_NEW_COMMITS (1u << 5)
|
||||
#define GIT_SUBMODULE_STATUS_MISSING_COMMITS (1u << 6)
|
||||
#define GIT_SUBMODULE_STATUS_MODIFIED_CONTENT (1u << 7)
|
||||
#define GIT_SUBMODULE_STATUS_UNTRACKED_CONTENT (1u << 8)
|
||||
|
||||
cleanup:
|
||||
git_repository_free(sm_repo);
|
||||
git_tree_free(owner_tree);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_submodule_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;
|
||||
|
||||
assert(repo && submodule_path && status);
|
||||
|
||||
if ((error = git_submodule_lookup(&sm, repo, submodule_path)) == 0)
|
||||
return git_submodule_status(status, head, sm, ignore);
|
||||
|
||||
/* if submodule still exists in HEAD, then it is DELETED */
|
||||
if (!(error = head_oid_for_submodule(&owner_head, repo, submodule_path))) {
|
||||
*status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE;
|
||||
if (head)
|
||||
git_oid_cmp(head, &owner_head);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* submodule was not found - let's see what we can determine about it */
|
||||
workdir = git_repository_workdir(repo);
|
||||
|
||||
if (error != GIT_ENOTFOUND || !workdir) {
|
||||
*status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE;
|
||||
return error;
|
||||
}
|
||||
|
||||
giterr_clear();
|
||||
error = 0;
|
||||
|
||||
/* figure out if this is NEW, NOT_CHECKED_OUT, or what */
|
||||
if (git_buf_joinpath(&path, workdir, submodule_path) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_path_contains(&path, DOT_GIT)) {
|
||||
git_repository *sm_repo;
|
||||
|
||||
*status = GIT_SUBMODULE_STATUS_UNTRACKED_SUBMODULE;
|
||||
|
||||
/* only bother look up head if it was non-NULL */
|
||||
if (head != NULL &&
|
||||
!(error = git_repository_open(&sm_repo, path.ptr)))
|
||||
{
|
||||
error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE);
|
||||
git_repository_free(sm_repo);
|
||||
}
|
||||
} else
|
||||
*status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE;
|
||||
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/* 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_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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user