Commit 09fad506 by Vicent Martí

Merge pull request #852 from arrbee/submodule-extensions

Submodule extensions
parents 091c742a 97a17e4e
...@@ -391,6 +391,21 @@ GIT_EXTERN(int) git_diff_print_patch( ...@@ -391,6 +391,21 @@ GIT_EXTERN(int) git_diff_print_patch(
void *cb_data, void *cb_data,
git_diff_data_fn print_cb); git_diff_data_fn print_cb);
/**
* Query how many diff records are there in a diff list.
*
* You can optionally pass in a `git_delta_t` value if you want a count
* of just entries that match that delta type, or pass -1 for all delta
* records.
*
* @param diff A git_diff_list generated by one of the above functions
* @param delta_t A git_delta_t value to filter the count, or -1 for all records
* @return Count of number of deltas matching delta_t type
*/
GIT_EXTERN(int) git_diff_entrycount(
git_diff_list *diff,
int delta_t);
/**@}*/ /**@}*/
......
...@@ -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;
/** /**
......
...@@ -185,6 +185,8 @@ GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str); ...@@ -185,6 +185,8 @@ GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str);
/** /**
* Check is an oid is all zeros. * Check is an oid is all zeros.
*
* @return 1 if all zeros, 0 otherwise.
*/ */
GIT_EXTERN(int) git_oid_iszero(const git_oid *a); GIT_EXTERN(int) git_oid_iszero(const git_oid *a);
......
...@@ -20,54 +20,153 @@ ...@@ -20,54 +20,153 @@
*/ */
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 * Return codes for submodule status.
*
* A combination of these flags will be returned to describe the status of a
* submodule. Depending on the "ignore" property of the submodule, some of
* the flags may never be returned because they indicate changes that are
* supposed to be ignored.
*
* Submodule info is contained in 4 places: the HEAD tree, the index, config
* files (both .git/config and .gitmodules), and the working directory. Any
* or all of those places might be missing information about the submodule
* depending on what state the repo is in. We consider all four places to
* build the combination of status flags.
*
* There are four values that are not really status, but give basic info
* about what sources of submodule data are available. These will be
* returned even if ignore is set to "ALL".
*
* * IN_HEAD - superproject head contains submodule
* * IN_INDEX - superproject index contains submodule
* * IN_CONFIG - superproject gitmodules has submodule
* * IN_WD - superproject workdir has submodule
*
* The following values will be returned so long as ignore is not "ALL".
*
* * INDEX_ADDED - in index, not in head
* * INDEX_DELETED - in head, not in index
* * INDEX_MODIFIED - index and head don't match
* * WD_UNINITIALIZED - workdir contains empty directory
* * WD_ADDED - in workdir, not index
* * WD_DELETED - in index, not workdir
* * WD_MODIFIED - index and workdir head don't match
*
* The following can only be returned if ignore is "NONE" or "UNTRACKED".
*
* * WD_INDEX_MODIFIED - submodule workdir index is dirty
* * WD_WD_MODIFIED - submodule workdir has modified files
*
* Lastly, the following will only be returned for ignore "NONE".
*
* * WD_UNTRACKED - wd contains untracked files
*/
typedef enum {
GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0),
GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1),
GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2),
GIT_SUBMODULE_STATUS_IN_WD = (1u << 3),
GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4),
GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5),
GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6),
GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7),
GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8),
GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9),
GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10),
GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11),
GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12),
GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),
} git_submodule_status_t;
#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \
(((S) & ~(GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX | \
GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD)) == 0)
/**
* Lookup submodule information by name or path.
*
* Given either the submodule name or path (they are usually the same), this
* returns a structure describing the submodule.
* *
* This record describes a submodule found in a repository. There * There are two expected error scenarios:
* 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. * - The submodule is not mentioned in the HEAD, the index, and the config,
* - `path` is the path to the submodule from the repo working directory. * but does "exist" in the working directory (i.e. there is a subdirectory
* It is almost always the same as `name`. * that is a valid self-contained git repo). In this case, this function
* - `url` is the url for the submodule. * returns GIT_EEXISTS to indicate the the submodule exists but not in a
* - `oid` is the HEAD SHA1 for the submodule. * state where a git_submodule can be instantiated.
* - `update` is a value from above - see gitmodules(5) update. * - The submodule is not mentioned in the HEAD, index, or config and the
* - `ignore` is a value from above - see gitmodules(5) ignore. * working directory doesn't contain a value git repo at that path.
* - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. * There may or may not be anything else at that path, but nothing that
* - `refcount` is for internal use. * looks like a submodule. In this case, this returns GIT_ENOTFOUND.
* *
* If the submodule has been added to .gitmodules but not yet git added, * The submodule object is owned by the containing repo and will be freed
* then the `oid` will be zero. If the submodule has been deleted, but * when the repo is freed. The caller need not free the submodule.
* the delete has not been committed yet, then the `oid` will be set, but *
* the `url` will be NULL. * @param submodule Pointer to submodule description object pointer..
* @param repo The repository.
* @param name The name of the submodule. Trailing slashes will be ignored.
* @return 0 on success, GIT_ENOTFOUND if submodule does not exist,
* GIT_EEXISTS if submodule exists in working directory only, -1 on
* other errors.
*/ */
typedef struct { 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 +176,297 @@ typedef struct { ...@@ -77,26 +176,297 @@ 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).
*
* @param submodule The submodule to finish adding.
*/
GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule);
/**
* Add current submodule HEAD commit to index of superproject.
*
* @param submodule The submodule to add to the index
* @param write_index Boolean if this should immediately write the index
* file. If you pass this as false, you will have to get the
* git_index and explicitly call `git_index_write()` on it to
* save the change.
* @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_submodule_add_to_index(
git_submodule *submodule,
int write_index);
/**
* Write submodule settings to .gitmodules file.
*
* This commits any in-memory changes to the submodule to the gitmodules
* file on disk. You may also be interested in `git_submodule_init()` which
* 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
* set either temporarily or permanently with `git_submodule_set_ignore()`.
*
* @param status Combination of `GIT_SUBMODULE_STATUS` flags
* @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
......
...@@ -144,31 +144,40 @@ int git_buf_puts(git_buf *buf, const char *string) ...@@ -144,31 +144,40 @@ int git_buf_puts(git_buf *buf, const char *string)
int git_buf_puts_escaped( int git_buf_puts_escaped(
git_buf *buf, const char *string, const char *esc_chars, const char *esc_with) git_buf *buf, const char *string, const char *esc_chars, const char *esc_with)
{ {
const char *scan = string; const char *scan;
size_t total = 0, esc_with_len = strlen(esc_with); size_t total = 0, esc_len = strlen(esc_with), count;
while (*scan) { if (!string)
size_t count = strcspn(scan, esc_chars); return 0;
total += count + 1 + esc_with_len;
scan += count + 1; for (scan = string; *scan; ) {
/* count run of non-escaped characters */
count = strcspn(scan, esc_chars);
total += count;
scan += count;
/* count run of escaped characters */
count = strspn(scan, esc_chars);
total += count * (esc_len + 1);
scan += count;
} }
ENSURE_SIZE(buf, buf->size + total + 1); ENSURE_SIZE(buf, buf->size + total + 1);
for (scan = string; *scan; ) { for (scan = string; *scan; ) {
size_t count = strcspn(scan, esc_chars); count = strcspn(scan, esc_chars);
memmove(buf->ptr + buf->size, scan, count); memmove(buf->ptr + buf->size, scan, count);
scan += count; scan += count;
buf->size += count; buf->size += count;
if (*scan) { for (count = strspn(scan, esc_chars); count > 0; --count) {
memmove(buf->ptr + buf->size, esc_with, esc_with_len); /* copy escape sequence */
buf->size += esc_with_len; memmove(buf->ptr + buf->size, esc_with, esc_len);
buf->size += esc_len;
memmove(buf->ptr + buf->size, scan, 1); /* copy character to be escaped */
scan += 1; buf->ptr[buf->size] = *scan;
buf->size += 1; buf->size++;
scan++;
} }
} }
......
...@@ -31,7 +31,7 @@ typedef struct tree_walk_data ...@@ -31,7 +31,7 @@ typedef struct tree_walk_data
git_checkout_opts *opts; git_checkout_opts *opts;
git_repository *repo; git_repository *repo;
git_odb *odb; git_odb *odb;
bool do_symlinks; bool no_symlinks;
} tree_walk_data; } tree_walk_data;
...@@ -48,9 +48,9 @@ static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf, ...@@ -48,9 +48,9 @@ static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf,
/* Create the link */ /* Create the link */
const char *new = git_buf_cstr(&linktarget), const char *new = git_buf_cstr(&linktarget),
*old = git_buf_cstr(fnbuf); *old = git_buf_cstr(fnbuf);
retcode = data->do_symlinks retcode = data->no_symlinks
? p_symlink(new, old) ? git_futils_fake_symlink(new, old)
: git_futils_fake_symlink(new, old); : p_symlink(new, old);
} }
git_buf_free(&linktarget); git_buf_free(&linktarget);
git_blob_free(blob); git_blob_free(blob);
...@@ -176,13 +176,14 @@ int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer ...@@ -176,13 +176,14 @@ int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer
return GIT_ERROR; return GIT_ERROR;
} }
memset(&payload, 0, sizeof(payload));
/* Determine if symlinks should be handled */ /* Determine if symlinks should be handled */
if (!git_repository_config(&cfg, repo)) { if (!git_repository_config__weakptr(&cfg, repo)) {
int temp = true; int temp = true;
if (!git_config_get_bool(&temp, cfg, "core.symlinks")) { if (!git_config_get_bool(&temp, cfg, "core.symlinks")) {
payload.do_symlinks = !!temp; payload.no_symlinks = !temp;
} }
git_config_free(cfg);
} }
stats->total = stats->processed = 0; stats->total = stats->processed = 0;
......
...@@ -195,7 +195,7 @@ static int file_foreach( ...@@ -195,7 +195,7 @@ static int file_foreach(
void *data) void *data)
{ {
diskfile_backend *b = (diskfile_backend *)backend; diskfile_backend *b = (diskfile_backend *)backend;
cvar_t *var; cvar_t *var, *next_var;
const char *key; const char *key;
regex_t regex; regex_t regex;
int result = 0; int result = 0;
...@@ -212,7 +212,9 @@ static int file_foreach( ...@@ -212,7 +212,9 @@ static int file_foreach(
} }
git_strmap_foreach(b->values, key, var, git_strmap_foreach(b->values, key, var,
for (; var != NULL; var = CVAR_LIST_NEXT(var)) { for (; var != NULL; var = next_var) {
next_var = CVAR_LIST_NEXT(var);
/* skip non-matching keys if regexp was provided */ /* skip non-matching keys if regexp was provided */
if (regexp && regexec(&regex, key, 0, NULL, 0) != 0) if (regexp && regexec(&regex, key, 0, NULL, 0) != 0)
continue; continue;
...@@ -253,11 +255,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) ...@@ -253,11 +255,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 */
......
...@@ -718,6 +718,25 @@ int git_diff_print_patch( ...@@ -718,6 +718,25 @@ int git_diff_print_patch(
return error; return error;
} }
int git_diff_entrycount(git_diff_list *diff, int delta_t)
{
int count = 0;
unsigned int i;
git_diff_delta *delta;
assert(diff);
if (delta_t < 0)
return diff->deltas.length;
git_vector_foreach(&diff->deltas, i, delta) {
if (delta->status == (git_delta_t)delta_t)
count++;
}
return count;
}
int git_diff_blobs( int git_diff_blobs(
git_blob *old_blob, git_blob *old_blob,
git_blob *new_blob, git_blob *new_blob,
......
...@@ -110,6 +110,11 @@ void giterr_set_regex(const regex_t *regex, int error_code) ...@@ -110,6 +110,11 @@ void giterr_set_regex(const regex_t *regex, int error_code)
void giterr_clear(void) void giterr_clear(void)
{ {
GIT_GLOBAL->last_error = NULL; GIT_GLOBAL->last_error = NULL;
errno = 0;
#ifdef GIT_WIN32
SetLastError(0);
#endif
} }
const git_error *giterr_last(void) const git_error *giterr_last(void)
......
...@@ -50,6 +50,7 @@ static int lock_file(git_filebuf *file, int flags) ...@@ -50,6 +50,7 @@ static int lock_file(git_filebuf *file, int flags)
if (flags & GIT_FILEBUF_FORCE) if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock); p_unlink(file->path_lock);
else { else {
giterr_clear(); /* actual OS error code just confuses */
giterr_set(GITERR_OS, giterr_set(GITERR_OS,
"Failed to lock file '%s' for writing", file->path_lock); "Failed to lock file '%s' for writing", file->path_lock);
return -1; return -1;
......
...@@ -525,7 +525,9 @@ static int workdir_iterator__advance( ...@@ -525,7 +525,9 @@ static int workdir_iterator__advance(
while ((wf = wi->stack) != NULL) { while ((wf = wi->stack) != NULL) {
next = git_vector_get(&wf->entries, ++wf->index); next = git_vector_get(&wf->entries, ++wf->index);
if (next != NULL) { if (next != NULL) {
if (strcmp(next->path, DOT_GIT "/") == 0) /* match git's behavior of ignoring anything named ".git" */
if (strcmp(next->path, DOT_GIT "/") == 0 ||
strcmp(next->path, DOT_GIT) == 0)
continue; continue;
/* else found a good entry */ /* else found a good entry */
break; break;
...@@ -607,8 +609,8 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) ...@@ -607,8 +609,8 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
wi->entry.path = ps->path; wi->entry.path = ps->path;
/* skip over .git directory */ /* skip over .git entry */
if (strcmp(ps->path, DOT_GIT "/") == 0) if (strcmp(ps->path, DOT_GIT "/") == 0 || strcmp(ps->path, DOT_GIT) == 0)
return workdir_iterator__advance((git_iterator *)wi, NULL); return workdir_iterator__advance((git_iterator *)wi, NULL);
/* if there is an error processing the entry, treat as ignored */ /* if there is an error processing the entry, treat as ignored */
...@@ -629,15 +631,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) ...@@ -629,15 +631,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
/* detect submodules */ /* detect submodules */
if (S_ISDIR(wi->entry.mode)) { if (S_ISDIR(wi->entry.mode)) {
bool is_submodule = git_path_contains(&wi->path, DOT_GIT); int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
bool is_submodule = (res == 0);
/* if there is no .git, still check submodules data */ if (res == GIT_ENOTFOUND)
if (!is_submodule) { giterr_clear();
int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
is_submodule = (res == 0);
if (res == GIT_ENOTFOUND)
giterr_clear();
}
/* if submodule, mark as GITLINK and remove trailing slash */ /* if submodule, mark as GITLINK and remove trailing slash */
if (is_submodule) { if (is_submodule) {
......
...@@ -146,8 +146,13 @@ static int load_workdir(git_repository *repo, git_buf *parent_path) ...@@ -146,8 +146,13 @@ static int load_workdir(git_repository *repo, git_buf *parent_path)
return -1; return -1;
error = git_config_get_string(&worktree, config, "core.worktree"); error = git_config_get_string(&worktree, config, "core.worktree");
if (!error && worktree != NULL) if (!error && worktree != NULL) {
repo->workdir = git__strdup(worktree); error = git_path_prettify_dir(
&worktree_buf, worktree, repo->path_repository);
if (error < 0)
return error;
repo->workdir = git_buf_detach(&worktree_buf);
}
else if (error != GIT_ENOTFOUND) else if (error != GIT_ENOTFOUND)
return error; return error;
else { else {
......
...@@ -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)
...@@ -36,7 +42,7 @@ static kh_inline khint_t str_hash_no_trailing_slash(const char *s) ...@@ -36,7 +42,7 @@ static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
khint_t h; khint_t h;
for (h = 0; *s; ++s) for (h = 0; *s; ++s)
if (s[1] || *s != '/') if (s[1] != '\0' || *s != '/')
h = (h << 5) - h + *s; h = (h << 5) - h + *s;
return h; return h;
...@@ -47,341 +53,1420 @@ static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b) ...@@ -47,341 +53,1420 @@ static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
size_t alen = a ? strlen(a) : 0; size_t alen = a ? strlen(a) : 0;
size_t blen = b ? strlen(b) : 0; size_t blen = b ? strlen(b) : 0;
if (alen && a[alen] == '/') if (alen > 0 && a[alen - 1] == '/')
alen--; alen--;
if (blen && b[blen] == '/') if (blen > 0 && b[blen - 1] == '/')
blen--; blen--;
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 int submodule_get(git_submodule **, git_repository *, const char *, const char *);
static void submodule_release(git_submodule *sm, int decr);
static int submodule_load_from_index(git_repository *, const git_index_entry *);
static int submodule_load_from_head(git_repository*, const char*, const git_oid*);
static int submodule_load_from_config(const char *, const char *, void *);
static int submodule_load_from_wd_lite(git_submodule *, const char *, void *);
static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);
static int submodule_index_status(unsigned int *status, git_submodule *sm);
static int submodule_wd_status(unsigned int *status, git_submodule *sm);
static git_submodule *submodule_alloc(const char *name) static int submodule_cmp(const void *a, const void *b)
{ {
git_submodule *sm = git__calloc(1, sizeof(git_submodule)); return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
if (sm == NULL) }
return sm;
sm->path = sm->name = git__strdup(name);
if (!sm->name) {
git__free(sm);
return NULL;
}
return sm; 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);
} }
static void submodule_release(git_submodule *sm, int decr) /*
* 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 */
{ {
if (!sm) int error;
return; khiter_t pos;
sm->refcount -= decr; assert(repo && name);
if (sm->refcount == 0) { if ((error = load_submodule_config(repo, false)) < 0)
if (sm->name != sm->path) return error;
git__free(sm->path);
git__free(sm->name); pos = git_strmap_lookup_index(repo->submodules, name);
git__free(sm->url);
git__free(sm); 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 int submodule_from_entry( int git_submodule_foreach(
git_strmap *smcfg, git_index_entry *entry) git_repository *repo,
int (*callback)(git_submodule *sm, const char *name, void *payload),
void *payload)
{ {
git_submodule *sm;
void *old_sm;
khiter_t pos;
int error; int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
seen._cmp = submodule_cmp;
pos = git_strmap_lookup_index(smcfg, entry->path); assert(repo && callback);
if (git_strmap_valid_index(smcfg, pos))
sm = git_strmap_value_at(smcfg, pos);
else
sm = submodule_alloc(entry->path);
git_oid_cpy(&sm->oid, &entry->oid); if ((error = load_submodule_config(repo, false)) < 0)
return error;
if (strcmp(sm->path, entry->path) != 0) { git_strmap_foreach_value(repo->submodules, sm, {
if (sm->path != sm->name) { /* Usually the following will not come into play - it just prevents
git__free(sm->path); * us from issuing a callback twice for a submodule where the name
sm->path = sm->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;
} }
sm->path = git__strdup(entry->path);
if (!sm->path)
goto fail;
}
git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);
if (error < 0)
goto fail;
sm->refcount++;
if (old_sm && ((git_submodule *)old_sm) != sm) { if (callback(sm, sm->name, payload)) {
/* TODO: log warning about multiple entrys for same submodule path */ error = GIT_EUSER;
submodule_release(old_sm, 1); break;
} }
});
return 0; git_vector_free(&seen);
fail: return error;
submodule_release(sm, 0);
return -1;
} }
static int submodule_from_config( void git_submodule_config_free(git_repository *repo)
const char *key, const char *value, void *data)
{ {
git_strmap *smcfg = data; git_strmap *smcfg;
const char *namestart;
const char *property;
git_buf name = GIT_BUF_INIT;
git_submodule *sm; git_submodule *sm;
void *old_sm = NULL;
bool is_path;
khiter_t pos;
int error;
if (git__prefixcmp(key, "submodule.") != 0) assert(repo);
return 0;
namestart = key + strlen("submodule."); smcfg = repo->submodules;
property = strrchr(namestart, '.'); repo->submodules = NULL;
if (property == NULL)
return 0;
property++;
is_path = (strcmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0) if (smcfg == NULL)
return -1; return;
pos = git_strmap_lookup_index(smcfg, name.ptr); git_strmap_foreach_value(smcfg, sm, {
if (!git_strmap_valid_index(smcfg, pos) && is_path) submodule_release(sm,1);
pos = git_strmap_lookup_index(smcfg, value); });
if (!git_strmap_valid_index(smcfg, pos)) git_strmap_free(smcfg);
sm = submodule_alloc(name.ptr); }
else
sm = git_strmap_value_at(smcfg, pos);
if (!sm)
goto fail;
if (strcmp(sm->name, name.ptr) != 0) { int git_submodule_add_setup(
assert(sm->path == sm->name); git_submodule **submodule,
sm->name = git_buf_detach(&name); 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;
git_strmap_insert2(smcfg, sm->name, sm, old_sm, error); assert(repo && url && path);
if (error < 0)
goto fail;
sm->refcount++;
}
else if (is_path && strcmp(sm->path, value) != 0) {
assert(sm->path == sm->name);
sm->path = git__strdup(value);
if (sm->path == NULL)
goto fail;
git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); /* see if there is already an entry for this submodule */
if (error < 0)
goto fail;
sm->refcount++;
}
git_buf_free(&name);
if (old_sm && ((git_submodule *)old_sm) != sm) { if (git_submodule_lookup(&sm, repo, path) < 0)
/* TODO: log warning about multiple submodules with same path */ giterr_clear();
submodule_release(old_sm, 1); else {
giterr_set(GITERR_SUBMODULE,
"Attempt to add a submodule that already exists");
return GIT_EEXISTS;
} }
if (is_path) /* resolve parameters */
return 0;
/* copy other properties into submodule entry */ if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {
if (strcmp(property, "url") == 0) { if (!(error = lookup_head_remote(&real_url, repo)))
if (sm->url) { error = git_path_apply_relative(&real_url, url);
git__free(sm->url); } else if (strchr(url, ':') != NULL || url[0] == '/') {
sm->url = NULL; error = git_buf_sets(&real_url, url);
} } else {
if ((sm->url = git__strdup(value)) == NULL) giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
goto fail; error = -1;
}
else if (strcmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
giterr_set(GITERR_INVALID,
"Invalid value for submodule update property: '%s'", value);
goto fail;
}
sm->update = (git_submodule_update_t)val;
} }
else if (strcmp(property, "fetchRecurseSubmodules") == 0) { if (error)
if (git__parse_bool(&sm->fetch_recurse, value) < 0) { goto cleanup;
giterr_set(GITERR_INVALID,
"Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); /* validate and normalize path */
goto fail;
} 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;
} }
else if (strcmp(property, "ignore") == 0) {
int val; /* update .gitmodules */
if (git_config_lookup_map_value(
_sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {
giterr_set(GITERR_INVALID, giterr_set(GITERR_SUBMODULE,
"Invalid value for submodule ignore property: '%s'", value); "Adding submodules to a bare repository is not supported (for now)");
goto fail; return -1;
}
sm->ignore = (git_submodule_ignore_t)val;
} }
/* ignore other unknown submodule properties */
return 0; if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, path)) < 0)
goto cleanup;
fail: if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
submodule_release(sm, 0); (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
git_buf_free(&name); goto cleanup;
return -1;
}
static int load_submodule_config(git_repository *repo) git_buf_clear(&name);
{
int error;
git_index *index;
unsigned int i, max_i;
git_oid gitmodules_oid;
git_strmap *smcfg;
struct git_config_file *mods = NULL;
if (repo->submodules) /* init submodule repository and add origin remote as needed */
return 0;
/* submodule data is kept in a hashtable with each submodule stored error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
* under both its name and its path. These are usually the same, but if (error < 0)
* that is not guaranteed. 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/
*/ */
smcfg = git_strmap_alloc();
GITERR_CHECK_ALLOC(smcfg);
/* scan index for gitmodules (and .gitmodules entry) */ memset(&initopt, 0, sizeof(initopt));
if ((error = git_repository_index__weakptr(&index, repo)) < 0) initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
goto cleanup; GIT_REPOSITORY_INIT_NO_REINIT;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); initopt.origin_url = real_url.ptr;
max_i = git_index_entrycount(index);
for (i = 0; i < max_i; i++) { if (git_path_exists(name.ptr) &&
git_index_entry *entry = git_index_get(index, i); git_path_contains(&name, DOT_GIT))
if (S_ISGITLINK(entry->mode)) { {
if ((error = submodule_from_entry(smcfg, entry)) < 0) /* repo appears to already exist - reinit? */
goto cleanup;
}
else if (strcmp(entry->path, ".gitmodules") == 0)
git_oid_cpy(&gitmodules_oid, &entry->oid);
} }
else if (use_gitlink) {
git_buf repodir = GIT_BUF_INIT;
/* load .gitmodules from workdir if it exists */ error = git_buf_join_n(
if (git_repository_workdir(repo) != NULL) { &repodir, '/', 3, git_repository_path(repo), "modules", path);
/* look in workdir for .gitmodules */ if (error < 0)
git_buf path = GIT_BUF_INIT; goto cleanup;
if (!git_buf_joinpath(
&path, git_repository_workdir(repo), ".gitmodules") &&
git_path_isfile(path.ptr))
{
if (!(error = git_config_file__ondisk(&mods, path.ptr)))
error = git_config_file_open(mods);
}
git_buf_free(&path);
}
/* load .gitmodules from object cache if not in workdir */ initopt.workdir_path = name.ptr;
if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
/* TODO: is it worth loading gitmodules from object cache? */
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;
/* process .gitmodules info */ /* add submodule to hash and "reload" it */
if (!error && mods != NULL)
error = git_config_file_foreach(mods, submodule_from_config, smcfg);
/* store submodule config in repo */ if (!(error = submodule_get(&sm, repo, path, NULL)) &&
if (!error) !(error = git_submodule_reload(sm)))
repo->submodules = smcfg; error = git_submodule_init(sm, false);
cleanup: cleanup:
if (submodule != NULL)
*submodule = !error ? sm : NULL;
if (mods != NULL) if (mods != NULL)
git_config_file_free(mods); git_config_file_free(mods);
if (error) git_repository_free(subrepo);
git_strmap_free(smcfg); git_buf_free(&real_url);
git_buf_free(&name);
return error; return error;
} }
void git_submodule_config_free(git_repository *repo) int git_submodule_add_finalize(git_submodule *sm)
{ {
git_strmap *smcfg = repo->submodules; int error;
git_submodule *sm; git_index *index;
repo->submodules = NULL;
if (smcfg == NULL) assert(sm);
return;
git_strmap_foreach_value(smcfg, sm, { if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
submodule_release(sm,1); (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0)
}); return error;
git_strmap_free(smcfg);
}
static int submodule_cmp(const void *a, const void *b) return git_submodule_add_to_index(sm, true);
{
return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
} }
int git_submodule_foreach( int git_submodule_add_to_index(git_submodule *sm, int write_index)
git_repository *repo,
int (*callback)(const char *name, void *payload),
void *payload)
{ {
int error; int error;
git_submodule *sm; git_repository *repo, *sm_repo;
git_vector seen = GIT_VECTOR_INIT; git_index *index;
seen._cmp = submodule_cmp; git_buf path = GIT_BUF_INIT;
git_commit *head;
git_index_entry entry;
struct stat st;
if ((error = load_submodule_config(repo)) < 0) assert(sm);
return error;
git_strmap_foreach_value(repo->submodules, sm, { repo = sm->owner;
/* usually the following will not come into play */
if (sm->refcount > 1) {
if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
break;
}
if ((error = callback(sm->name, payload)) < 0) /* force reload of wd OID by git_submodule_open */
break; sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
});
git_vector_free(&seen); 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;
}
memset(&entry, 0, sizeof(entry));
entry.path = sm->path;
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);
/* add it */
error = git_index_add2(index, &entry);
/* write it, if requested */
if (!error && write_index) {
error = git_index_write(index);
if (!error)
git_oid_cpy(&sm->index_oid, &sm->wd_oid);
}
cleanup:
git_repository_free(sm_repo);
git_buf_free(&path);
return error; return error;
} }
int git_submodule_lookup( int git_submodule_save(git_submodule *submodule)
git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
git_repository *repo,
const char *name) /* trailing slash is allowed */
{ {
khiter_t pos; int error = 0;
git_config_file *mods;
git_buf key = GIT_BUF_INIT;
if (load_submodule_config(repo) < 0) 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; return -1;
}
pos = git_strmap_lookup_index(repo->submodules, name); if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)
if (!git_strmap_valid_index(repo->submodules, pos)) goto cleanup;
return GIT_ENOTFOUND;
if (sm_ptr) /* save values for path, url, update, ignore, fetchRecurseSubmodules */
*sm_ptr = git_strmap_value_at(repo->submodules, pos);
return 0; 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);
else
giterr_clear();
}
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;
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
pos = git_index_find(index, submodule->path);
if (pos >= 0) {
git_index_entry *entry = git_index_get(index, pos);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_load_from_index(repo, entry)) < 0)
return error;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
}
}
/* refresh HEAD tree data */
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))) {
if (S_ISGITLINK(te->attr)) {
error = submodule_load_from_head(repo, submodule->path, &te->oid);
} else {
submodule_mode_mismatch(
repo, submodule->path,
GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
}
git_tree_entry_free(te);
}
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);
git_config_file_free(mods);
}
if (error < 0)
return error;
/* refresh wd data */
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID);
error = submodule_load_from_wd_lite(submodule, submodule->path, NULL);
return error;
}
int git_submodule_status(
unsigned int *status,
git_submodule *submodule)
{
int error = 0;
unsigned int status_val;
assert(status && submodule);
status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);
if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {
if (!(error = submodule_index_status(&status_val, submodule)))
error = submodule_wd_status(&status_val, submodule);
}
*status = status_val;
return error;
}
/*
* INTERNAL FUNCTIONS
*/
static git_submodule *submodule_alloc(git_repository *repo, const char *name)
{
git_submodule *sm;
if (!name || !strlen(name)) {
giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
return NULL;
}
sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
goto fail;
sm->path = sm->name = git__strdup(name);
if (!sm->name)
goto fail;
sm->owner = repo;
sm->refcount = 1;
return sm;
fail:
submodule_release(sm, 0);
return NULL;
}
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 int submodule_get(
git_submodule **sm_ptr,
git_repository *repo,
const char *name,
const char *alternate)
{
git_strmap *smcfg = repo->submodules;
khiter_t pos;
git_submodule *sm;
int error;
assert(repo && name);
pos = git_strmap_lookup_index(smcfg, name);
if (!git_strmap_valid_index(smcfg, pos) && alternate)
pos = git_strmap_lookup_index(smcfg, alternate);
if (!git_strmap_valid_index(smcfg, pos)) {
sm = submodule_alloc(repo, name);
/* insert value at name - if another thread beats us to it, then use
* their record and release our own.
*/
pos = kh_put(str, smcfg, sm->name, &error);
if (error < 0) {
submodule_release(sm, 1);
sm = NULL;
} else if (error == 0) {
submodule_release(sm, 1);
sm = git_strmap_value_at(smcfg, pos);
} else {
git_strmap_set_value_at(smcfg, pos, sm);
}
} else {
sm = git_strmap_value_at(smcfg, pos);
}
*sm_ptr = sm;
return (sm != NULL) ? 0 : -1;
}
static int submodule_load_from_index(
git_repository *repo, const git_index_entry *entry)
{
git_submodule *sm;
if (submodule_get(&sm, repo, entry->path, NULL) < 0)
return -1;
if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
return 0;
}
sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;
git_oid_cpy(&sm->index_oid, &entry->oid);
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
return 0;
}
static int submodule_load_from_head(
git_repository *repo, const char *path, const git_oid *oid)
{
git_submodule *sm;
if (submodule_get(&sm, repo, path, NULL) < 0)
return -1;
sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
git_oid_cpy(&sm->head_oid, oid);
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
return 0;
}
static int submodule_config_error(const char *property, const char *value)
{
giterr_set(GITERR_INVALID,
"Invalid value for submodule '%s' property: '%s'", property, value);
return -1;
}
static int submodule_load_from_config(
const char *key, const char *value, void *data)
{
git_repository *repo = data;
git_strmap *smcfg = repo->submodules;
const char *namestart, *property, *alternate = NULL;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
bool is_path;
int error = 0;
if (git__prefixcmp(key, "submodule.") != 0)
return 0;
namestart = key + strlen("submodule.");
property = strrchr(namestart, '.');
if (property == NULL)
return 0;
property++;
is_path = (strcasecmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1;
if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) {
git_buf_free(&name);
return -1;
}
sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
/* Only from config might we get differing names & paths. If so, then
* update the submodule and insert under the alternative key.
*/
/* TODO: if case insensitive filesystem, then the following strcmps
* should be strcasecmp
*/
if (strcmp(sm->name, name.ptr) != 0) {
alternate = sm->name = git_buf_detach(&name);
} else if (is_path && value && strcmp(sm->path, value) != 0) {
alternate = sm->path = git__strdup(value);
if (!sm->path)
error = -1;
}
if (alternate) {
void *old_sm = NULL;
git_strmap_insert2(smcfg, alternate, sm, old_sm, error);
if (error >= 0)
sm->refcount++; /* inserted under a new key */
/* if we replaced an old module under this key, release the old one */
if (old_sm && ((git_submodule *)old_sm) != sm) {
submodule_release(old_sm, 1);
/* TODO: log warning about multiple submodules with same path */
}
}
git_buf_free(&name);
if (error < 0)
return error;
/* TODO: Look up path in index and if it is present but not a GITLINK
* then this should be deleted (at least to match git's behavior)
*/
if (is_path)
return 0;
/* copy other properties into submodule entry */
if (strcasecmp(property, "url") == 0) {
git__free(sm->url);
sm->url = NULL;
if (value != NULL && (sm->url = git__strdup(value)) == NULL)
return -1;
}
else if (strcasecmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0)
return submodule_config_error("update", value);
sm->update_default = sm->update = (git_submodule_update_t)val;
}
else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
if (git__parse_bool(&sm->fetch_recurse, value) < 0)
return submodule_config_error("fetchRecurseSubmodules", value);
}
else if (strcasecmp(property, "ignore") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0)
return submodule_config_error("ignore", value);
sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
return 0;
}
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 void submodule_mode_mismatch(
git_repository *repo, const char *path, unsigned int flag)
{
khiter_t pos = git_strmap_lookup_index(repo->submodules, path);
if (git_strmap_valid_index(repo->submodules, pos)) {
git_submodule *sm = git_strmap_value_at(repo->submodules, pos);
sm->flags |= flag;
}
}
static int load_submodule_config_from_index(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_iterator *i;
const git_index_entry *entry;
if ((error = git_iterator_for_index(&i, repo)) < 0)
return error;
error = git_iterator_current(i, &entry);
while (!error && entry != NULL) {
if (S_ISGITLINK(entry->mode)) {
error = submodule_load_from_index(repo, entry);
if (error < 0)
break;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
git_oid_cpy(gitmodules_oid, &entry->oid);
}
error = git_iterator_advance(i, &entry);
}
git_iterator_free(i);
return error;
}
static int load_submodule_config_from_head(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_tree *head;
git_iterator *i;
const git_index_entry *entry;
if ((error = git_repository_head_tree(&head, repo)) < 0)
return error;
if ((error = git_iterator_for_tree(&i, repo, head)) < 0) {
git_tree_free(head);
return error;
}
error = git_iterator_current(i, &entry);
while (!error && entry != NULL) {
if (S_ISGITLINK(entry->mode)) {
error = submodule_load_from_head(repo, entry->path, &entry->oid);
if (error < 0)
break;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
git_oid_iszero(gitmodules_oid))
git_oid_cpy(gitmodules_oid, &entry->oid);
}
error = git_iterator_advance(i, &entry);
}
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)
mods = NULL;
/* open should only fail here if the file is malformed */
else if (git_config_file_open(mods) < 0) {
git_config_file_free(mods);
mods = NULL;
}
}
}
if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
/* TODO: Retrieve .gitmodules content from ODB */
/* Should we actually do this? Core git does not, but it means you
* can't really get much information about submodules on bare repos.
*/
}
git_buf_free(&path);
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);
}
/* add submodule information from index */
if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)
goto cleanup;
/* add submodule information from HEAD */
if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)
goto cleanup;
/* add submodule information from .gitmodules */
if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL)
error = git_config_file_foreach(mods, submodule_load_from_config, repo);
if (error != 0)
goto cleanup;
/* shallow scan submodules in work tree */
if (!git_repository_is_bare(repo))
error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);
cleanup:
git_buf_free(&path);
if (mods != NULL)
git_config_file_free(mods);
if (error)
git_submodule_config_free(repo);
return error;
}
static int lookup_head_remote(git_buf *url, git_repository *repo)
{
int error;
git_config *cfg;
git_reference *head = NULL, *remote = NULL;
const char *tgt, *scan;
git_buf key = GIT_BUF_INIT;
/* 1. resolve HEAD -> refs/heads/BRANCH
* 2. lookup config branch.BRANCH.remote -> ORIGIN
* 3. lookup remote.ORIGIN.url
*/
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD cannot be resolved");
error = GIT_ENOTFOUND;
goto cleanup;
}
if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
if ((error = git_branch_tracking(&remote, head)) < 0)
goto cleanup;
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||
git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0)
{
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
scan = tgt = git_reference_target(remote) + strlen("refs/remotes/");
while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\')))
scan++; /* find non-escaped slash to end ORIGIN name */
error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);
if (error < 0)
goto cleanup;
if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)
goto cleanup;
error = git_buf_sets(url, tgt);
cleanup:
git_buf_free(&key);
git_reference_free(head);
git_reference_free(remote);
return error;
}
static int submodule_update_config(
git_submodule *submodule,
const char *attr,
const char *value,
bool overwrite,
bool only_existing)
{
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;
}
static int submodule_index_status(unsigned int *status, git_submodule *sm)
{
const git_oid *head_oid = git_submodule_head_oid(sm);
const git_oid *index_oid = git_submodule_index_oid(sm);
if (!head_oid) {
if (index_oid)
*status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
}
else if (!index_oid)
*status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
else if (!git_oid_equal(head_oid, index_oid))
*status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
return 0;
}
static int submodule_wd_status(unsigned int *status, git_submodule *sm)
{
int error = 0;
const git_oid *wd_oid, *index_oid;
git_repository *sm_repo = NULL;
/* open repo now if we need it (so wd_oid() call won't reopen) */
if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||
sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&
(sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)
{
if ((error = git_submodule_open(&sm_repo, sm)) < 0)
return error;
}
index_oid = git_submodule_index_oid(sm);
wd_oid = git_submodule_wd_oid(sm);
if (!index_oid) {
if (wd_oid)
*status |= GIT_SUBMODULE_STATUS_WD_ADDED;
}
else if (!wd_oid) {
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
(sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
else
*status |= GIT_SUBMODULE_STATUS_WD_DELETED;
}
else if (!git_oid_equal(index_oid, wd_oid))
*status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
if (sm_repo != NULL) {
git_tree *sm_head;
git_diff_options opt;
git_diff_list *diff;
/* the diffs below could be optimized with an early termination
* option to the git_diff functions, but for now this is sufficient
* (and certainly no worse that what core git does).
*/
/* perform head-to-index diff on submodule */
if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)
return error;
memset(&opt, 0, sizeof(opt));
if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
if (!error) {
if (git_diff_entrycount(diff, -1) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
git_diff_list_free(diff);
diff = NULL;
}
git_tree_free(sm_head);
if (error < 0)
return error;
/* perform index-to-workdir diff on submodule */
error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
if (!error) {
int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED);
if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
if (git_diff_entrycount(diff, -1) - untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff);
diff = NULL;
}
git_repository_free(sm_repo);
}
return error;
} }
/*
* 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 */
enum {
GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),
GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),
GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),
GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),
GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),
GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),
GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
};
#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
((S) & ~(0xFFFFFFFFu << 20))
#endif
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[submodule "sm_missing_commits"]
url = ../submod2_target
[submodule "sm_unchanged"]
url = ../submod2_target
[submodule "sm_changed_file"]
url = ../submod2_target
[submodule "sm_changed_index"]
url = ../submod2_target
[submodule "sm_changed_head"]
url = ../submod2_target
[submodule "sm_changed_untracked_file"]
url = ../submod2_target
[submodule "sm_added_and_uncommited"]
url = ../submod2_target
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer <rb@github.com> 1342559771 -0700 commit (initial): Initial commit
14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer <rb@github.com> 1342559831 -0700 commit: Adding a submodule
a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer <rb@github.com> 1342560036 -0700 commit: Updating submodule
5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer <rb@github.com> 1342560288 -0700 commit: Adding a bunch more test content
0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer <rb@github.com> 1342559771 -0700 commit (initial): Initial commit
14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer <rb@github.com> 1342559831 -0700 commit: Adding a submodule
a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer <rb@github.com> 1342560036 -0700 commit: Updating submodule
5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer <rb@github.com> 1342560288 -0700 commit: Adding a bunch more test content
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_added_and_uncommited
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_changed_file
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_changed_head
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer <rb@github.com> 1342560431 -0700 commit: Making a change in a submodule
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer <rb@github.com> 1342560431 -0700 commit: Making a change in a submodule
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_changed_index
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_changed_untracked_file
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_missing_commits
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
5e4963595a9774b90524d35a807169049de8ccad refs/remotes/origin/master
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
worktree = ../../../sm_unchanged
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
# pack-refs with: peeled
480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
7484482eb8db738cafa696993664607500a3f2b9
This is the submodule test data
This repo will have a bunch of submodules in different states
[submodule "sm_missing_commits"]
path = sm_missing_commits
url = ../submod2_target
[submodule "sm_unchanged"]
path = sm_unchanged
url = ../submod2_target
[submodule "sm_changed_file"]
path = sm_changed_file
url = ../submod2_target
[submodule "sm_changed_index"]
path = sm_changed_index
url = ../submod2_target
[submodule "sm_changed_head"]
path = sm_changed_head
url = ../submod2_target
[submodule "sm_changed_untracked_file"]
path = sm_changed_untracked_file
url = ../submod2_target
[submodule "sm_added_and_uncommited"]
path = sm_added_and_uncommited
url = ../submod2_target
This is a file in a plain directory
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
:
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# If you want to allow non-ascii filenames set this variable to true.
allownonascii=$(git config hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ascii filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
echo "Error: Attempt to add a non-ascii file name."
echo
echo "This can cause problems if you want to work"
echo "with people on other platforms."
echo
echo "To be portable it is advisable to rename the file ..."
echo
echo "If you know what you are doing you can disable this"
echo "check using:"
echo
echo " git config hooks.allownonascii true"
echo
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up-to-date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
exit 0
################################################################
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first comments out the
# "Conflicts:" part of a merge commit.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
case "$2,$3" in
merge,)
/usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$1" ;;
*) ;;
esac
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
#!/bin/sh
#
# An example hook script to blocks unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
allowmodifytag=$(git config --bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer <rb@github.com> 1342560358 -0700 commit (initial): Initial commit
0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer <rb@github.com> 1342560358 -0700 commit (initial): Initial commit
This is a git repo but not a submodule
gitdir: ../.git/modules/sm_added_and_uncommited
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
gitdir: ../.git/modules/sm_changed_file
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
In this case, the file is changed in the workdir
gitdir: ../.git/modules/sm_changed_head
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
This one has been changed and the change has been committed to HEAD.
gitdir: ../.git/modules/sm_changed_index
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
Here the file is changed in the index and the workdir
gitdir: ../.git/modules/sm_changed_untracked_file
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
gitdir: ../.git/modules/sm_missing_commits
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
gitdir: ../.git/modules/sm_unchanged
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer <rb@github.com> 1342559662 -0700 commit (initial): Initial commit
6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer <rb@github.com> 1342559709 -0700 commit: Adding test file
41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559726 -0700 commit: Updating test file
5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342559925 -0700 commit: One more update
0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer <rb@github.com> 1342559662 -0700 commit (initial): Initial commit
6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer <rb@github.com> 1342559709 -0700 commit: Adding test file
41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559726 -0700 commit: Updating test file
5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342559925 -0700 commit: One more update
This is the target for submod2 submodule links.
Don't add commits casually because you make break tests.
This is a file to modify in submodules
It already has some history.
You can add local changes as needed.
...@@ -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)
...@@ -56,7 +50,7 @@ void test_status_submodules__0(void) ...@@ -56,7 +50,7 @@ void test_status_submodules__0(void)
git_status_foreach(g_repo, cb_status__count, &counts) git_status_foreach(g_repo, cb_status__count, &counts)
); );
cl_assert(counts == 6); cl_assert_equal_i(6, counts);
} }
static const char *expected_files[] = { static const char *expected_files[] = {
...@@ -101,12 +95,12 @@ void test_status_submodules__1(void) ...@@ -101,12 +95,12 @@ void test_status_submodules__1(void)
git_status_foreach(g_repo, cb_status__match, &index) git_status_foreach(g_repo, cb_status__match, &index)
); );
cl_assert(index == 6); cl_assert_equal_i(6, index);
} }
void test_status_submodules__single_file(void) void test_status_submodules__single_file(void)
{ {
unsigned int status; unsigned int status = 0;
cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); cl_git_pass( git_status_file(&status, g_repo, "testrepo") );
cl_assert(status == 0); cl_assert(!status);
} }
#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);
git__free(old_url);
}
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
#include "fileops.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)
{
unsigned int status, expected;
git_submodule *sm;
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
expected = GIT_SUBMODULE_STATUS_IN_HEAD |
GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS_IN_CONFIG |
GIT_SUBMODULE_STATUS_IN_WD;
cl_assert(status == expected);
}
/* 4 values of GIT_SUBMODULE_IGNORE to check */
void test_submodule_status__ignore_none(void)
{
unsigned int status;
git_submodule *sm;
git_buf path = GIT_BUF_INIT;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
/* removed sm_unchanged for deleted workdir */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
/* now mkdir sm_unchanged to test uninitialized */
cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_reload(sm));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
/* update sm_changed_head in index */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_add_to_index(sm, true));
/* reload is not needed because add_to_index updates the submodule data */
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
/* remove sm_changed_head from index */
{
git_index *index;
int pos;
cl_git_pass(git_repository_index(&index, g_repo));
pos = git_index_find(index, "sm_changed_head");
cl_assert(pos >= 0);
cl_git_pass(git_index_remove(index, pos));
cl_git_pass(git_index_write(index));
git_index_free(index);
}
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_reload(sm));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0);
git_buf_free(&path);
}
static int set_sm_ignore(git_submodule *sm, const char *name, void *payload)
{
git_submodule_ignore_t ignore = *(git_submodule_ignore_t *)payload;
GIT_UNUSED(name);
git_submodule_set_ignore(sm, ignore);
return 0;
}
void test_submodule_status__ignore_untracked(void)
{
unsigned int status;
git_submodule *sm;
git_buf path = GIT_BUF_INIT;
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
/* removed sm_unchanged for deleted workdir */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
/* now mkdir sm_unchanged to test uninitialized */
cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_reload(sm));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
/* update sm_changed_head in index */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_add_to_index(sm, true));
/* reload is not needed because add_to_index updates the submodule data */
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
git_buf_free(&path);
}
void test_submodule_status__ignore_dirty(void)
{
unsigned int status;
git_submodule *sm;
git_buf path = GIT_BUF_INIT;
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
/* removed sm_unchanged for deleted workdir */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
/* now mkdir sm_unchanged to test uninitialized */
cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_reload(sm));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
/* update sm_changed_head in index */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_add_to_index(sm, true));
/* reload is not needed because add_to_index updates the submodule data */
cl_git_pass(git_submodule_status(&status, sm));
cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
git_buf_free(&path);
}
void test_submodule_status__ignore_all(void)
{
unsigned int status;
git_submodule *sm;
git_buf path = GIT_BUF_INIT;
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
/* removed sm_unchanged for deleted workdir */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
/* now mkdir sm_unchanged to test uninitialized */
cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0));
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
cl_git_pass(git_submodule_reload(sm));
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
/* update sm_changed_head in index */
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
cl_git_pass(git_submodule_add_to_index(sm, true));
/* reload is not needed because add_to_index updates the submodule data */
cl_git_pass(git_submodule_status(&status, sm));
cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
git_buf_free(&path);
}
#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);
{
libgit2-giterr-set-buffer
Memcheck:Leak
...
fun:git__realloc
fun:git_buf_try_grow
fun:git_buf_grow
fun:git_buf_vprintf
fun:giterr_set
}
{
mac-setenv-leak-1
Memcheck:Leak
fun:malloc_zone_malloc
fun:__setenv
fun:setenv
}
{
mac-setenv-leak-2
Memcheck:Leak
fun:malloc_zone_malloc
fun:malloc_set_zone_name
...
fun:init__zone0
fun:setenv
}
{
mac-dyld-initializer-leak
Memcheck:Leak
fun:malloc
...
fun:dyld_register_image_state_change_handler
fun:_dyld_initializer
}
{
mac-tz-leak-1
Memcheck:Leak
...
fun:token_table_add
fun:notify_register_check
fun:notify_register_tz
}
{
mac-tz-leak-2
Memcheck:Leak
fun:malloc
fun:tzload
}
{
mac-tz-leak-3
Memcheck:Leak
fun:malloc
fun:tzsetwall_basic
}
{
mac-tz-leak-4
Memcheck:Leak
fun:malloc
fun:gmtsub
}
{
mac-system-init-leak-1
Memcheck:Leak
...
fun:_libxpc_initializer
fun:libSystem_initializer
}
{
mac-system-init-leak-2
Memcheck:Leak
...
fun:__keymgr_initializer
fun:libSystem_initializer
}
{
mac-puts-leak
Memcheck:Leak
fun:malloc
fun:__smakebuf
...
fun:puts
}
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