mirror of
https://git.proxmox.com/git/libgit2
synced 2025-06-17 14:28:08 +00:00
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`.
This commit is contained in:
parent
decff7b4c1
commit
aa13bf05c8
@ -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.
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* - `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.
|
||||
*
|
||||
* 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.
|
||||
* 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).
|
||||
*/
|
||||
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;
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate over all submodules of a repository.
|
||||
* 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:
|
||||
*
|
||||
* - 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
GIT_EXTERN(int) git_submodule_lookup(
|
||||
git_submodule **submodule,
|
||||
git_repository *repo,
|
||||
const char *name);
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
|
1483
src/submodule.c
1483
src/submodule.c
File diff suppressed because it is too large
Load Diff
94
src/submodule.h
Normal file
94
src/submodule.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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)
|
||||
|
110
tests-clar/submodule/lookup.c
Normal file
110
tests-clar/submodule/lookup.c
Normal file
@ -0,0 +1,110 @@
|
||||
#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);
|
||||
}
|
256
tests-clar/submodule/modify.c
Normal file
256
tests-clar/submodule/modify.c
Normal file
@ -0,0 +1,256 @@
|
||||
#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);
|
||||
}
|
44
tests-clar/submodule/status.c
Normal file
44
tests-clar/submodule/status.c
Normal file
@ -0,0 +1,44 @@
|
||||
#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)
|
||||
*/
|
||||
}
|
||||
|
84
tests-clar/submodule/submodule_helpers.c
Normal file
84
tests-clar/submodule/submodule_helpers.c
Normal file
@ -0,0 +1,84 @@
|
||||
#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);
|
||||
}
|
2
tests-clar/submodule/submodule_helpers.h
Normal file
2
tests-clar/submodule/submodule_helpers.h
Normal file
@ -0,0 +1,2 @@
|
||||
extern void rewrite_gitmodules(const char *workdir);
|
||||
|
Loading…
Reference in New Issue
Block a user