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 { ...@@ -54,6 +54,7 @@ typedef enum {
GITERR_TREE, GITERR_TREE,
GITERR_INDEXER, GITERR_INDEXER,
GITERR_SSL, GITERR_SSL,
GITERR_SUBMODULE,
} git_error_t; } git_error_t;
/** /**
......
...@@ -20,54 +20,169 @@ ...@@ -20,54 +20,169 @@
*/ */
GIT_BEGIN_DECL 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 { typedef enum {
GIT_SUBMODULE_UPDATE_DEFAULT = -1,
GIT_SUBMODULE_UPDATE_CHECKOUT = 0, GIT_SUBMODULE_UPDATE_CHECKOUT = 0,
GIT_SUBMODULE_UPDATE_REBASE = 1, GIT_SUBMODULE_UPDATE_REBASE = 1,
GIT_SUBMODULE_UPDATE_MERGE = 2 GIT_SUBMODULE_UPDATE_MERGE = 2,
GIT_SUBMODULE_UPDATE_NONE = 3
} git_submodule_update_t; } 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 { typedef enum {
GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */ GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */
GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */ GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */
GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */ GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */
} git_submodule_ignore_t; } 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 * - The submodule is not mentioned in the HEAD, the index, and the config,
* should be an entry for every submodule found in the HEAD and for * but does "exist" in the working directory (i.e. there is a subdirectory
* every submodule described in .gitmodules. The fields are as follows: * 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. * The submodule object is owned by the containing repo and will be freed
* - `path` is the path to the submodule from the repo working directory. * when the repo is freed. The caller need not free the submodule.
* 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.
* *
* If the submodule has been added to .gitmodules but not yet git added, * @param submodule Pointer to submodule description object pointer..
* then the `oid` will be zero. If the submodule has been deleted, but * @param repo The repository.
* the delete has not been committed yet, then the `oid` will be set, but * @param name The name of the submodule. Trailing slashes will be ignored.
* the `url` will be NULL. * @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 { GIT_EXTERN(int) git_submodule_lookup(
char *name; git_submodule **submodule,
char *path; git_repository *repo,
char *url; const char *name);
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;
/** /**
* 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 repo The repository
* @param callback Function to be called with the name of each submodule. * @param callback Function to be called with the name of each submodule.
...@@ -77,26 +192,286 @@ typedef struct { ...@@ -77,26 +192,286 @@ typedef struct {
*/ */
GIT_EXTERN(int) git_submodule_foreach( GIT_EXTERN(int) git_submodule_foreach(
git_repository *repo, git_repository *repo,
int (*callback)(const char *name, void *payload), int (*callback)(git_submodule *sm, const char *name, void *payload),
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 does "git submodule add" up to the fetch and checkout of the
* this returns a structure describing the submodule. If the submodule * submodule contents. It preps a new submodule, creates an entry in
* does not exist, this will return GIT_ENOTFOUND and set the submodule * .gitmodules and creates an empty initialized repository either at the
* pointer to NULL. * 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.. * To fully emulate "git submodule add" call this function, then open the
* @param repo The repository. * submodule repo and perform the clone step as needed. Lastly, call
* @param name The name of the submodule. Trailing slashes will be ignored. * `git_submodule_add_finalize` to wrap up adding the new submodule and
* @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error * .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_submodule **submodule,
git_repository *repo, 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 GIT_END_DECL
......
...@@ -253,11 +253,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) ...@@ -253,11 +253,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
char *tmp = NULL; char *tmp = NULL;
git__free(key); git__free(key);
if (existing->next != NULL) { if (existing->next != NULL) {
giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
return -1; 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) { if (value) {
tmp = git__strdup(value); tmp = git__strdup(value);
GITERR_CHECK_ALLOC(tmp); GITERR_CHECK_ALLOC(tmp);
......
...@@ -19,12 +19,24 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) ...@@ -19,12 +19,24 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg)
cfg->free(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_INLINE(int) git_config_file_set_string(
git_config_file *cfg, const char *name, const char *value) git_config_file *cfg, const char *name, const char *value)
{ {
return cfg->set(cfg, name, 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_INLINE(int) git_config_file_foreach(
git_config_file *cfg, git_config_file *cfg,
int (*fn)(const char *key, const char *value, void *data), int (*fn)(const char *key, const char *value, void *data),
......
...@@ -530,7 +530,7 @@ static int maybe_modified( ...@@ -530,7 +530,7 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
return -1; return -1;
else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
else { else {
/* TODO: support other GIT_SUBMODULE_IGNORE values */ /* TODO: support other GIT_SUBMODULE_IGNORE values */
......
...@@ -17,18 +17,24 @@ ...@@ -17,18 +17,24 @@
#include "config_file.h" #include "config_file.h"
#include "config.h" #include "config.h"
#include "repository.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[] = { static git_cvar_map _sm_update_map[] = {
{GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
{GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, {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[] = { static git_cvar_map _sm_ignore_map[] = {
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, {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) 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) ...@@ -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); 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)); git_submodule *sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL) if (sm == NULL)
...@@ -69,80 +791,126 @@ static git_submodule *submodule_alloc(const char *name) ...@@ -69,80 +791,126 @@ static git_submodule *submodule_alloc(const char *name)
return NULL; 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) git_submodule *sm = submodule_lookup_or_create(repo, entry->path, NULL);
return;
sm->refcount -= decr; if (!sm)
return -1;
if (sm->refcount == 0) { if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
if (sm->name != sm->path) sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
git__free(sm->path); return 0;
git__free(sm->name);
git__free(sm->url);
git__free(sm);
} }
}
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)) git_oid_cpy(&sm->index_oid, &entry->oid);
sm = git_strmap_value_at(smcfg, pos); sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
else
sm = submodule_alloc(entry->path);
git_oid_cpy(&sm->oid, &entry->oid); return submodule_update_map(repo, sm, sm->path);
}
if (strcmp(sm->path, entry->path) != 0) { static int submodule_load_from_head(
if (sm->path != sm->name) { git_repository *repo, const char *path, const git_oid *oid)
git__free(sm->path); {
sm->path = sm->name; git_submodule *sm = submodule_lookup_or_create(repo, path, NULL);
}
sm->path = git__strdup(entry->path);
if (!sm->path)
goto fail;
}
git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); if (!sm)
if (error < 0) return -1;
goto fail;
sm->refcount++;
if (old_sm && ((git_submodule *)old_sm) != sm) { sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
/* TODO: log warning about multiple entrys for same submodule path */
submodule_release(old_sm, 1);
}
return 0; git_oid_cpy(&sm->head_oid, oid);
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
fail: return submodule_update_map(repo, sm, sm->path);
submodule_release(sm, 0);
return -1;
} }
static int submodule_from_config( static int submodule_load_from_config(
const char *key, const char *value, void *data) 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 *namestart;
const char *property; const char *property;
git_buf name = GIT_BUF_INIT; git_buf name = GIT_BUF_INIT;
git_submodule *sm; git_submodule *sm;
void *old_sm = NULL; void *old_sm = NULL;
bool is_path; bool is_path;
khiter_t pos;
int error; int error;
if (git__prefixcmp(key, "submodule.") != 0) if (git__prefixcmp(key, "submodule.") != 0)
...@@ -153,21 +921,17 @@ static int submodule_from_config( ...@@ -153,21 +921,17 @@ static int submodule_from_config(
if (property == NULL) if (property == NULL)
return 0; return 0;
property++; property++;
is_path = (strcmp(property, "path") == 0); is_path = (strcasecmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0) if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1; return -1;
pos = git_strmap_lookup_index(smcfg, name.ptr); sm = submodule_lookup_or_create(repo, name.ptr, is_path ? value : NULL);
if (!git_strmap_valid_index(smcfg, pos) && is_path)
pos = git_strmap_lookup_index(smcfg, value);
if (!git_strmap_valid_index(smcfg, pos))
sm = submodule_alloc(name.ptr);
else
sm = git_strmap_value_at(smcfg, pos);
if (!sm) if (!sm)
goto fail; goto fail;
sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
if (strcmp(sm->name, name.ptr) != 0) { if (strcmp(sm->name, name.ptr) != 0) {
assert(sm->path == sm->name); assert(sm->path == sm->name);
sm->name = git_buf_detach(&name); sm->name = git_buf_detach(&name);
...@@ -177,7 +941,7 @@ static int submodule_from_config( ...@@ -177,7 +941,7 @@ static int submodule_from_config(
goto fail; goto fail;
sm->refcount++; 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); assert(sm->path == sm->name);
sm->path = git__strdup(value); sm->path = git__strdup(value);
if (sm->path == NULL) if (sm->path == NULL)
...@@ -195,19 +959,23 @@ static int submodule_from_config( ...@@ -195,19 +959,23 @@ static int submodule_from_config(
submodule_release(old_sm, 1); 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) if (is_path)
return 0; return 0;
/* copy other properties into submodule entry */ /* copy other properties into submodule entry */
if (strcmp(property, "url") == 0) { if (strcasecmp(property, "url") == 0) {
if (sm->url) { if (sm->url) {
git__free(sm->url); git__free(sm->url);
sm->url = NULL; sm->url = NULL;
} }
if ((sm->url = git__strdup(value)) == NULL) if (value != NULL && (sm->url = git__strdup(value)) == NULL)
goto fail; goto fail;
} }
else if (strcmp(property, "update") == 0) { else if (strcasecmp(property, "update") == 0) {
int val; int val;
if (git_config_lookup_map_value( if (git_config_lookup_map_value(
_sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
...@@ -215,16 +983,16 @@ static int submodule_from_config( ...@@ -215,16 +983,16 @@ static int submodule_from_config(
"Invalid value for submodule update property: '%s'", value); "Invalid value for submodule update property: '%s'", value);
goto fail; 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) { if (git__parse_bool(&sm->fetch_recurse, value) < 0) {
giterr_set(GITERR_INVALID, giterr_set(GITERR_INVALID,
"Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value);
goto fail; goto fail;
} }
} }
else if (strcmp(property, "ignore") == 0) { else if (strcasecmp(property, "ignore") == 0) {
int val; int val;
if (git_config_lookup_map_value( if (git_config_lookup_map_value(
_sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {
...@@ -232,7 +1000,7 @@ static int submodule_from_config( ...@@ -232,7 +1000,7 @@ static int submodule_from_config(
"Invalid value for submodule ignore property: '%s'", value); "Invalid value for submodule ignore property: '%s'", value);
goto fail; goto fail;
} }
sm->ignore = (git_submodule_ignore_t)val; sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
} }
/* ignore other unknown submodule properties */ /* ignore other unknown submodule properties */
...@@ -244,144 +1012,471 @@ fail: ...@@ -244,144 +1012,471 @@ fail:
return -1; 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; int error;
git_index *index; git_iterator *i;
unsigned int i, max_i; const git_index_entry *entry;
git_oid gitmodules_oid;
git_strmap *smcfg;
struct git_config_file *mods = NULL;
if (repo->submodules) if ((error = git_iterator_for_index(&i, repo)) < 0)
return 0; return error;
/* submodule data is kept in a hashtable with each submodule stored error = git_iterator_current(i, &entry);
* 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);
/* scan index for gitmodules (and .gitmodules entry) */ while (!error && entry != NULL) {
if ((error = git_repository_index__weakptr(&index, repo)) < 0)
goto cleanup;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
max_i = git_index_entrycount(index);
for (i = 0; i < max_i; i++) {
git_index_entry *entry = git_index_get(index, i);
if (S_ISGITLINK(entry->mode)) { if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_from_entry(smcfg, entry)) < 0) error = submodule_load_from_index(repo, entry);
goto cleanup; if (error < 0)
} break;
else if (strcmp(entry->path, ".gitmodules") == 0) } else if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
git_oid_cpy(&gitmodules_oid, &entry->oid); git_oid_cpy(gitmodules_oid, &entry->oid);
error = git_iterator_advance(i, &entry);
} }
/* load .gitmodules from workdir if it exists */ git_iterator_free(i);
if (git_repository_workdir(repo) != NULL) {
/* look in workdir for .gitmodules */ return error;
git_buf path = GIT_BUF_INIT; }
if (!git_buf_joinpath(
&path, git_repository_workdir(repo), ".gitmodules") && static int load_submodule_config_from_head(
git_path_isfile(path.ptr)) git_repository *repo, git_oid *gitmodules_oid)
{ {
if (!(error = git_config_file__ondisk(&mods, path.ptr))) int error;
error = git_config_file_open(mods); 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 (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { /* TODO: Retrieve .gitmodules content from ODB */
/* TODO: is it worth loading gitmodules from object cache? */
/* 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 */ /* add submodule information from index */
if (!error && mods != NULL)
error = git_config_file_foreach(mods, submodule_from_config, smcfg); 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 (!git_repository_is_bare(repo))
if (!error) error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);
repo->submodules = smcfg;
cleanup: cleanup:
git_buf_free(&path);
if (mods != NULL) if (mods != NULL)
git_config_file_free(mods); git_config_file_free(mods);
if (error) if (error)
git_strmap_free(smcfg); git_submodule_config_free(repo);
return error; 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; int error;
git_submodule *sm; 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) if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
return; giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD cannot be resolved");
error = GIT_ENOTFOUND;
goto cleanup;
}
git_strmap_foreach_value(smcfg, sm, { if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
submodule_release(sm,1); giterr_set(GITERR_SUBMODULE,
}); "Cannot resolve relative URL when HEAD is not symbolic");
git_strmap_free(smcfg); 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( #if 0
git_repository *repo,
int (*callback)(const char *name, void *payload), static int head_oid_for_submodule(
void *payload) 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; int error;
git_submodule *sm; const char *workdir;
git_vector seen = GIT_VECTOR_INIT; git_repository *owner, *sm_repo = NULL;
seen._cmp = submodule_cmp; git_oid owner_head, sm_head;
if ((error = load_submodule_config(repo)) < 0) assert(submodule && status);
return error;
git_strmap_foreach_value(repo->submodules, sm, { if (head == NULL)
/* usually the following will not come into play */ head = &sm_head;
if (sm->refcount > 1) {
if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) owner = submodule->owner;
continue; workdir = git_repository_workdir(owner);
if ((error = git_vector_insert(&seen, sm)) < 0)
break; 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) return 0;
break; }
});
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; return error;
} }
int git_submodule_lookup( int git_submodule_status_for_path(
git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ unsigned int *status,
git_oid *head,
git_repository *repo, 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; return -1;
pos = git_strmap_lookup_index(repo->submodules, name); if (git_path_contains(&path, DOT_GIT)) {
if (!git_strmap_valid_index(repo->submodules, pos)) git_repository *sm_repo;
return GIT_ENOTFOUND;
if (sm_ptr) *status = GIT_SUBMODULE_STATUS_UNTRACKED_SUBMODULE;
*sm_ptr = git_strmap_value_at(repo->submodules, pos);
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 @@ ...@@ -3,24 +3,17 @@
#include "path.h" #include "path.h"
#include "posix.h" #include "posix.h"
#include "status_helpers.h" #include "status_helpers.h"
#include "../submodule/submodule_helpers.h"
static git_repository *g_repo = NULL; static git_repository *g_repo = NULL;
void test_status_submodules__initialize(void) void test_status_submodules__initialize(void)
{ {
git_buf modpath = GIT_BUF_INIT;
g_repo = cl_git_sandbox_init("submodules"); g_repo = cl_git_sandbox_init("submodules");
cl_fixture_sandbox("testrepo.git"); cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_buf_sets(&modpath, git_repository_workdir(g_repo))); rewrite_gitmodules(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);
p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git");
} }
...@@ -28,6 +21,7 @@ void test_status_submodules__initialize(void) ...@@ -28,6 +21,7 @@ void test_status_submodules__initialize(void)
void test_status_submodules__cleanup(void) void test_status_submodules__cleanup(void)
{ {
cl_git_sandbox_cleanup(); cl_git_sandbox_cleanup();
cl_fixture_cleanup("testrepo.git");
} }
void test_status_submodules__api(void) void test_status_submodules__api(void)
...@@ -40,8 +34,8 @@ 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_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
cl_assert(sm != NULL); cl_assert(sm != NULL);
cl_assert_equal_s("testrepo", sm->name); cl_assert_equal_s("testrepo", git_submodule_name(sm));
cl_assert_equal_s("testrepo", sm->path); cl_assert_equal_s("testrepo", git_submodule_path(sm));
} }
void test_status_submodules__0(void) 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