mirror of
https://git.proxmox.com/git/libgit2
synced 2025-12-30 21:34:00 +00:00
Merge pull request #1661 from arrbee/index-add-all
Index operations using globs
This commit is contained in:
commit
8b2fa181b2
@ -11,6 +11,7 @@
|
||||
#include "indexer.h"
|
||||
#include "types.h"
|
||||
#include "oid.h"
|
||||
#include "strarray.h"
|
||||
|
||||
/**
|
||||
* @file git2/index.h
|
||||
@ -125,6 +126,18 @@ typedef enum {
|
||||
GIT_INDEXCAP_FROM_OWNER = ~0u
|
||||
} git_indexcap_t;
|
||||
|
||||
/** Callback for APIs that add/remove/update files matching pathspec */
|
||||
typedef int (*git_index_matched_path_cb)(
|
||||
const char *path, const char *matched_pathspec, void *payload);
|
||||
|
||||
/** Flags for APIs that add files matching pathspec */
|
||||
typedef enum {
|
||||
GIT_INDEX_ADD_DEFAULT = 0,
|
||||
GIT_INDEX_ADD_FORCE = (1u << 0),
|
||||
GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = (1u << 1),
|
||||
GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2),
|
||||
} git_index_add_option_t;
|
||||
|
||||
/** @name Index File Functions
|
||||
*
|
||||
* These functions work on the index file itself.
|
||||
@ -420,6 +433,108 @@ GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path);
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path);
|
||||
|
||||
/**
|
||||
* Add or update index entries matching files in the working directory.
|
||||
*
|
||||
* This method will fail in bare index instances.
|
||||
*
|
||||
* The `pathspec` is a list of file names or shell glob patterns that will
|
||||
* matched against files in the repository's working directory. Each file
|
||||
* that matches will be added to the index (either updating an existing
|
||||
* entry or adding a new entry). You can disable glob expansion and force
|
||||
* exact matching with the `GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH` flag.
|
||||
*
|
||||
* Files that are ignored will be skipped (unlike `git_index_add_bypath`).
|
||||
* If a file is already tracked in the index, then it *will* be updated
|
||||
* even if it is ignored. Pass the `GIT_INDEX_ADD_FORCE` flag to
|
||||
* skip the checking of ignore rules.
|
||||
*
|
||||
* To emulate `git add -A` and generate an error if the pathspec contains
|
||||
* the exact path of an ignored file (when not using FORCE), add the
|
||||
* `GIT_INDEX_ADD_CHECK_PATHSPEC` flag. This checks that each entry
|
||||
* in the `pathspec` that is an exact match to a filename on disk is
|
||||
* either not ignored or already in the index. If this check fails, the
|
||||
* function will return GIT_EINVALIDSPEC.
|
||||
*
|
||||
* To emulate `git add -A` with the "dry-run" option, just use a callback
|
||||
* function that always returns a positive value. See below for details.
|
||||
*
|
||||
* If any files are currently the result of a merge conflict, those files
|
||||
* will no longer be marked as conflicting. The data about the conflicts
|
||||
* will be moved to the "resolve undo" (REUC) section.
|
||||
*
|
||||
* If you provide a callback function, it will be invoked on each matching
|
||||
* item in the working directory immediately *before* it is added to /
|
||||
* updated in the index. Returning zero will add the item to the index,
|
||||
* greater than zero will skip the item, and less than zero will abort the
|
||||
* scan and cause GIT_EUSER to be returned.
|
||||
*
|
||||
* @param index an existing index object
|
||||
* @param pathspec array of path patterns
|
||||
* @param flags combination of git_index_add_option_t flags
|
||||
* @param callback notification callback for each added/updated path (also
|
||||
* gets index of matching pathspec entry); can be NULL;
|
||||
* return 0 to add, >0 to skip, <0 to abort scan.
|
||||
* @param payload payload passed through to callback function
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_add_all(
|
||||
git_index *index,
|
||||
const git_strarray *pathspec,
|
||||
unsigned int flags,
|
||||
git_index_matched_path_cb callback,
|
||||
void *payload);
|
||||
|
||||
/**
|
||||
* Remove all matching index entries.
|
||||
*
|
||||
* If you provide a callback function, it will be invoked on each matching
|
||||
* item in the index immediately *before* it is removed. Return 0 to
|
||||
* remove the item, > 0 to skip the item, and < 0 to abort the scan.
|
||||
*
|
||||
* @param index An existing index object
|
||||
* @param pathspec array of path patterns
|
||||
* @param callback notification callback for each removed path (also
|
||||
* gets index of matching pathspec entry); can be NULL;
|
||||
* return 0 to add, >0 to skip, <0 to abort scan.
|
||||
* @param payload payload passed through to callback function
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_remove_all(
|
||||
git_index *index,
|
||||
const git_strarray *pathspec,
|
||||
git_index_matched_path_cb callback,
|
||||
void *payload);
|
||||
|
||||
/**
|
||||
* Update all index entries to match the working directory
|
||||
*
|
||||
* This method will fail in bare index instances.
|
||||
*
|
||||
* This scans the existing index entries and synchronizes them with the
|
||||
* working directory, deleting them if the corresponding working directory
|
||||
* file no longer exists otherwise updating the information (including
|
||||
* adding the latest version of file to the ODB if needed).
|
||||
*
|
||||
* If you provide a callback function, it will be invoked on each matching
|
||||
* item in the index immediately *before* it is updated (either refreshed
|
||||
* or removed depending on working directory state). Return 0 to proceed
|
||||
* with updating the item, > 0 to skip the item, and < 0 to abort the scan.
|
||||
*
|
||||
* @param index An existing index object
|
||||
* @param pathspec array of path patterns
|
||||
* @param callback notification callback for each updated path (also
|
||||
* gets index of matching pathspec entry); can be NULL;
|
||||
* return 0 to add, >0 to skip, <0 to abort scan.
|
||||
* @param payload payload passed through to callback function
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_update_all(
|
||||
git_index *index,
|
||||
const git_strarray *pathspec,
|
||||
git_index_matched_path_cb callback,
|
||||
void *payload);
|
||||
|
||||
/**
|
||||
* Find the first position of any entries which point to given
|
||||
* path in the Git index.
|
||||
|
||||
@ -675,8 +675,10 @@ static int maybe_modified(
|
||||
}
|
||||
}
|
||||
|
||||
/* if oids and modes match, then file is unmodified */
|
||||
else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode)
|
||||
/* if oids and modes match (and are valid), then file is unmodified */
|
||||
else if (git_oid_equal(&oitem->oid, &nitem->oid) &&
|
||||
omode == nmode &&
|
||||
!git_oid_iszero(&oitem->oid))
|
||||
status = GIT_DELTA_UNMODIFIED;
|
||||
|
||||
/* if we have an unknown OID and a workdir iterator, then check some
|
||||
|
||||
58
src/ignore.c
58
src/ignore.c
@ -340,3 +340,61 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
int git_ignore__check_pathspec_for_exact_ignores(
|
||||
git_repository *repo,
|
||||
git_vector *vspec,
|
||||
bool no_fnmatch)
|
||||
{
|
||||
int error = 0;
|
||||
size_t i;
|
||||
git_attr_fnmatch *match;
|
||||
int ignored;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
const char *wd, *filename;
|
||||
git_index *idx;
|
||||
|
||||
if ((error = git_repository__ensure_not_bare(
|
||||
repo, "validate pathspec")) < 0 ||
|
||||
(error = git_repository_index(&idx, repo)) < 0)
|
||||
return error;
|
||||
|
||||
wd = git_repository_workdir(repo);
|
||||
|
||||
git_vector_foreach(vspec, i, match) {
|
||||
/* skip wildcard matches (if they are being used) */
|
||||
if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
|
||||
!no_fnmatch)
|
||||
continue;
|
||||
|
||||
filename = match->pattern;
|
||||
|
||||
/* if file is already in the index, it's fine */
|
||||
if (git_index_get_bypath(idx, filename, 0) != NULL)
|
||||
continue;
|
||||
|
||||
if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
|
||||
break;
|
||||
|
||||
/* is there a file on disk that matches this exactly? */
|
||||
if (!git_path_isfile(path.ptr))
|
||||
continue;
|
||||
|
||||
/* is that file ignored? */
|
||||
if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
|
||||
break;
|
||||
|
||||
if (ignored) {
|
||||
giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
|
||||
filename);
|
||||
error = GIT_EINVALIDSPEC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
git_index_free(idx);
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@ -41,4 +41,13 @@ extern void git_ignore__free(git_ignores *ign);
|
||||
|
||||
extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored);
|
||||
|
||||
/* command line Git sometimes generates an error message if given a
|
||||
* pathspec that contains an exact match to an ignored file (provided
|
||||
* --force isn't also given). This makes it easy to check it that has
|
||||
* happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored
|
||||
* exact matches (that are not already present in the index).
|
||||
*/
|
||||
extern int git_ignore__check_pathspec_for_exact_ignores(
|
||||
git_repository *repo, git_vector *pathspec, bool no_fnmatch);
|
||||
|
||||
#endif
|
||||
|
||||
221
src/index.c
221
src/index.c
@ -15,6 +15,8 @@
|
||||
#include "hash.h"
|
||||
#include "iterator.h"
|
||||
#include "pathspec.h"
|
||||
#include "ignore.h"
|
||||
|
||||
#include "git2/odb.h"
|
||||
#include "git2/oid.h"
|
||||
#include "git2/blob.h"
|
||||
@ -997,7 +999,7 @@ static int index_conflict__get_byindex(
|
||||
int stage, len = 0;
|
||||
|
||||
assert(ancestor_out && our_out && their_out && index);
|
||||
|
||||
|
||||
*ancestor_out = NULL;
|
||||
*our_out = NULL;
|
||||
*their_out = NULL;
|
||||
@ -1010,7 +1012,7 @@ static int index_conflict__get_byindex(
|
||||
|
||||
stage = GIT_IDXENTRY_STAGE(conflict_entry);
|
||||
path = conflict_entry->path;
|
||||
|
||||
|
||||
switch (stage) {
|
||||
case 3:
|
||||
*their_out = conflict_entry;
|
||||
@ -2044,3 +2046,218 @@ git_repository *git_index_owner(const git_index *index)
|
||||
{
|
||||
return INDEX_OWNER(index);
|
||||
}
|
||||
|
||||
int git_index_add_all(
|
||||
git_index *index,
|
||||
const git_strarray *paths,
|
||||
unsigned int flags,
|
||||
git_index_matched_path_cb cb,
|
||||
void *payload)
|
||||
{
|
||||
int error;
|
||||
git_repository *repo;
|
||||
git_iterator *wditer = NULL;
|
||||
const git_index_entry *wd = NULL;
|
||||
git_index_entry *entry;
|
||||
git_pathspec_context ps;
|
||||
const char *match;
|
||||
size_t existing;
|
||||
bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0;
|
||||
int ignorecase;
|
||||
git_oid blobid;
|
||||
|
||||
assert(index);
|
||||
|
||||
if (INDEX_OWNER(index) == NULL)
|
||||
return create_index_error(-1,
|
||||
"Could not add paths to index. "
|
||||
"Index is not backed up by an existing repository.");
|
||||
|
||||
repo = INDEX_OWNER(index);
|
||||
if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0)
|
||||
return error;
|
||||
|
||||
if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0)
|
||||
return -1;
|
||||
|
||||
if ((error = git_pathspec_context_init(&ps, paths)) < 0)
|
||||
return error;
|
||||
|
||||
/* optionally check that pathspec doesn't mention any ignored files */
|
||||
if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 &&
|
||||
(flags & GIT_INDEX_ADD_FORCE) == 0 &&
|
||||
(error = git_ignore__check_pathspec_for_exact_ignores(
|
||||
repo, &ps.pathspec, no_fnmatch)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_iterator_for_workdir(
|
||||
&wditer, repo, 0, ps.prefix, ps.prefix)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
while (!(error = git_iterator_advance(&wd, wditer))) {
|
||||
|
||||
/* check if path actually matches */
|
||||
if (!git_pathspec_match_path(
|
||||
&ps.pathspec, wd->path, no_fnmatch, ignorecase, &match))
|
||||
continue;
|
||||
|
||||
/* skip ignored items that are not already in the index */
|
||||
if ((flags & GIT_INDEX_ADD_FORCE) == 0 &&
|
||||
git_iterator_current_is_ignored(wditer) &&
|
||||
index_find(&existing, index, wd->path, 0) < 0)
|
||||
continue;
|
||||
|
||||
/* issue notification callback if requested */
|
||||
if (cb && (error = cb(wd->path, match, payload)) != 0) {
|
||||
if (error > 0) /* return > 0 means skip this one */
|
||||
continue;
|
||||
if (error < 0) { /* return < 0 means abort */
|
||||
giterr_clear();
|
||||
error = GIT_EUSER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Should we check if the file on disk is already an exact
|
||||
* match to the file in the index and skip this work if it is?
|
||||
*/
|
||||
|
||||
/* write the blob to disk and get the oid */
|
||||
if ((error = git_blob_create_fromworkdir(&blobid, repo, wd->path)) < 0)
|
||||
break;
|
||||
|
||||
/* make the new entry to insert */
|
||||
if ((entry = index_entry_dup(wd)) == NULL) {
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
entry->oid = blobid;
|
||||
|
||||
/* add working directory item to index */
|
||||
if ((error = index_insert(index, entry, 1)) < 0) {
|
||||
index_entry_free(entry);
|
||||
break;
|
||||
}
|
||||
|
||||
git_tree_cache_invalidate_path(index->tree, wd->path);
|
||||
|
||||
/* add implies conflict resolved, move conflict entries to REUC */
|
||||
if ((error = index_conflict_to_reuc(index, wd->path)) < 0) {
|
||||
if (error != GIT_ENOTFOUND)
|
||||
break;
|
||||
giterr_clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (error == GIT_ITEROVER)
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_iterator_free(wditer);
|
||||
git_pathspec_context_free(&ps);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
enum {
|
||||
INDEX_ACTION_NONE = 0,
|
||||
INDEX_ACTION_UPDATE = 1,
|
||||
INDEX_ACTION_REMOVE = 2,
|
||||
};
|
||||
|
||||
static int index_apply_to_all(
|
||||
git_index *index,
|
||||
int action,
|
||||
const git_strarray *paths,
|
||||
git_index_matched_path_cb cb,
|
||||
void *payload)
|
||||
{
|
||||
int error = 0;
|
||||
size_t i;
|
||||
git_pathspec_context ps;
|
||||
const char *match;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
assert(index);
|
||||
|
||||
if ((error = git_pathspec_context_init(&ps, paths)) < 0)
|
||||
return error;
|
||||
|
||||
git_vector_sort(&index->entries);
|
||||
|
||||
for (i = 0; !error && i < index->entries.length; ++i) {
|
||||
git_index_entry *entry = git_vector_get(&index->entries, i);
|
||||
|
||||
/* check if path actually matches */
|
||||
if (!git_pathspec_match_path(
|
||||
&ps.pathspec, entry->path, false, index->ignore_case, &match))
|
||||
continue;
|
||||
|
||||
/* issue notification callback if requested */
|
||||
if (cb && (error = cb(entry->path, match, payload)) != 0) {
|
||||
if (error > 0) { /* return > 0 means skip this one */
|
||||
error = 0;
|
||||
continue;
|
||||
}
|
||||
if (error < 0) { /* return < 0 means abort */
|
||||
giterr_clear();
|
||||
error = GIT_EUSER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* index manipulation may alter entry, so don't depend on it */
|
||||
if ((error = git_buf_sets(&path, entry->path)) < 0)
|
||||
break;
|
||||
|
||||
switch (action) {
|
||||
case INDEX_ACTION_NONE:
|
||||
break;
|
||||
case INDEX_ACTION_UPDATE:
|
||||
error = git_index_add_bypath(index, path.ptr);
|
||||
|
||||
if (error == GIT_ENOTFOUND) {
|
||||
giterr_clear();
|
||||
|
||||
error = git_index_remove_bypath(index, path.ptr);
|
||||
|
||||
if (!error) /* back up foreach if we removed this */
|
||||
i--;
|
||||
}
|
||||
break;
|
||||
case INDEX_ACTION_REMOVE:
|
||||
if (!(error = git_index_remove_bypath(index, path.ptr)))
|
||||
i--; /* back up foreach if we removed this */
|
||||
break;
|
||||
default:
|
||||
giterr_set(GITERR_INVALID, "Unknown index action %d", action);
|
||||
error = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
git_buf_free(&path);
|
||||
git_pathspec_context_free(&ps);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_index_remove_all(
|
||||
git_index *index,
|
||||
const git_strarray *pathspec,
|
||||
git_index_matched_path_cb cb,
|
||||
void *payload)
|
||||
{
|
||||
return index_apply_to_all(
|
||||
index, INDEX_ACTION_REMOVE, pathspec, cb, payload);
|
||||
}
|
||||
|
||||
int git_index_update_all(
|
||||
git_index *index,
|
||||
const git_strarray *pathspec,
|
||||
git_index_matched_path_cb cb,
|
||||
void *payload)
|
||||
{
|
||||
return index_apply_to_all(
|
||||
index, INDEX_ACTION_UPDATE, pathspec, cb, payload);
|
||||
}
|
||||
|
||||
@ -166,3 +166,28 @@ bool git_pathspec_match_path(
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int git_pathspec_context_init(
|
||||
git_pathspec_context *ctxt, const git_strarray *paths)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
memset(ctxt, 0, sizeof(*ctxt));
|
||||
|
||||
ctxt->prefix = git_pathspec_prefix(paths);
|
||||
|
||||
if ((error = git_pool_init(&ctxt->pool, 1, 0)) < 0 ||
|
||||
(error = git_pathspec_init(&ctxt->pathspec, paths, &ctxt->pool)) < 0)
|
||||
git_pathspec_context_free(ctxt);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void git_pathspec_context_free(
|
||||
git_pathspec_context *ctxt)
|
||||
{
|
||||
git__free(ctxt->prefix);
|
||||
git_pathspec_free(&ctxt->pathspec);
|
||||
git_pool_clear(&ctxt->pool);
|
||||
memset(ctxt, 0, sizeof(*ctxt));
|
||||
}
|
||||
|
||||
@ -37,4 +37,18 @@ extern bool git_pathspec_match_path(
|
||||
bool casefold,
|
||||
const char **matched_pathspec);
|
||||
|
||||
/* easy pathspec setup */
|
||||
|
||||
typedef struct {
|
||||
char *prefix;
|
||||
git_vector pathspec;
|
||||
git_pool pool;
|
||||
} git_pathspec_context;
|
||||
|
||||
extern int git_pathspec_context_init(
|
||||
git_pathspec_context *ctxt, const git_strarray *paths);
|
||||
|
||||
extern void git_pathspec_context_free(
|
||||
git_pathspec_context *ctxt);
|
||||
|
||||
#endif
|
||||
|
||||
274
tests-clar/index/addall.c
Normal file
274
tests-clar/index/addall.c
Normal file
@ -0,0 +1,274 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "../status/status_helpers.h"
|
||||
#include "posix.h"
|
||||
|
||||
git_repository *g_repo = NULL;
|
||||
|
||||
void test_index_addall__initialize(void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_index_addall__cleanup(void)
|
||||
{
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
}
|
||||
|
||||
#define STATUS_INDEX_FLAGS \
|
||||
(GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \
|
||||
GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \
|
||||
GIT_STATUS_INDEX_TYPECHANGE)
|
||||
|
||||
#define STATUS_WT_FLAGS \
|
||||
(GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \
|
||||
GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \
|
||||
GIT_STATUS_WT_RENAMED)
|
||||
|
||||
typedef struct {
|
||||
size_t index_adds;
|
||||
size_t index_dels;
|
||||
size_t index_mods;
|
||||
size_t wt_adds;
|
||||
size_t wt_dels;
|
||||
size_t wt_mods;
|
||||
size_t ignores;
|
||||
} index_status_counts;
|
||||
|
||||
static int index_status_cb(
|
||||
const char *path, unsigned int status_flags, void *payload)
|
||||
{
|
||||
index_status_counts *vals = payload;
|
||||
|
||||
/* cb_status__print(path, status_flags, NULL); */
|
||||
|
||||
GIT_UNUSED(path);
|
||||
|
||||
if (status_flags & GIT_STATUS_INDEX_NEW)
|
||||
vals->index_adds++;
|
||||
if (status_flags & GIT_STATUS_INDEX_MODIFIED)
|
||||
vals->index_mods++;
|
||||
if (status_flags & GIT_STATUS_INDEX_DELETED)
|
||||
vals->index_dels++;
|
||||
if (status_flags & GIT_STATUS_INDEX_TYPECHANGE)
|
||||
vals->index_mods++;
|
||||
|
||||
if (status_flags & GIT_STATUS_WT_NEW)
|
||||
vals->wt_adds++;
|
||||
if (status_flags & GIT_STATUS_WT_MODIFIED)
|
||||
vals->wt_mods++;
|
||||
if (status_flags & GIT_STATUS_WT_DELETED)
|
||||
vals->wt_dels++;
|
||||
if (status_flags & GIT_STATUS_WT_TYPECHANGE)
|
||||
vals->wt_mods++;
|
||||
|
||||
if (status_flags & GIT_STATUS_IGNORED)
|
||||
vals->ignores++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void check_status(
|
||||
git_repository *repo,
|
||||
size_t index_adds, size_t index_dels, size_t index_mods,
|
||||
size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores)
|
||||
{
|
||||
index_status_counts vals;
|
||||
|
||||
memset(&vals, 0, sizeof(vals));
|
||||
|
||||
cl_git_pass(git_status_foreach(repo, index_status_cb, &vals));
|
||||
|
||||
cl_assert_equal_sz(index_adds, vals.index_adds);
|
||||
cl_assert_equal_sz(index_dels, vals.index_dels);
|
||||
cl_assert_equal_sz(index_mods, vals.index_mods);
|
||||
cl_assert_equal_sz(wt_adds, vals.wt_adds);
|
||||
cl_assert_equal_sz(wt_dels, vals.wt_dels);
|
||||
cl_assert_equal_sz(wt_mods, vals.wt_mods);
|
||||
cl_assert_equal_sz(ignores, vals.ignores);
|
||||
}
|
||||
|
||||
static void check_stat_data(git_index *index, const char *path, bool match)
|
||||
{
|
||||
const git_index_entry *entry;
|
||||
struct stat st;
|
||||
|
||||
cl_must_pass(p_lstat(path, &st));
|
||||
|
||||
/* skip repo base dir name */
|
||||
while (*path != '/')
|
||||
++path;
|
||||
++path;
|
||||
|
||||
entry = git_index_get_bypath(index, path, 0);
|
||||
cl_assert(entry);
|
||||
|
||||
if (match) {
|
||||
cl_assert(st.st_ctime == entry->ctime.seconds);
|
||||
cl_assert(st.st_mtime == entry->mtime.seconds);
|
||||
cl_assert(st.st_size == entry->file_size);
|
||||
cl_assert(st.st_uid == entry->uid);
|
||||
cl_assert(st.st_gid == entry->gid);
|
||||
cl_assert_equal_b(st.st_mode & ~0777, entry->mode & ~0777);
|
||||
cl_assert_equal_b(st.st_mode & 0111, entry->mode & 0111);
|
||||
} else {
|
||||
/* most things will still match */
|
||||
cl_assert(st.st_size != entry->file_size);
|
||||
/* would check mtime, but with second resolution it won't work :( */
|
||||
}
|
||||
}
|
||||
|
||||
static void commit_index_to_head(
|
||||
git_repository *repo,
|
||||
const char *commit_message)
|
||||
{
|
||||
git_index *index;
|
||||
git_oid tree_id, commit_id;
|
||||
git_tree *tree;
|
||||
git_signature *sig;
|
||||
git_commit *parent = NULL;
|
||||
|
||||
git_revparse_single((git_object **)&parent, repo, "HEAD");
|
||||
/* it is okay if looking up the HEAD fails */
|
||||
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_git_pass(git_index_write_tree(&tree_id, index));
|
||||
cl_git_pass(git_index_write(index)); /* not needed, but might as well */
|
||||
git_index_free(index);
|
||||
|
||||
cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
|
||||
|
||||
cl_git_pass(git_signature_now(&sig, "Testy McTester", "tt@tester.test"));
|
||||
|
||||
cl_git_pass(git_commit_create_v(
|
||||
&commit_id, repo, "HEAD", sig, sig,
|
||||
NULL, commit_message, tree, parent ? 1 : 0, parent));
|
||||
|
||||
git_commit_free(parent);
|
||||
git_tree_free(tree);
|
||||
git_signature_free(sig);
|
||||
}
|
||||
|
||||
void test_index_addall__repo_lifecycle(void)
|
||||
{
|
||||
int error;
|
||||
git_index *index;
|
||||
git_strarray paths = { NULL, 0 };
|
||||
char *strs[1];
|
||||
|
||||
cl_git_pass(git_repository_init(&g_repo, "addall", false));
|
||||
check_status(g_repo, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
cl_git_pass(git_repository_index(&index, g_repo));
|
||||
|
||||
cl_git_mkfile("addall/file.foo", "a file");
|
||||
check_status(g_repo, 0, 0, 0, 1, 0, 0, 0);
|
||||
|
||||
cl_git_mkfile("addall/.gitignore", "*.foo\n");
|
||||
check_status(g_repo, 0, 0, 0, 1, 0, 0, 1);
|
||||
|
||||
cl_git_mkfile("addall/file.bar", "another file");
|
||||
check_status(g_repo, 0, 0, 0, 2, 0, 0, 1);
|
||||
|
||||
strs[0] = "file.*";
|
||||
paths.strings = strs;
|
||||
paths.count = 1;
|
||||
|
||||
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
|
||||
check_stat_data(index, "addall/file.bar", true);
|
||||
check_status(g_repo, 1, 0, 0, 1, 0, 0, 1);
|
||||
|
||||
cl_git_rewritefile("addall/file.bar", "new content for file");
|
||||
check_stat_data(index, "addall/file.bar", false);
|
||||
check_status(g_repo, 1, 0, 0, 1, 0, 1, 1);
|
||||
|
||||
cl_git_mkfile("addall/file.zzz", "yet another one");
|
||||
cl_git_mkfile("addall/other.zzz", "yet another one");
|
||||
cl_git_mkfile("addall/more.zzz", "yet another one");
|
||||
check_status(g_repo, 1, 0, 0, 4, 0, 1, 1);
|
||||
|
||||
cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
|
||||
check_stat_data(index, "addall/file.bar", true);
|
||||
check_status(g_repo, 1, 0, 0, 4, 0, 0, 1);
|
||||
|
||||
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
|
||||
check_stat_data(index, "addall/file.zzz", true);
|
||||
check_status(g_repo, 2, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
commit_index_to_head(g_repo, "first commit");
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
/* attempt to add an ignored file - does nothing */
|
||||
strs[0] = "file.foo";
|
||||
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
/* add with check - should generate error */
|
||||
error = git_index_add_all(
|
||||
index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL);
|
||||
cl_assert_equal_i(GIT_EINVALIDSPEC, error);
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
|
||||
|
||||
/* add with force - should allow */
|
||||
cl_git_pass(git_index_add_all(
|
||||
index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL));
|
||||
check_stat_data(index, "addall/file.foo", true);
|
||||
check_status(g_repo, 1, 0, 0, 3, 0, 0, 0);
|
||||
|
||||
/* now it's in the index, so regular add should work */
|
||||
cl_git_rewritefile("addall/file.foo", "new content for file");
|
||||
check_stat_data(index, "addall/file.foo", false);
|
||||
check_status(g_repo, 1, 0, 0, 3, 0, 1, 0);
|
||||
|
||||
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
|
||||
check_stat_data(index, "addall/file.foo", true);
|
||||
check_status(g_repo, 1, 0, 0, 3, 0, 0, 0);
|
||||
|
||||
cl_git_pass(git_index_add_bypath(index, "more.zzz"));
|
||||
check_stat_data(index, "addall/more.zzz", true);
|
||||
check_status(g_repo, 2, 0, 0, 2, 0, 0, 0);
|
||||
|
||||
cl_git_rewritefile("addall/file.zzz", "new content for file");
|
||||
check_status(g_repo, 2, 0, 0, 2, 0, 1, 0);
|
||||
|
||||
cl_git_pass(git_index_add_bypath(index, "file.zzz"));
|
||||
check_stat_data(index, "addall/file.zzz", true);
|
||||
check_status(g_repo, 2, 0, 1, 2, 0, 0, 0);
|
||||
|
||||
strs[0] = "*.zzz";
|
||||
cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL));
|
||||
check_status(g_repo, 1, 1, 0, 4, 0, 0, 0);
|
||||
|
||||
cl_git_pass(git_index_add_bypath(index, "file.zzz"));
|
||||
check_status(g_repo, 1, 0, 1, 3, 0, 0, 0);
|
||||
|
||||
commit_index_to_head(g_repo, "second commit");
|
||||
check_status(g_repo, 0, 0, 0, 3, 0, 0, 0);
|
||||
|
||||
cl_must_pass(p_unlink("addall/file.zzz"));
|
||||
check_status(g_repo, 0, 0, 0, 3, 1, 0, 0);
|
||||
|
||||
/* update_all should be able to remove entries */
|
||||
cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
|
||||
check_status(g_repo, 0, 1, 0, 3, 0, 0, 0);
|
||||
|
||||
strs[0] = "*";
|
||||
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
|
||||
check_status(g_repo, 3, 1, 0, 0, 0, 0, 0);
|
||||
|
||||
/* must be able to remove at any position while still updating other files */
|
||||
cl_must_pass(p_unlink("addall/.gitignore"));
|
||||
cl_git_rewritefile("addall/file.zzz", "reconstructed file");
|
||||
cl_git_rewritefile("addall/more.zzz", "altered file reality");
|
||||
check_status(g_repo, 3, 1, 0, 1, 1, 1, 0);
|
||||
|
||||
cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
|
||||
check_status(g_repo, 2, 1, 0, 1, 0, 0, 0);
|
||||
/* this behavior actually matches 'git add -u' where "file.zzz" has
|
||||
* been removed from the index, so when you go to update, even though
|
||||
* it exists in the HEAD, it is not re-added to the index, leaving it
|
||||
* as a DELETE when comparing HEAD to index and as an ADD comparing
|
||||
* index to worktree
|
||||
*/
|
||||
|
||||
git_index_free(index);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user