Commit aa13bf05 by Russell Belfer

Major submodule rewrite

This replaces the old submodule API with a new extended API that
supports most of the things that can be done with `git submodule`.
parent decff7b4
......@@ -54,6 +54,7 @@ typedef enum {
GITERR_TREE,
GITERR_INDEXER,
GITERR_SSL,
GITERR_SUBMODULE,
} git_error_t;
/**
......
......@@ -20,54 +20,169 @@
*/
GIT_BEGIN_DECL
/**
* Opaque structure representing a submodule.
*
* Submodule support in libgit2 builds a list of known submodules and keeps
* it in the repository. The list is built from the .gitmodules file, the
* .git/config file, the index, and the HEAD tree. Items in the working
* directory that look like submodules (i.e. a git repo) but are not
* mentioned in those places won't be tracked.
*/
typedef struct git_submodule git_submodule;
/**
* Values that could be specified for the update rule of a submodule.
*
* Use the DEFAULT value if you have altered the update value via
* `git_submodule_set_update()` and wish to reset to the original default.
*/
typedef enum {
GIT_SUBMODULE_UPDATE_DEFAULT = -1,
GIT_SUBMODULE_UPDATE_CHECKOUT = 0,
GIT_SUBMODULE_UPDATE_REBASE = 1,
GIT_SUBMODULE_UPDATE_MERGE = 2
GIT_SUBMODULE_UPDATE_MERGE = 2,
GIT_SUBMODULE_UPDATE_NONE = 3
} git_submodule_update_t;
/**
* Values that could be specified for how closely to examine the
* working directory when getting submodule status.
*
* Use the DEFUALT value if you have altered the ignore value via
* `git_submodule_set_ignore()` and wish to reset to the original value.
*/
typedef enum {
GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */
GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */
GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */
GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */
GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */
} git_submodule_ignore_t;
/**
* Description of submodule
* 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.
*
* 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.
*/
#define GIT_SUBMODULE_WD_STATUS(s) \
(((s) >> GIT_SUBMODULE_STATUS_WD_DATA_OFFSET) & 0x07)
/**
* Lookup submodule information by name or path.
*
* Given either the submodule name or path (they are usually the same), this
* returns a structure describing the submodule.
*
* There are two expected error scenarios:
*
* This record describes a submodule found in a repository. There
* should be an entry for every submodule found in the HEAD and for
* every submodule described in .gitmodules. The fields are as follows:
* - The submodule is not mentioned in the HEAD, the index, and the config,
* but does "exist" in the working directory (i.e. there is a subdirectory
* that is a valid self-contained git repo). In this case, this function
* returns GIT_EEXISTS to indicate the the submodule exists but not in a
* state where a git_submodule can be instantiated.
* - The submodule is not mentioned in the HEAD, index, or config and the
* working directory doesn't contain a value git repo at that path.
* There may or may not be anything else at that path, but nothing that
* looks like a submodule. In this case, this returns GIT_ENOTFOUND.
*
* - `name` is the name of the submodule from .gitmodules.
* - `path` is the path to the submodule from the repo working directory.
* It is almost always the same as `name`.
* - `url` is the url for the submodule.
* - `oid` is the HEAD SHA1 for the submodule.
* - `update` is a value from above - see gitmodules(5) update.
* - `ignore` is a value from above - see gitmodules(5) ignore.
* - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.
* - `refcount` is for internal use.
* The submodule object is owned by the containing repo and will be freed
* when the repo is freed. The caller need not free the submodule.
*
* If the submodule has been added to .gitmodules but not yet git added,
* then the `oid` will be zero. If the submodule has been deleted, but
* the delete has not been committed yet, then the `oid` will be set, but
* the `url` will be NULL.
* @param submodule Pointer to submodule description object pointer..
* @param repo The repository.
* @param name The name of the submodule. Trailing slashes will be ignored.
* @return 0 on success, GIT_ENOTFOUND if submodule does not exist,
* GIT_EEXISTS if submodule exists in working directory only, -1 on
* other errors.
*/
typedef struct {
char *name;
char *path;
char *url;
git_oid oid; /* sha1 of submodule HEAD ref or zero if not committed */
git_submodule_update_t update;
git_submodule_ignore_t ignore;
int fetch_recurse;
int refcount;
} git_submodule;
GIT_EXTERN(int) git_submodule_lookup(
git_submodule **submodule,
git_repository *repo,
const char *name);
/**
* Iterate over all submodules of a repository.
* Iterate over all tracked submodules of a repository.
*
* See the note on `git_submodule` above. This iterates over the tracked
* submodules as decribed therein.
*
* If you are concerned about items in the working directory that look like
* submodules but are not tracked, the diff API will generate a diff record
* for workdir items that look like submodules but are not tracked, showing
* them as added in the workdir. Also, the status API will treat the entire
* subdirectory of a contained git repo as a single GIT_STATUS_WT_NEW item.
*
* @param repo The repository
* @param callback Function to be called with the name of each submodule.
......@@ -77,26 +192,286 @@ typedef struct {
*/
GIT_EXTERN(int) git_submodule_foreach(
git_repository *repo,
int (*callback)(const char *name, void *payload),
int (*callback)(git_submodule *sm, const char *name, void *payload),
void *payload);
/**
* Lookup submodule information by name or path.
* Set up a new git submodule for checkout.
*
* Given either the submodule name or path (they are usually the same),
* this returns a structure describing the submodule. If the submodule
* does not exist, this will return GIT_ENOTFOUND and set the submodule
* pointer to NULL.
* This does "git submodule add" up to the fetch and checkout of the
* submodule contents. It preps a new submodule, creates an entry in
* .gitmodules and creates an empty initialized repository either at the
* given path in the working directory or in .git/modules with a gitlink
* from the working directory to the new repo.
*
* @param submodule Pointer to submodule description object pointer..
* @param repo The repository.
* @param name The name of the submodule. Trailing slashes will be ignored.
* @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error
* 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
* .gitmodules to the index to be ready to commit.
*
* @param submodule The newly created submodule ready to open for clone
* @param repo Superproject repository to contain the new submodule
* @param url URL for the submodules remote
* @param path Path at which the submodule should be created
* @param use_gitlink Should workdir contain a gitlink to the repo in
* .git/modules vs. repo directly in workdir.
* @return 0 on success, GIT_EEXISTS if submodule already exists,
* -1 on other errors.
*/
GIT_EXTERN(int) git_submodule_lookup(
GIT_EXTERN(int) git_submodule_add_setup(
git_submodule **submodule,
git_repository *repo,
const char *name);
const char *url,
const char *path,
int use_gitlink);
/**
* Resolve the setup of a new git submodule.
*
* This should be called on a submodule once you have called 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).
*/
GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule);
/**
* Add current submodule HEAD commit to index of superproject.
*/
GIT_EXTERN(int) git_submodule_add_to_index(git_submodule *submodule);
/**
* 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
* 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.
*
* @param submodule The submodule to write.
* @return 0 on success, <0 on failure.
*/
GIT_EXTERN(int) git_submodule_save(git_submodule *submodule);
/**
* Get the containing repository for a 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
* freed, then this may be a dangling reference.
*
* @param submodule Pointer to submodule object
* @return Pointer to `git_repository`
*/
GIT_EXTERN(git_repository *) git_submodule_owner(git_submodule *submodule);
/**
* Get the name of submodule.
*
* @param submodule Pointer to submodule object
* @return Pointer to the submodule name
*/
GIT_EXTERN(const char *) git_submodule_name(git_submodule *submodule);
/**
* Get the path to the submodule.
*
* The path is almost always the same as the submodule name, but the
* two are actually not required to match.
*
* @param submodule Pointer to submodule object
* @return Pointer to the submodule path
*/
GIT_EXTERN(const char *) git_submodule_path(git_submodule *submodule);
/**
* Get the URL for the submodule.
*
* @param submodule Pointer to submodule object
* @return Pointer to the submodule url
*/
GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule);
/**
* Set the URL for the 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
* write the changes to the checked out submodule repository.
*
* @param submodule Pointer to the submodule object
* @param url URL that should be used for the submodule
* @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_submodule_set_url(git_submodule *submodule, const char *url);
/**
* Get the OID for the submodule in the index.
*
* @param submodule Pointer to submodule object
* @return Pointer to git_oid or NULL if submodule is not in index.
*/
GIT_EXTERN(const git_oid *) git_submodule_index_oid(git_submodule *submodule);
/**
* Get the OID for the submodule in the current HEAD tree.
*
* @param submodule Pointer to submodule object
* @return Pointer to git_oid or NULL if submodule is not in the HEAD.
*/
GIT_EXTERN(const git_oid *) git_submodule_head_oid(git_submodule *submodule);
/**
* Get the OID for the submodule in the current working directory.
*
* 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.
*
* @param submodule Pointer to submodule object
* @return Pointer to git_oid or NULL if submodule is not checked out.
*/
GIT_EXTERN(const git_oid *) git_submodule_wd_oid(git_submodule *submodule);
/**
* Get the ignore rule for the submodule.
*
* There are four ignore values:
*
* - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents
* 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
* 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
* scan the working tree of the submodule at all.
* - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo.
* The working directory will be consider clean so long as there is a
* checked out version present.
*/
GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
git_submodule *submodule);
/**
* 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.
*
* Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling
* `git_submodule_reload` will revert the rule to the value that was in the
* original config.
*
* @return old value for ignore
*/
GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(
git_submodule *submodule,
git_submodule_ignore_t ignore);
/**
* Get the update rule for the submodule.
*/
GIT_EXTERN(git_submodule_update_t) git_submodule_update(
git_submodule *submodule);
/**
* 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.
*
* Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling
* `git_submodule_reload` will revert the rule to the value that was in the
* original config.
*
* @return old value for update
*/
GIT_EXTERN(git_submodule_update_t) git_submodule_set_update(
git_submodule *submodule,
git_submodule_update_t update);
/**
* Copy submodule info into ".git/config" file.
*
* Just like "git submodule init", this copies information about the
* submodule into ".git/config". You can use the accessor functions
* above to alter the in-memory git_submodule object and control what
* is written to the config, overriding what is in .gitmodules.
*
* @param submodule The submodule to write into the superproject config
* @param overwrite By default, existing entries will not be overwritten,
* but setting this to true forces them to be updated.
* @return 0 on success, <0 on failure.
*/
GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite);
/**
* Copy submodule remote info into submodule repo.
*
* This copies the information about the submodules URL into the checked out
* submodule config, acting like "git submodule sync". This is useful if
* you have altered the URL for the submodule (or it has been altered by a
* fetch of upstream changes) and you need to update your local repo.
*/
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
* function will return distinct `git_repository` objects. This will only
* work if the submodule is checked out into the working directory.
*
* @param subrepo Pointer to the submodule repo which was opened
* @param submodule Submodule to be opened
* @return 0 on success, <0 if submodule repo could not be opened.
*/
GIT_EXTERN(int) git_submodule_open(
git_repository **repo,
git_submodule *submodule);
/**
* Reread submodule info from config, index, and HEAD.
*
* Call this to reread cached submodule information for this submodule if
* you have reason to believe that it has changed.
*/
GIT_EXTERN(int) git_submodule_reload(git_submodule *submodule);
/**
* Reread all submodule info.
*
* Call this to reload all cached submodule information for the repo.
*/
GIT_EXTERN(int) git_submodule_reload_all(git_repository *repo);
/**
* Get the status for a submodule.
*
* 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()`).
*
* @param status Combination of GIT_SUBMODULE_STATUS values from above.
* @param submodule Submodule for which to get status
* @return 0 on success, <0 on error
*/
GIT_EXTERN(int) git_submodule_status(
unsigned int *status,
git_submodule *submodule);
/** @} */
GIT_END_DECL
......
......@@ -253,11 +253,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
char *tmp = NULL;
git__free(key);
if (existing->next != NULL) {
giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
return -1;
}
/* don't update if old and new values already match */
if ((!existing->value && !value) ||
(existing->value && value && !strcmp(existing->value, value)))
return 0;
if (value) {
tmp = git__strdup(value);
GITERR_CHECK_ALLOC(tmp);
......
......@@ -19,12 +19,24 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg)
cfg->free(cfg);
}
GIT_INLINE(int) git_config_file_get_string(
const char **out, git_config_file *cfg, const char *name)
{
return cfg->get(cfg, name, out);
}
GIT_INLINE(int) git_config_file_set_string(
git_config_file *cfg, const char *name, const char *value)
{
return cfg->set(cfg, name, value);
}
GIT_INLINE(int) git_config_file_delete(
git_config_file *cfg, const char *name)
{
return cfg->del(cfg, name);
}
GIT_INLINE(int) git_config_file_foreach(
git_config_file *cfg,
int (*fn)(const char *key, const char *value, void *data),
......
......@@ -530,7 +530,7 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED;
else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
return -1;
else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)
else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
status = GIT_DELTA_UNMODIFIED;
else {
/* TODO: support other GIT_SUBMODULE_IGNORE values */
......
......@@ -17,18 +17,24 @@
#include "config_file.h"
#include "config.h"
#include "repository.h"
#include "submodule.h"
#include "tree.h"
#include "iterator.h"
#define GIT_MODULES_FILE ".gitmodules"
static git_cvar_map _sm_update_map[] = {
{GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
{GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
{GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}
{GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
};
static git_cvar_map _sm_ignore_map[] = {
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
};
static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
......@@ -55,9 +61,725 @@ static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
return (alen == blen && strncmp(a, b, alen) == 0);
}
__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash);
__KHASH_IMPL(
str, static kh_inline, const char *, void *, 1,
str_hash_no_trailing_slash, str_equal_no_trailing_slash);
static int load_submodule_config(
git_repository *repo, bool force);
static git_config_file *open_gitmodules(
git_repository *, bool, const git_oid *);
static int lookup_head_remote(
git_buf *url, git_repository *repo);
static git_submodule *submodule_lookup_or_create(
git_repository *repo, const char *n1, const char *n2);
static int submodule_update_map(
git_repository *repo, git_submodule *sm, const char *key);
static void submodule_release(
git_submodule *sm, int decr);
static int submodule_load_from_index(
git_repository *, const git_index_entry *);
static int submodule_load_from_head(
git_repository *, const char *, const git_oid *);
static int submodule_load_from_config(
const char *, const char *, void *);
static int submodule_update_config(
git_submodule *, const char *, const char *, bool, bool);
static int submodule_cmp(const void *a, const void *b)
{
return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
}
static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
{
ssize_t idx = git_buf_rfind(key, '.');
git_buf_truncate(key, (size_t)(idx + 1));
return git_buf_puts(key, suffix);
}
/*
* PUBLIC APIS
*/
int git_submodule_lookup(
git_submodule **sm_ptr, /* NULL if user only wants to test existence */
git_repository *repo,
const char *name) /* trailing slash is allowed */
{
int error;
khiter_t pos;
assert(repo && name);
if ((error = load_submodule_config(repo, false)) < 0)
return error;
pos = git_strmap_lookup_index(repo->submodules, name);
if (!git_strmap_valid_index(repo->submodules, pos)) {
error = GIT_ENOTFOUND;
/* check if a plausible submodule exists at path */
if (git_repository_workdir(repo)) {
git_buf path = GIT_BUF_INIT;
if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0)
return -1;
if (git_path_contains_dir(&path, DOT_GIT))
error = GIT_EEXISTS;
git_buf_free(&path);
}
return error;
}
if (sm_ptr)
*sm_ptr = git_strmap_value_at(repo->submodules, pos);
return 0;
}
static git_submodule *submodule_alloc(const char *name)
int git_submodule_foreach(
git_repository *repo,
int (*callback)(git_submodule *sm, const char *name, void *payload),
void *payload)
{
int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
seen._cmp = submodule_cmp;
assert(repo && callback);
if ((error = load_submodule_config(repo, false)) < 0)
return error;
git_strmap_foreach_value(repo->submodules, sm, {
/* Usually the following will not come into play - it just prevents
* us from issuing a callback twice for a submodule where the name
* and path are not the same.
*/
if (sm->refcount > 1) {
if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
break;
}
if ((error = callback(sm, sm->name, payload)) < 0)
break;
});
git_vector_free(&seen);
return error;
}
void git_submodule_config_free(git_repository *repo)
{
git_strmap *smcfg;
git_submodule *sm;
assert(repo);
smcfg = repo->submodules;
repo->submodules = NULL;
if (smcfg == NULL)
return;
git_strmap_foreach_value(smcfg, sm, {
submodule_release(sm,1);
});
git_strmap_free(smcfg);
}
int git_submodule_add_setup(
git_submodule **submodule,
git_repository *repo,
const char *url,
const char *path,
int use_gitlink)
{
int error = 0;
git_config_file *mods = NULL;
git_submodule *sm;
git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
git_repository_init_options initopt;
git_repository *subrepo = NULL;
assert(repo && url && path);
/* see if there is already an entry for this submodule */
if (git_submodule_lookup(&sm, repo, path) < 0)
giterr_clear();
else {
giterr_set(GITERR_SUBMODULE,
"Attempt to add a submodule that already exists");
return GIT_EEXISTS;
}
/* resolve parameters */
if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {
if (!(error = lookup_head_remote(&real_url, repo)))
error = git_path_apply_relative(&real_url, url);
} else if (strchr(url, ':') != NULL || url[0] == '/') {
error = git_buf_sets(&real_url, url);
} else {
giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
error = -1;
}
if (error)
goto cleanup;
/* validate and normalize path */
if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
path += strlen(git_repository_workdir(repo));
if (git_path_root(path) >= 0) {
giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");
error = -1;
goto cleanup;
}
/* update .gitmodules */
if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
return -1;
}
if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, path)) < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
goto cleanup;
git_buf_clear(&name);
/* init submodule repository and add origin remote as needed */
error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
if (error < 0)
goto cleanup;
/* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
* gitlink in the sub-repo workdir directory to that repository
*
* Old style: sub-repo goes directly into repo/<name>/.git/
*/
memset(&initopt, 0, sizeof(initopt));
initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
GIT_REPOSITORY_INIT_NO_REINIT;
initopt.origin_url = real_url.ptr;
if (git_path_exists(name.ptr) &&
git_path_contains(&name, DOT_GIT))
{
/* repo appears to already exist - reinit? */
}
else if (use_gitlink) {
git_buf repodir = GIT_BUF_INIT;
error = git_buf_join_n(
&repodir, '/', 3, git_repository_path(repo), "modules", path);
if (error < 0)
goto cleanup;
initopt.workdir_path = name.ptr;
initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
git_buf_free(&repodir);
}
else {
error = git_repository_init_ext(&subrepo, name.ptr, &initopt);
}
if (error < 0)
goto cleanup;
/* add submodule to hash and "reload" it */
if ((sm = submodule_lookup_or_create(repo, path, NULL)) == NULL) {
error = -1;
goto cleanup;
}
if ((error = submodule_update_map(repo, sm, sm->path)) < 0)
goto cleanup;
if ((error = git_submodule_reload(sm)) < 0)
goto cleanup;
error = git_submodule_init(sm, false);
cleanup:
if (submodule != NULL)
*submodule = !error ? sm : NULL;
if (mods != NULL)
git_config_file_free(mods);
git_repository_free(subrepo);
git_buf_free(&real_url);
git_buf_free(&name);
return error;
}
int git_submodule_add_finalize(git_submodule *sm)
{
int error;
git_index *index;
assert(sm);
if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
(error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0)
return error;
return git_submodule_add_to_index(sm);
}
int git_submodule_add_to_index(git_submodule *sm)
{
int error;
git_repository *repo, *sm_repo;
git_index *index;
git_buf path = GIT_BUF_INIT;
git_commit *head;
git_index_entry entry;
struct stat st;
assert(sm);
repo = sm->owner;
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
(error = git_buf_joinpath(
&path, git_repository_workdir(repo), sm->path)) < 0 ||
(error = git_submodule_open(&sm_repo, sm)) < 0)
goto cleanup;
/* read stat information for submodule working directory */
if (p_stat(path.ptr, &st) < 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot add submodule without working directory");
error = -1;
goto cleanup;
}
git_index__init_entry_from_stat(&st, &entry);
/* calling git_submodule_open will have set sm->wd_oid if possible */
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot add submodule without HEAD to index");
error = -1;
goto cleanup;
}
git_oid_cpy(&entry.oid, &sm->wd_oid);
if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
goto cleanup;
entry.ctime.seconds = git_commit_time(head);
entry.ctime.nanoseconds = 0;
entry.mtime.seconds = git_commit_time(head);
entry.mtime.nanoseconds = 0;
git_commit_free(head);
/* now add it */
error = git_index_add2(index, &entry);
cleanup:
git_repository_free(sm_repo);
git_buf_free(&path);
return error;
}
int git_submodule_save(git_submodule *submodule)
{
int error = 0;
git_config_file *mods;
git_buf key = GIT_BUF_INIT;
assert(submodule);
mods = open_gitmodules(submodule->owner, true, NULL);
if (!mods) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
return -1;
}
if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)
goto cleanup;
/* save values for path, url, update, ignore, fetchRecurseSubmodules */
if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 ||
(error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 ||
(error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
{
const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
NULL : _sm_update_map[submodule->update].str_match;
error = git_config_file_set_string(mods, key.ptr, val);
}
if (error < 0)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT)
{
const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ?
NULL : _sm_ignore_map[submodule->ignore].str_match;
error = git_config_file_set_string(mods, key.ptr, val);
}
if (error < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(
&key, "fetchRecurseSubmodules")) < 0 ||
(error = git_config_file_set_string(
mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0)
goto cleanup;
/* update internal defaults */
submodule->ignore_default = submodule->ignore;
submodule->update_default = submodule->update;
submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
cleanup:
if (mods != NULL)
git_config_file_free(mods);
git_buf_free(&key);
return error;
}
git_repository *git_submodule_owner(git_submodule *submodule)
{
assert(submodule);
return submodule->owner;
}
const char *git_submodule_name(git_submodule *submodule)
{
assert(submodule);
return submodule->name;
}
const char *git_submodule_path(git_submodule *submodule)
{
assert(submodule);
return submodule->path;
}
const char *git_submodule_url(git_submodule *submodule)
{
assert(submodule);
return submodule->url;
}
int git_submodule_set_url(git_submodule *submodule, const char *url)
{
assert(submodule && url);
git__free(submodule->url);
submodule->url = git__strdup(url);
GITERR_CHECK_ALLOC(submodule->url);
return 0;
}
const git_oid *git_submodule_index_oid(git_submodule *submodule)
{
assert(submodule);
if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
return &submodule->index_oid;
else
return NULL;
}
const git_oid *git_submodule_head_oid(git_submodule *submodule)
{
assert(submodule);
if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
return &submodule->head_oid;
else
return NULL;
}
const git_oid *git_submodule_wd_oid(git_submodule *submodule)
{
assert(submodule);
if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
git_repository *subrepo;
/* calling submodule open grabs the HEAD OID if possible */
if (!git_submodule_open(&subrepo, submodule))
git_repository_free(subrepo);
}
if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
return &submodule->wd_oid;
else
return NULL;
}
git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
{
assert(submodule);
return submodule->ignore;
}
git_submodule_ignore_t git_submodule_set_ignore(
git_submodule *submodule, git_submodule_ignore_t ignore)
{
git_submodule_ignore_t old;
assert(submodule);
if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)
ignore = submodule->ignore_default;
old = submodule->ignore;
submodule->ignore = ignore;
return old;
}
git_submodule_update_t git_submodule_update(git_submodule *submodule)
{
assert(submodule);
return submodule->update;
}
git_submodule_update_t git_submodule_set_update(
git_submodule *submodule, git_submodule_update_t update)
{
git_submodule_update_t old;
assert(submodule);
if (update == GIT_SUBMODULE_UPDATE_DEFAULT)
update = submodule->update_default;
old = submodule->update;
submodule->update = update;
return old;
}
int git_submodule_init(git_submodule *submodule, int overwrite)
{
int error;
/* write "submodule.NAME.url" */
if (!submodule->url) {
giterr_set(GITERR_SUBMODULE,
"No URL configured for submodule '%s'", submodule->name);
return -1;
}
error = submodule_update_config(
submodule, "url", submodule->url, overwrite != 0, false);
if (error < 0)
return error;
/* write "submodule.NAME.update" if not default */
if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT)
error = submodule_update_config(
submodule, "update", NULL, (overwrite != 0), false);
else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
error = submodule_update_config(
submodule, "update",
_sm_update_map[submodule->update].str_match,
(overwrite != 0), false);
return error;
}
int git_submodule_sync(git_submodule *submodule)
{
if (!submodule->url) {
giterr_set(GITERR_SUBMODULE,
"No URL configured for submodule '%s'", submodule->name);
return -1;
}
/* copy URL over to config only if it already exists */
return submodule_update_config(
submodule, "url", submodule->url, true, true);
}
int git_submodule_open(
git_repository **subrepo,
git_submodule *submodule)
{
int error;
git_buf path = GIT_BUF_INIT;
git_repository *repo;
const char *workdir;
assert(submodule && subrepo);
repo = submodule->owner;
workdir = git_repository_workdir(repo);
if (!workdir) {
giterr_set(GITERR_REPOSITORY,
"Cannot open submodule repository in a bare repo");
return GIT_ENOTFOUND;
}
if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) {
giterr_set(GITERR_REPOSITORY,
"Cannot open submodule repository that is not checked out");
return GIT_ENOTFOUND;
}
if (git_buf_joinpath(&path, workdir, submodule->path) < 0)
return -1;
error = git_repository_open(subrepo, path.ptr);
git_buf_free(&path);
/* if we have opened the submodule successfully, let's grab the HEAD OID */
if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
if (!git_reference_name_to_oid(
&submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
else
giterr_clear();
}
return error;
}
int git_submodule_reload_all(git_repository *repo)
{
assert(repo);
return load_submodule_config(repo, true);
}
int git_submodule_reload(git_submodule *submodule)
{
git_repository *repo;
git_index *index;
int pos, error;
git_tree *head;
git_config_file *mods;
assert(submodule);
/* refresh index data */
repo = submodule->owner;
if (git_repository_index__weakptr(&index, repo) < 0)
return -1;
pos = git_index_find(index, submodule->path);
if (pos >= 0) {
git_index_entry *entry = git_index_get(index, pos);
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
if ((error = submodule_load_from_index(repo, entry)) < 0)
return error;
}
/* refresh HEAD tree data */
if (!(error = git_repository_head_tree(&head, repo))) {
git_tree_entry *te;
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_HEAD |
GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) {
error = submodule_load_from_head(repo, submodule->path, &te->oid);
git_tree_entry_free(te);
}
else if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
}
git_tree_free(head);
}
if (error < 0)
return error;
/* refresh config data */
if ((mods = open_gitmodules(repo, false, NULL)) != NULL) {
git_buf path = GIT_BUF_INIT;
git_buf_sets(&path, "submodule\\.");
git_buf_puts_escape_regex(&path, submodule->name);
git_buf_puts(&path, ".*");
if (git_buf_oom(&path))
error = -1;
else
error = git_config_file_foreach_match(
mods, path.ptr, submodule_load_from_config, repo);
git_buf_free(&path);
}
return error;
}
int git_submodule_status(
unsigned int *status,
git_submodule *submodule)
{
assert(status && submodule);
/* TODO: move status code from below and update */
*status = 0;
return 0;
}
/*
* INTERNAL FUNCTIONS
*/
static git_submodule *submodule_alloc(git_repository *repo, const char *name)
{
git_submodule *sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
......@@ -69,80 +791,126 @@ static git_submodule *submodule_alloc(const char *name)
return NULL;
}
return sm;
sm->owner = repo;
return sm;
}
static void submodule_release(git_submodule *sm, int decr)
{
if (!sm)
return;
sm->refcount -= decr;
if (sm->refcount == 0) {
if (sm->name != sm->path) {
git__free(sm->path);
sm->path = NULL;
}
git__free(sm->name);
sm->name = NULL;
git__free(sm->url);
sm->url = NULL;
sm->owner = NULL;
git__free(sm);
}
}
static git_submodule *submodule_lookup_or_create(
git_repository *repo, const char *n1, const char *n2)
{
git_strmap *smcfg = repo->submodules;
khiter_t pos;
git_submodule *sm;
assert(n1);
pos = git_strmap_lookup_index(smcfg, n1);
if (!git_strmap_valid_index(smcfg, pos) && n2)
pos = git_strmap_lookup_index(smcfg, n2);
if (!git_strmap_valid_index(smcfg, pos))
sm = submodule_alloc(repo, n1);
else
sm = git_strmap_value_at(smcfg, pos);
return sm;
}
static int submodule_update_map(
git_repository *repo, git_submodule *sm, const char *key)
{
void *old_sm;
int error;
git_strmap_insert2(repo->submodules, key, sm, old_sm, error);
if (error < 0) {
submodule_release(sm, 0);
return -1;
}
sm->refcount++;
if (old_sm && ((git_submodule *)old_sm) != sm)
submodule_release(old_sm, 1);
return 0;
}
static void submodule_release(git_submodule *sm, int decr)
static int submodule_load_from_index(
git_repository *repo, const git_index_entry *entry)
{
if (!sm)
return;
git_submodule *sm = submodule_lookup_or_create(repo, entry->path, NULL);
sm->refcount -= decr;
if (!sm)
return -1;
if (sm->refcount == 0) {
if (sm->name != sm->path)
git__free(sm->path);
git__free(sm->name);
git__free(sm->url);
git__free(sm);
if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
return 0;
}
}
static int submodule_from_entry(
git_strmap *smcfg, git_index_entry *entry)
{
git_submodule *sm;
void *old_sm;
khiter_t pos;
int error;
pos = git_strmap_lookup_index(smcfg, entry->path);
sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;
if (git_strmap_valid_index(smcfg, pos))
sm = git_strmap_value_at(smcfg, pos);
else
sm = submodule_alloc(entry->path);
git_oid_cpy(&sm->index_oid, &entry->oid);
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
git_oid_cpy(&sm->oid, &entry->oid);
return submodule_update_map(repo, sm, sm->path);
}
if (strcmp(sm->path, entry->path) != 0) {
if (sm->path != sm->name) {
git__free(sm->path);
sm->path = sm->name;
}
sm->path = git__strdup(entry->path);
if (!sm->path)
goto fail;
}
static int submodule_load_from_head(
git_repository *repo, const char *path, const git_oid *oid)
{
git_submodule *sm = submodule_lookup_or_create(repo, path, NULL);
git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
if (error < 0)
goto fail;
sm->refcount++;
if (!sm)
return -1;
if (old_sm && ((git_submodule *)old_sm) != sm) {
/* TODO: log warning about multiple entrys for same submodule path */
submodule_release(old_sm, 1);
}
sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
return 0;
git_oid_cpy(&sm->head_oid, oid);
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
fail:
submodule_release(sm, 0);
return -1;
return submodule_update_map(repo, sm, sm->path);
}
static int submodule_from_config(
static int submodule_load_from_config(
const char *key, const char *value, void *data)
{
git_strmap *smcfg = data;
git_repository *repo = data;
git_strmap *smcfg = repo->submodules;
const char *namestart;
const char *property;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
void *old_sm = NULL;
bool is_path;
khiter_t pos;
int error;
if (git__prefixcmp(key, "submodule.") != 0)
......@@ -153,21 +921,17 @@ static int submodule_from_config(
if (property == NULL)
return 0;
property++;
is_path = (strcmp(property, "path") == 0);
is_path = (strcasecmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1;
pos = git_strmap_lookup_index(smcfg, name.ptr);
if (!git_strmap_valid_index(smcfg, pos) && is_path)
pos = git_strmap_lookup_index(smcfg, value);
if (!git_strmap_valid_index(smcfg, pos))
sm = submodule_alloc(name.ptr);
else
sm = git_strmap_value_at(smcfg, pos);
sm = submodule_lookup_or_create(repo, name.ptr, is_path ? value : NULL);
if (!sm)
goto fail;
sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
if (strcmp(sm->name, name.ptr) != 0) {
assert(sm->path == sm->name);
sm->name = git_buf_detach(&name);
......@@ -177,7 +941,7 @@ static int submodule_from_config(
goto fail;
sm->refcount++;
}
else if (is_path && strcmp(sm->path, value) != 0) {
else if (is_path && value && strcmp(sm->path, value) != 0) {
assert(sm->path == sm->name);
sm->path = git__strdup(value);
if (sm->path == NULL)
......@@ -195,19 +959,23 @@ static int submodule_from_config(
submodule_release(old_sm, 1);
}
/* TODO: Look up path in index and if it is present but not a GITLINK
* then this should be deleted (at least to match git's behavior)
*/
if (is_path)
return 0;
/* copy other properties into submodule entry */
if (strcmp(property, "url") == 0) {
if (strcasecmp(property, "url") == 0) {
if (sm->url) {
git__free(sm->url);
sm->url = NULL;
}
if ((sm->url = git__strdup(value)) == NULL)
if (value != NULL && (sm->url = git__strdup(value)) == NULL)
goto fail;
}
else if (strcmp(property, "update") == 0) {
else if (strcasecmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
......@@ -215,16 +983,16 @@ static int submodule_from_config(
"Invalid value for submodule update property: '%s'", value);
goto fail;
}
sm->update = (git_submodule_update_t)val;
sm->update_default = sm->update = (git_submodule_update_t)val;
}
else if (strcmp(property, "fetchRecurseSubmodules") == 0) {
else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
if (git__parse_bool(&sm->fetch_recurse, value) < 0) {
giterr_set(GITERR_INVALID,
"Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value);
goto fail;
}
}
else if (strcmp(property, "ignore") == 0) {
else if (strcasecmp(property, "ignore") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {
......@@ -232,7 +1000,7 @@ static int submodule_from_config(
"Invalid value for submodule ignore property: '%s'", value);
goto fail;
}
sm->ignore = (git_submodule_ignore_t)val;
sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
......@@ -244,144 +1012,471 @@ fail:
return -1;
}
static int load_submodule_config(git_repository *repo)
static int submodule_load_from_wd_lite(
git_submodule *sm, const char *name, void *payload)
{
git_repository *repo = git_submodule_owner(sm);
git_buf path = GIT_BUF_INIT;
GIT_UNUSED(name);
GIT_UNUSED(payload);
if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0)
return -1;
if (git_path_isdir(path.ptr))
sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
if (git_path_contains(&path, DOT_GIT))
sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
git_buf_free(&path);
return 0;
}
static int load_submodule_config_from_index(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_index *index;
unsigned int i, max_i;
git_oid gitmodules_oid;
git_strmap *smcfg;
struct git_config_file *mods = NULL;
git_iterator *i;
const git_index_entry *entry;
if (repo->submodules)
return 0;
if ((error = git_iterator_for_index(&i, repo)) < 0)
return error;
/* submodule data is kept in a hashtable with each submodule stored
* under both its name and its path. These are usually the same, but
* that is not guaranteed.
*/
smcfg = git_strmap_alloc();
GITERR_CHECK_ALLOC(smcfg);
error = git_iterator_current(i, &entry);
/* scan index for gitmodules (and .gitmodules entry) */
if ((error = git_repository_index__weakptr(&index, repo)) < 0)
goto cleanup;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
max_i = git_index_entrycount(index);
while (!error && entry != NULL) {
for (i = 0; i < max_i; i++) {
git_index_entry *entry = git_index_get(index, i);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_from_entry(smcfg, entry)) < 0)
goto cleanup;
}
else if (strcmp(entry->path, ".gitmodules") == 0)
git_oid_cpy(&gitmodules_oid, &entry->oid);
error = submodule_load_from_index(repo, entry);
if (error < 0)
break;
} else if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
git_oid_cpy(gitmodules_oid, &entry->oid);
error = git_iterator_advance(i, &entry);
}
/* load .gitmodules from workdir if it exists */
if (git_repository_workdir(repo) != NULL) {
/* look in workdir for .gitmodules */
git_buf path = GIT_BUF_INIT;
if (!git_buf_joinpath(
&path, git_repository_workdir(repo), ".gitmodules") &&
git_path_isfile(path.ptr))
{
if (!(error = git_config_file__ondisk(&mods, path.ptr)))
error = git_config_file_open(mods);
git_iterator_free(i);
return error;
}
static int load_submodule_config_from_head(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_tree *head;
git_iterator *i;
const git_index_entry *entry;
if ((error = git_repository_head_tree(&head, repo)) < 0)
return error;
if ((error = git_iterator_for_tree(&i, repo, head)) < 0) {
git_tree_free(head);
return error;
}
error = git_iterator_current(i, &entry);
while (!error && entry != NULL) {
if (S_ISGITLINK(entry->mode)) {
error = submodule_load_from_head(repo, entry->path, &entry->oid);
if (error < 0)
break;
} else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
git_oid_iszero(gitmodules_oid))
git_oid_cpy(gitmodules_oid, &entry->oid);
error = git_iterator_advance(i, &entry);
}
git_iterator_free(i);
git_tree_free(head);
return error;
}
static git_config_file *open_gitmodules(
git_repository *repo,
bool okay_to_create,
const git_oid *gitmodules_oid)
{
const char *workdir = git_repository_workdir(repo);
git_buf path = GIT_BUF_INIT;
git_config_file *mods = NULL;
if (workdir != NULL) {
if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
return NULL;
if (okay_to_create || git_path_isfile(path.ptr)) {
/* git_config_file__ondisk should only fail if OOM */
if (git_config_file__ondisk(&mods, path.ptr) < 0)
return NULL;
/* open should only fail here if the file is malformed */
if (git_config_file_open(mods) < 0) {
git_config_file_free(mods);
mods = NULL;
}
}
git_buf_free(&path);
}
/* load .gitmodules from object cache if not in workdir */
if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) {
/* TODO: is it worth loading gitmodules from object cache? */
if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
/* TODO: Retrieve .gitmodules content from ODB */
/* Should we actually do this? Core git does not, but it means you
* can't really get much information about submodules on bare repos.
*/
}
return mods;
}
static int load_submodule_config(git_repository *repo, bool force)
{
int error;
git_oid gitmodules_oid;
git_buf path = GIT_BUF_INIT;
git_config_file *mods = NULL;
if (repo->submodules && !force)
return 0;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
/* Submodule data is kept in a hashtable keyed by both name and path.
* These are usually the same, but that is not guaranteed.
*/
if (!repo->submodules) {
repo->submodules = git_strmap_alloc();
GITERR_CHECK_ALLOC(repo->submodules);
}
/* process .gitmodules info */
if (!error && mods != NULL)
error = git_config_file_foreach(mods, submodule_from_config, smcfg);
/* add submodule information from index */
if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)
goto cleanup;
/* add submodule information from HEAD */
if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)
goto cleanup;
/* add submodule information from .gitmodules */
if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL)
error = git_config_file_foreach(mods, submodule_load_from_config, repo);
if (error != 0)
goto cleanup;
/* shallow scan submodules in work tree */
/* store submodule config in repo */
if (!error)
repo->submodules = smcfg;
if (!git_repository_is_bare(repo))
error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);
cleanup:
git_buf_free(&path);
if (mods != NULL)
git_config_file_free(mods);
if (error)
git_strmap_free(smcfg);
git_submodule_config_free(repo);
return error;
}
void git_submodule_config_free(git_repository *repo)
static int lookup_head_remote(git_buf *url, git_repository *repo)
{
git_strmap *smcfg = repo->submodules;
git_submodule *sm;
int error;
git_config *cfg;
git_reference *head = NULL, *remote = NULL;
const char *tgt, *scan;
git_buf key = GIT_BUF_INIT;
/* 1. resolve HEAD -> refs/heads/BRANCH
* 2. lookup config branch.BRANCH.remote -> ORIGIN
* 3. lookup remote.ORIGIN.url
*/
repo->submodules = NULL;
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
if (smcfg == NULL)
return;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD cannot be resolved");
error = GIT_ENOTFOUND;
goto cleanup;
}
git_strmap_foreach_value(smcfg, sm, {
submodule_release(sm,1);
});
git_strmap_free(smcfg);
if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
if ((error = git_branch_tracking(&remote, head)) < 0)
goto cleanup;
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||
git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0)
{
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
scan = tgt = git_reference_target(remote) + strlen("refs/remotes/");
while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\')))
scan++; /* find non-escaped slash to end ORIGIN name */
error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);
if (error < 0)
goto cleanup;
if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)
goto cleanup;
error = git_buf_sets(url, tgt);
cleanup:
git_buf_free(&key);
git_reference_free(head);
git_reference_free(remote);
return error;
}
static int submodule_cmp(const void *a, const void *b)
static int submodule_update_config(
git_submodule *submodule,
const char *attr,
const char *value,
bool overwrite,
bool only_existing)
{
return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
int error;
git_config *config;
git_buf key = GIT_BUF_INIT;
const char *old = NULL;
assert(submodule);
error = git_repository_config__weakptr(&config, submodule->owner);
if (error < 0)
return error;
error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);
if (error < 0)
goto cleanup;
if (git_config_get_string(&old, config, key.ptr) < 0)
giterr_clear();
if (!old && only_existing)
goto cleanup;
if (old && !overwrite)
goto cleanup;
if ((!old && !value) || (old && value && strcmp(old, value) == 0))
goto cleanup;
if (!value)
error = git_config_delete(config, key.ptr);
else
error = git_config_set_string(config, key.ptr, value);
cleanup:
git_buf_free(&key);
return error;
}
int git_submodule_foreach(
git_repository *repo,
int (*callback)(const char *name, void *payload),
void *payload)
#if 0
static int head_oid_for_submodule(
git_oid *oid,
git_repository *owner,
const char *path)
{
int error = 0;
git_oid head_oid;
git_tree *head_tree = NULL, *container_tree = NULL;
unsigned int pos;
const git_tree_entry *entry;
if (git_reference_name_to_oid(&head_oid, owner, GIT_HEAD_FILE) < 0 ||
git_tree_lookup(&head_tree, owner, &head_oid) < 0 ||
git_tree_resolve_path(&container_tree, &pos, head_tree, path) < 0 ||
(entry = git_tree_entry_byindex(container_tree, pos)) == NULL)
{
memset(oid, 0, sizeof(*oid));
error = GIT_ENOTFOUND;
}
else {
git_oid_cpy(oid, &entry->oid);
}
git_tree_free(head_tree);
git_tree_free(container_tree);
return error;
}
int git_submodule_status(
unsigned int *status,
git_oid *head,
git_submodule *sm,
git_submodule_ignore_t ignore)
{
int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
seen._cmp = submodule_cmp;
const char *workdir;
git_repository *owner, *sm_repo = NULL;
git_oid owner_head, sm_head;
if ((error = load_submodule_config(repo)) < 0)
return error;
assert(submodule && status);
git_strmap_foreach_value(repo->submodules, sm, {
/* usually the following will not come into play */
if (sm->refcount > 1) {
if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
break;
if (head == NULL)
head = &sm_head;
owner = submodule->owner;
workdir = git_repository_workdir(owner);
if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)
ignore = sm->ignore;
/* if this is a bare repo or the submodule dir has no .git yet,
* then it is not checked out and we'll just return index data.
*/
if (!workdir || (sm->flags & GIT_SUBMODULE_FLAG__HAS_DOTGIT) == 0) {
*status = GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT;
if (sm->index_oid_valid)
git_oid_cpy(head, &sm->index_oid);
else
memset(head, 0, sizeof(git_oid));
if (git_oid_iszero(head)) {
if (sm->url)
*status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE;
} else if (!sm->url) {
*status = GIT_SUBMODULE_STATUS_DELETED_SUBMODULE;
}
if ((error = callback(sm->name, payload)) < 0)
break;
});
return 0;
}
git_vector_free(&seen);
/* look up submodule path in repo head to find if new or deleted */
if ((error = head_oid_for_submodule(&owner_head, owner, sm->path)) < 0) {
*status = GIT_SUBMODULE_STATUS_NEW_SUBMODULE;
/* ??? */
}
if (ignore == GIT_SUBMODULE_IGNORE_ALL) {
*status = GIT_SUBMODULE_STATUS_CLEAN;
git_oid_cpy(head, &sm->oid);
return 0;
}
if ((error = git_submodule_open(&sm_repo, sm)) < 0)
return error;
if ((error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE)) < 0)
goto cleanup;
if (ignore == GIT_SUBMODULE_IGNORE_DIRTY &&
git_oid_cmp(head, &sm->oid) == 0)
{
*status = GIT_SUBMODULE_STATUS_CLEAN;
return 0;
}
/* look up submodule oid from index in repo to find if new commits or missing commits */
/* run a short status to find if modified or untracked content */
#define GIT_SUBMODULE_STATUS_NEW_SUBMODULE (1u << 2)
#define GIT_SUBMODULE_STATUS_DELETED_SUBMODULE (1u << 3)
#define GIT_SUBMODULE_STATUS_NOT_CHECKED_OUT (1u << 4)
#define GIT_SUBMODULE_STATUS_NEW_COMMITS (1u << 5)
#define GIT_SUBMODULE_STATUS_MISSING_COMMITS (1u << 6)
#define GIT_SUBMODULE_STATUS_MODIFIED_CONTENT (1u << 7)
#define GIT_SUBMODULE_STATUS_UNTRACKED_CONTENT (1u << 8)
cleanup:
git_repository_free(sm_repo);
git_tree_free(owner_tree);
return error;
}
int git_submodule_lookup(
git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
int git_submodule_status_for_path(
unsigned int *status,
git_oid *head,
git_repository *repo,
const char *name) /* trailing slash is allowed */
const char *submodule_path,
git_submodule_ignore_t ignore)
{
khiter_t pos;
int error;
git_submodule *sm;
const char *workdir;
git_buf path = GIT_BUF_INIT;
git_oid owner_head;
assert(repo && submodule_path && status);
if ((error = git_submodule_lookup(&sm, repo, submodule_path)) == 0)
return git_submodule_status(status, head, sm, ignore);
/* if 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;
}
if (load_submodule_config(repo) < 0)
/* submodule was not found - let's see what we can determine about it */
workdir = git_repository_workdir(repo);
if (error != GIT_ENOTFOUND || !workdir) {
*status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE;
return error;
}
giterr_clear();
error = 0;
/* figure out if this is NEW, NOT_CHECKED_OUT, or what */
if (git_buf_joinpath(&path, workdir, submodule_path) < 0)
return -1;
pos = git_strmap_lookup_index(repo->submodules, name);
if (!git_strmap_valid_index(repo->submodules, pos))
return GIT_ENOTFOUND;
if (git_path_contains(&path, DOT_GIT)) {
git_repository *sm_repo;
if (sm_ptr)
*sm_ptr = git_strmap_value_at(repo->submodules, pos);
*status = GIT_SUBMODULE_STATUS_UNTRACKED_SUBMODULE;
return 0;
/* only bother look up head if it was non-NULL */
if (head != NULL &&
!(error = git_repository_open(&sm_repo, path.ptr)))
{
error = git_reference_name_to_oid(head, sm_repo, GIT_HEAD_FILE);
git_repository_free(sm_repo);
}
} else
*status = GIT_SUBMODULE_STATUS_NOT_A_SUBMODULE;
git_buf_free(&path);
return error;
}
#endif
/*
* Copyright (C) 2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_submodule_h__
#define INCLUDE_submodule_h__
/* Notes:
*
* Submodule information can be in four places: the index, the config files
* (both .git/config and .gitmodules), the HEAD tree, and the working
* directory.
*
* In the index:
* - submodule is found by path
* - may be missing, present, or of the wrong type
* - will have an oid if present
*
* In the HEAD tree:
* - submodule is found by path
* - may be missing, present, or of the wrong type
* - will have an oid if present
*
* In the config files:
* - submodule is found by submodule "name" which is usually the path
* - may be missing or present
* - will have a name, path, url, and other properties
*
* In the working directory:
* - submodule is found by path
* - may be missing, an empty directory, a checked out directory,
* or of the wrong type
* - if checked out, will have a HEAD oid
* - if checked out, will have git history that can be used to compare oids
* - if checked out, may have modified files and/or untracked files
*/
/**
* Description of submodule
*
* This record describes a submodule found in a repository. There should be
* an entry for every submodule found in the HEAD and index, and for every
* submodule described in .gitmodules. The fields are as follows:
*
* - `owner` is the git_repository containing this submodule
* - `name` is the name of the submodule from .gitmodules.
* - `path` is the path to the submodule from the repo root. It is almost
* always the same as `name`.
* - `url` is the url for the submodule.
* - `tree_oid` is the SHA1 for the submodule path in the repo HEAD.
* - `index_oid` is the SHA1 for the submodule recorded in the index.
* - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule.
* - `update` is a git_submodule_update_t value - see gitmodules(5) update.
* - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.
* - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.
* - `refcount` tracks how many hashmap entries there are for this submodule.
* It only comes into play if the name and path of the submodule differ.
* - `flags` is for internal use, tracking where this submodule has been
* found (head, index, config, workdir) and other misc info about it.
*
* If the submodule has been added to .gitmodules but not yet git added,
* then the `index_oid` will be valid and zero. If the submodule has been
* deleted, but the delete has not been committed yet, then the `index_oid`
* will be set, but the `url` will be NULL.
*/
struct git_submodule {
git_repository *owner;
char *name;
char *path; /* important: may point to same string data as "name" */
char *url;
uint32_t flags;
git_oid head_oid;
git_oid index_oid;
git_oid wd_oid;
/* information from config */
git_submodule_update_t update;
git_submodule_update_t update_default;
git_submodule_ignore_t ignore;
git_submodule_ignore_t ignore_default;
int fetch_recurse;
/* internal information */
int refcount;
};
/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
#define GIT_SUBMODULE_STATUS__WD_SCANNED (1u << 15)
#define GIT_SUBMODULE_STATUS__HEAD_OID_VALID (1u << 16)
#define GIT_SUBMODULE_STATUS__INDEX_OID_VALID (1u << 17)
#define GIT_SUBMODULE_STATUS__WD_OID_VALID (1u << 18)
#define GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES (1u << 19)
#endif
......@@ -3,24 +3,17 @@
#include "path.h"
#include "posix.h"
#include "status_helpers.h"
#include "../submodule/submodule_helpers.h"
static git_repository *g_repo = NULL;
void test_status_submodules__initialize(void)
{
git_buf modpath = GIT_BUF_INIT;
g_repo = cl_git_sandbox_init("submodules");
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_buf_sets(&modpath, git_repository_workdir(g_repo)));
cl_assert(git_path_dirname_r(&modpath, modpath.ptr) >= 0);
cl_git_pass(git_buf_joinpath(&modpath, modpath.ptr, "testrepo.git\n"));
p_rename("submodules/gitmodules", "submodules/.gitmodules");
cl_git_append2file("submodules/.gitmodules", modpath.ptr);
git_buf_free(&modpath);
rewrite_gitmodules(git_repository_workdir(g_repo));
p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git");
}
......@@ -28,6 +21,7 @@ void test_status_submodules__initialize(void)
void test_status_submodules__cleanup(void)
{
cl_git_sandbox_cleanup();
cl_fixture_cleanup("testrepo.git");
}
void test_status_submodules__api(void)
......@@ -40,8 +34,8 @@ void test_status_submodules__api(void)
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
cl_assert(sm != NULL);
cl_assert_equal_s("testrepo", sm->name);
cl_assert_equal_s("testrepo", sm->path);
cl_assert_equal_s("testrepo", git_submodule_name(sm));
cl_assert_equal_s("testrepo", git_submodule_path(sm));
}
void test_status_submodules__0(void)
......
#include "clar_libgit2.h"
#include "submodule_helpers.h"
#include "posix.h"
static git_repository *g_repo = NULL;
void test_submodule_lookup__initialize(void)
{
g_repo = cl_git_sandbox_init("submod2");
cl_fixture_sandbox("submod2_target");
p_rename("submod2_target/.gitted", "submod2_target/.git");
/* must create submod2_target before rewrite so prettify will work */
rewrite_gitmodules(git_repository_workdir(g_repo));
p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git");
}
void test_submodule_lookup__cleanup(void)
{
cl_git_sandbox_cleanup();
cl_fixture_cleanup("submod2_target");
}
void test_submodule_lookup__simple_lookup(void)
{
git_submodule *sm;
/* lookup existing */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_assert(sm);
/* lookup pending change in .gitmodules that is not in HEAD */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_assert(sm);
/* lookup git repo subdir that is not added as submodule */
cl_assert(git_submodule_lookup(&sm, g_repo, "not_submodule") == GIT_EEXISTS);
/* lookup existing directory that is not a submodule */
cl_assert(git_submodule_lookup(&sm, g_repo, "just_a_dir") == GIT_ENOTFOUND);
/* lookup existing file that is not a submodule */
cl_assert(git_submodule_lookup(&sm, g_repo, "just_a_file") == GIT_ENOTFOUND);
/* lookup non-existent item */
cl_assert(git_submodule_lookup(&sm, g_repo, "no_such_file") == GIT_ENOTFOUND);
}
void test_submodule_lookup__accessors(void)
{
git_submodule *sm;
const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0";
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_assert(git_submodule_owner(sm) == g_repo);
cl_assert_equal_s("sm_unchanged", git_submodule_name(sm));
cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0);
cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0);
cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_wd_oid(sm), oid) == 0);
cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE);
cl_assert(git_submodule_update(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_assert_equal_s("sm_changed_head", git_submodule_name(sm));
cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_wd_oid(sm),
"3d9386c507f6b093471a3e324085657a3c2b4247") == 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_assert_equal_s("sm_added_and_uncommited", git_submodule_name(sm));
cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0);
cl_assert(git_submodule_head_oid(sm) == NULL);
cl_assert(git_oid_streq(git_submodule_wd_oid(sm), oid) == 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
cl_assert_equal_s("sm_missing_commits", git_submodule_name(sm));
cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0);
cl_assert(git_oid_streq(git_submodule_wd_oid(sm),
"5e4963595a9774b90524d35a807169049de8ccad") == 0);
}
typedef struct {
int count;
} sm_lookup_data;
static int sm_lookup_cb(git_submodule *sm, const char *name, void *payload)
{
sm_lookup_data *data = payload;
data->count += 1;
cl_assert_equal_s(git_submodule_name(sm), name);
return 0;
}
void test_submodule_lookup__foreach(void)
{
sm_lookup_data data;
memset(&data, 0, sizeof(data));
cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data));
cl_assert_equal_i(7, data.count);
}
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
static git_repository *g_repo = NULL;
#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git"
#define SM_LIBGIT2 "sm_libgit2"
#define SM_LIBGIT2B "sm_libgit2b"
void test_submodule_modify__initialize(void)
{
g_repo = cl_git_sandbox_init("submod2");
cl_fixture_sandbox("submod2_target");
p_rename("submod2_target/.gitted", "submod2_target/.git");
/* must create submod2_target before rewrite so prettify will work */
rewrite_gitmodules(git_repository_workdir(g_repo));
p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git");
}
void test_submodule_modify__cleanup(void)
{
cl_git_sandbox_cleanup();
cl_fixture_cleanup("submod2_target");
}
void test_submodule_modify__add(void)
{
git_submodule *sm;
git_config *cfg;
const char *s;
/* re-add existing submodule */
cl_assert(
git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1) ==
GIT_EEXISTS );
/* add a submodule using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2, 1)
);
cl_assert(git_path_isfile("submod2/" SM_LIBGIT2 "/.git"));
cl_assert(git_path_isdir("submod2/.git/modules"));
cl_assert(git_path_isdir("submod2/.git/modules/" SM_LIBGIT2));
cl_assert(git_path_isfile("submod2/.git/modules/" SM_LIBGIT2 "/HEAD"));
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(
git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2 ".url"));
cl_assert_equal_s(s, SM_LIBGIT2_URL);
git_config_free(cfg);
/* add a submodule not using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2B, 0)
);
cl_assert(git_path_isdir("submod2/" SM_LIBGIT2B "/.git"));
cl_assert(git_path_isfile("submod2/" SM_LIBGIT2B "/.git/HEAD"));
cl_assert(!git_path_exists("submod2/.git/modules/" SM_LIBGIT2B));
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(
git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2B ".url"));
cl_assert_equal_s(s, SM_LIBGIT2_URL);
git_config_free(cfg);
}
static int delete_one_config(
const char *var_name, const char *value, void *payload)
{
git_config *cfg = payload;
GIT_UNUSED(value);
return git_config_delete(cfg, var_name);
}
static int init_one_submodule(
git_submodule *sm, const char *name, void *payload)
{
GIT_UNUSED(name);
GIT_UNUSED(payload);
return git_submodule_init(sm, false);
}
void test_submodule_modify__init(void)
{
git_config *cfg;
const char *str;
/* erase submodule data from .git/config */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(
git_config_foreach_match(cfg, "submodule\\..*", delete_one_config, cfg));
git_config_free(cfg);
/* confirm no submodule data in config */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url"));
cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url"));
cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url"));
git_config_free(cfg);
/* call init and see that settings are copied */
cl_git_pass(git_submodule_foreach(g_repo, init_one_submodule, NULL));
git_submodule_reload_all(g_repo);
/* confirm submodule data in config */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url"));
cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url"));
cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url"));
cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
git_config_free(cfg);
}
static int sync_one_submodule(
git_submodule *sm, const char *name, void *payload)
{
GIT_UNUSED(name);
GIT_UNUSED(payload);
return git_submodule_sync(sm);
}
void test_submodule_modify__sync(void)
{
git_submodule *sm1, *sm2, *sm3;
git_config *cfg;
const char *str;
#define SM1 "sm_unchanged"
#define SM2 "sm_changed_head"
#define SM3 "sm_added_and_uncommited"
/* look up some submodules */
cl_git_pass(git_submodule_lookup(&sm1, g_repo, SM1));
cl_git_pass(git_submodule_lookup(&sm2, g_repo, SM2));
cl_git_pass(git_submodule_lookup(&sm3, g_repo, SM3));
/* At this point, the .git/config URLs for the submodules have
* not be rewritten with the absolute paths (although the
* .gitmodules have. Let's confirm that they DO NOT match
* yet, then we can do a sync to make them match...
*/
/* check submodule info does not match before sync */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url"));
cl_assert(strcmp(git_submodule_url(sm1), str) != 0);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url"));
cl_assert(strcmp(git_submodule_url(sm2), str) != 0);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url"));
cl_assert(strcmp(git_submodule_url(sm3), str) != 0);
git_config_free(cfg);
/* sync all the submodules */
cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL));
/* check that submodule config is updated */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url"));
cl_assert_equal_s(git_submodule_url(sm1), str);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url"));
cl_assert_equal_s(git_submodule_url(sm2), str);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url"));
cl_assert_equal_s(git_submodule_url(sm3), str);
git_config_free(cfg);
}
void test_submodule_modify__edit_and_save(void)
{
git_submodule *sm1, *sm2;
char *old_url;
git_submodule_ignore_t old_ignore;
git_submodule_update_t old_update;
git_repository *r2;
cl_git_pass(git_submodule_lookup(&sm1, g_repo, "sm_changed_head"));
old_url = git__strdup(git_submodule_url(sm1));
/* modify properties of submodule */
cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL));
old_ignore = git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED);
old_update = git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE);
cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
/* revert without saving (and confirm setters return old value) */
cl_git_pass(git_submodule_set_url(sm1, old_url));
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED,
(int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE,
(int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT));
/* check that revert was successful */
cl_assert_equal_s(old_url, git_submodule_url(sm1));
cl_assert_equal_i((int)old_ignore, (int)git_submodule_ignore(sm1));
cl_assert_equal_i((int)old_update, (int)git_submodule_update(sm1));
/* modify properties of submodule (again) */
cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL));
git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED);
git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE);
/* call save */
cl_git_pass(git_submodule_save(sm1));
/* attempt to "revert" values */
git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT);
git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT);
/* but ignore and update should NOT revert because the DEFAULT
* should now be the newly saved value...
*/
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
/* call reload and check that the new values are loaded */
cl_git_pass(git_submodule_reload(sm1));
cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
/* open a second copy of the repo and compare submodule */
cl_git_pass(git_repository_open(&r2, "submod2"));
cl_git_pass(git_submodule_lookup(&sm2, r2, "sm_changed_head"));
cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm2));
cl_assert_equal_i(
(int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm2));
cl_assert_equal_i(
(int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm2));
git_repository_free(r2);
}
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
static git_repository *g_repo = NULL;
void test_submodule_status__initialize(void)
{
g_repo = cl_git_sandbox_init("submod2");
cl_fixture_sandbox("submod2_target");
p_rename("submod2_target/.gitted", "submod2_target/.git");
/* must create submod2_target before rewrite so prettify will work */
rewrite_gitmodules(git_repository_workdir(g_repo));
p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git");
}
void test_submodule_status__cleanup(void)
{
cl_git_sandbox_cleanup();
cl_fixture_cleanup("submod2_target");
}
void test_submodule_status__unchanged(void)
{
/* make sure it really looks unchanged */
}
void test_submodule_status__changed(void)
{
/* 4 values of GIT_SUBMODULE_IGNORE to check */
/* 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)
*/
}
#include "clar_libgit2.h"
#include "buffer.h"
#include "path.h"
#include "util.h"
#include "posix.h"
#include "submodule_helpers.h"
/* rewrite gitmodules -> .gitmodules
* rewrite the empty or relative urls inside each module
* rename the .gitted directory inside any submodule to .git
*/
void rewrite_gitmodules(const char *workdir)
{
git_buf in_f = GIT_BUF_INIT, out_f = GIT_BUF_INIT, path = GIT_BUF_INIT;
FILE *in, *out;
char line[256];
cl_git_pass(git_buf_joinpath(&in_f, workdir, "gitmodules"));
cl_git_pass(git_buf_joinpath(&out_f, workdir, ".gitmodules"));
cl_assert((in = fopen(in_f.ptr, "r")) != NULL);
cl_assert((out = fopen(out_f.ptr, "w")) != NULL);
while (fgets(line, sizeof(line), in) != NULL) {
char *scan = line;
while (*scan == ' ' || *scan == '\t') scan++;
/* rename .gitted -> .git in submodule directories */
if (git__prefixcmp(scan, "path =") == 0) {
scan += strlen("path =");
while (*scan == ' ') scan++;
git_buf_joinpath(&path, workdir, scan);
git_buf_rtrim(&path);
git_buf_joinpath(&path, path.ptr, ".gitted");
if (!git_buf_oom(&path) && p_access(path.ptr, F_OK) == 0) {
git_buf_joinpath(&out_f, workdir, scan);
git_buf_rtrim(&out_f);
git_buf_joinpath(&out_f, out_f.ptr, ".git");
if (!git_buf_oom(&out_f))
p_rename(path.ptr, out_f.ptr);
}
}
/* copy non-"url =" lines verbatim */
if (git__prefixcmp(scan, "url =") != 0) {
fputs(line, out);
continue;
}
/* convert relative URLs in "url =" lines */
scan += strlen("url =");
while (*scan == ' ') scan++;
if (*scan == '.') {
git_buf_joinpath(&path, workdir, scan);
git_buf_rtrim(&path);
} else if (!*scan || *scan == '\n') {
git_buf_joinpath(&path, workdir, "../testrepo.git");
} else {
fputs(line, out);
continue;
}
git_path_prettify(&path, path.ptr, NULL);
git_buf_putc(&path, '\n');
cl_assert(!git_buf_oom(&path));
fwrite(line, scan - line, sizeof(char), out);
fputs(path.ptr, out);
}
fclose(in);
fclose(out);
cl_must_pass(p_unlink(in_f.ptr));
git_buf_free(&in_f);
git_buf_free(&out_f);
git_buf_free(&path);
}
extern void rewrite_gitmodules(const char *workdir);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment