mirror of
https://git.proxmox.com/git/libgit2
synced 2025-10-17 22:48:55 +00:00
Add complex checkout test and then fix checkout
This started as a complex new test for checkout going through the "typechanges" test repository, but that revealed numerous issues with checkout, including: * complete failure with submodules * failure to create blobs with exec bits * problems when replacing a tree with a blob because the tree "example/" sorts after the blob "example" so the delete was being processed after the single file blob was created This fixes most of those problems and includes a number of other minor changes that made it easier to do that, including improving the TYPECHANGE support in diff/status, etc.
This commit is contained in:
parent
f3a04e0f49
commit
0d64bef941
@ -21,12 +21,27 @@
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Checkout behavior flags
|
||||
*
|
||||
* These flags control what checkout does with files. Pass in a
|
||||
* combination of these values OR'ed together.
|
||||
*
|
||||
* - GIT_CHECKOUT_DEFAULT: With this value, checkout does not update
|
||||
* any files in the working directory.
|
||||
* - GIT_CHECKOUT_OVERWRITE_MODIFIED: When a file exists and is modified,
|
||||
* replace the modifications with the new version.
|
||||
* - GIT_CHECKOUT_CREATE_MISSING: When a file does not exist in the
|
||||
* working directory, create it.
|
||||
* - GIT_CHECKOUT_REMOVE_UNTRACKED: If an untracked file in encountered
|
||||
* in the working directory, delete it.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_CHECKOUT_DEFAULT = (1 << 0),
|
||||
GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1),
|
||||
GIT_CHECKOUT_CREATE_MISSING = (1 << 2),
|
||||
GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3),
|
||||
};
|
||||
} git_checkout_strategy_t;
|
||||
|
||||
/* Use zeros to indicate default settings */
|
||||
typedef struct git_checkout_opts {
|
||||
|
@ -56,6 +56,9 @@ GIT_BEGIN_DECL
|
||||
* - GIT_DIFF_DONT_SPLIT_TYPECHANGE: normally, a type change between files
|
||||
* will be converted into a DELETED record for the old file and an ADDED
|
||||
* record for the new one; this option enabled TYPECHANGE records.
|
||||
* - GIT_DIFF_SKIP_BINARY_CHECK: the binary flag in the delta record will
|
||||
* not be updated. This is useful if iterating over a diff without hunk
|
||||
* and line callbacks and you want to avoid loading files completely.
|
||||
*/
|
||||
enum {
|
||||
GIT_DIFF_NORMAL = 0,
|
||||
@ -73,7 +76,9 @@ enum {
|
||||
GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
|
||||
GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12),
|
||||
GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13),
|
||||
GIT_DIFF_DONT_SPLIT_TYPECHANGE = (1 << 14),
|
||||
GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14),
|
||||
GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15),
|
||||
GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -100,7 +100,7 @@ GIT_EXTERN(git_tree_entry *) git_tree_entry_dup(const git_tree_entry *entry);
|
||||
* @param tree a previously loaded tree.
|
||||
* @return object identity for the tree.
|
||||
*/
|
||||
GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
|
||||
GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree);
|
||||
|
||||
/**
|
||||
* Get the number of entries listed in a tree
|
||||
@ -108,7 +108,7 @@ GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
|
||||
* @param tree a previously loaded tree.
|
||||
* @return the number of entries in the tree
|
||||
*/
|
||||
GIT_EXTERN(unsigned int) git_tree_entrycount(git_tree *tree);
|
||||
GIT_EXTERN(unsigned int) git_tree_entrycount(const git_tree *tree);
|
||||
|
||||
/**
|
||||
* Lookup a tree entry by its filename
|
||||
|
230
src/checkout.c
230
src/checkout.c
@ -30,6 +30,7 @@ struct checkout_diff_data
|
||||
git_indexer_stats *stats;
|
||||
git_repository *owner;
|
||||
bool can_symlink;
|
||||
bool create_submodules;
|
||||
int error;
|
||||
};
|
||||
|
||||
@ -40,18 +41,27 @@ static int buffer_to_file(
|
||||
int file_open_flags,
|
||||
mode_t file_mode)
|
||||
{
|
||||
int fd, error_write, error_close;
|
||||
int fd, error, error_close;
|
||||
|
||||
if (git_futils_mkpath2file(path, dir_mode) < 0)
|
||||
return -1;
|
||||
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
|
||||
return error;
|
||||
|
||||
if ((fd = p_open(path, file_open_flags, file_mode)) < 0)
|
||||
return -1;
|
||||
return fd;
|
||||
|
||||
error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
|
||||
|
||||
error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
|
||||
error_close = p_close(fd);
|
||||
|
||||
return error_write ? error_write : error_close;
|
||||
if (!error)
|
||||
error = error_close;
|
||||
|
||||
if (!error &&
|
||||
(file_mode & 0100) != 0 &&
|
||||
(error = p_chmod(path, file_mode)) < 0)
|
||||
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int blob_content_to_file(
|
||||
@ -125,107 +135,122 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
|
||||
return error;
|
||||
}
|
||||
|
||||
static int checkout_submodule(
|
||||
struct checkout_diff_data *data,
|
||||
const git_diff_file *file)
|
||||
{
|
||||
if (git_futils_mkdir(
|
||||
file->path, git_repository_workdir(data->owner),
|
||||
data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0)
|
||||
return -1;
|
||||
|
||||
/* TODO: two cases:
|
||||
* 1 - submodule already checked out, but we need to move the HEAD
|
||||
* to the new OID, or
|
||||
* 2 - submodule not checked out and we should recursively check it out
|
||||
*
|
||||
* Checkout will not execute a pull request on the submodule, but a
|
||||
* clone command should probably be able to. Do we need a submodule
|
||||
* callback option?
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int checkout_blob(
|
||||
git_repository *repo,
|
||||
const git_oid *blob_oid,
|
||||
const char *path,
|
||||
mode_t filemode,
|
||||
bool can_symlink,
|
||||
git_checkout_opts *opts)
|
||||
struct checkout_diff_data *data,
|
||||
const git_diff_file *file)
|
||||
{
|
||||
git_blob *blob;
|
||||
int error;
|
||||
|
||||
if ((error = git_blob_lookup(&blob, repo, blob_oid)) < 0)
|
||||
return error; /* Add an error message */
|
||||
git_buf_truncate(data->path, data->workdir_len);
|
||||
if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0)
|
||||
return -1;
|
||||
|
||||
if (S_ISLNK(filemode))
|
||||
error = blob_content_to_link(blob, path, can_symlink);
|
||||
if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0)
|
||||
return error;
|
||||
|
||||
if (S_ISLNK(file->mode))
|
||||
error = blob_content_to_link(
|
||||
blob, git_buf_cstr(data->path), data->can_symlink);
|
||||
else
|
||||
error = blob_content_to_file(blob, path, filemode, opts);
|
||||
error = blob_content_to_file(
|
||||
blob, git_buf_cstr(data->path), file->mode, data->checkout_opts);
|
||||
|
||||
git_blob_free(blob);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int checkout_diff_fn(
|
||||
void *cb_data,
|
||||
const git_diff_delta *delta,
|
||||
float progress)
|
||||
static int checkout_remove_the_old(
|
||||
void *cb_data, const git_diff_delta *delta, float progress)
|
||||
{
|
||||
struct checkout_diff_data *data = cb_data;
|
||||
int error = 0;
|
||||
git_checkout_opts *opts = data->checkout_opts;
|
||||
bool do_delete = false, do_checkout_blob = false;
|
||||
|
||||
data->stats->processed = (unsigned int)(data->stats->total * progress);
|
||||
GIT_UNUSED(progress);
|
||||
data->stats->processed++;
|
||||
|
||||
switch (delta->status) {
|
||||
case GIT_DELTA_UNTRACKED:
|
||||
if ((opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
|
||||
do_delete = true;
|
||||
break;
|
||||
|
||||
case GIT_DELTA_MODIFIED:
|
||||
case GIT_DELTA_TYPECHANGE:
|
||||
if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) {
|
||||
|
||||
if (opts->skipped_notify_cb != NULL &&
|
||||
opts->skipped_notify_cb(
|
||||
delta->new_file.path,
|
||||
&delta->old_file.oid,
|
||||
delta->old_file.mode,
|
||||
opts->notify_payload) != 0)
|
||||
{
|
||||
giterr_clear();
|
||||
error = GIT_EUSER;
|
||||
}
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
do_checkout_blob = true;
|
||||
|
||||
if (delta->status == GIT_DELTA_TYPECHANGE)
|
||||
do_delete = true;
|
||||
break;
|
||||
|
||||
case GIT_DELTA_DELETED:
|
||||
if ((opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
|
||||
do_checkout_blob = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.",
|
||||
delta->status, delta->new_file.path);
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
if ((delta->status == GIT_DELTA_UNTRACKED &&
|
||||
(opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) ||
|
||||
(delta->status == GIT_DELTA_TYPECHANGE &&
|
||||
(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0))
|
||||
{
|
||||
data->error = git_futils_rmdir_r(
|
||||
delta->new_file.path,
|
||||
git_repository_workdir(data->owner),
|
||||
GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
}
|
||||
|
||||
git_buf_truncate(data->path, data->workdir_len);
|
||||
return data->error;
|
||||
}
|
||||
|
||||
if ((error = git_buf_joinpath(
|
||||
data->path, git_buf_cstr(data->path), delta->new_file.path)) < 0)
|
||||
goto cleanup;
|
||||
static int checkout_create_the_new(
|
||||
void *cb_data, const git_diff_delta *delta, float progress)
|
||||
{
|
||||
int error = 0;
|
||||
struct checkout_diff_data *data = cb_data;
|
||||
git_checkout_opts *opts = data->checkout_opts;
|
||||
bool do_checkout = false, do_notify = false;
|
||||
|
||||
if (do_delete &&
|
||||
(error = git_futils_rmdir_r(
|
||||
git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS)) < 0)
|
||||
goto cleanup;
|
||||
GIT_UNUSED(progress);
|
||||
data->stats->processed++;
|
||||
|
||||
if (do_checkout_blob)
|
||||
error = checkout_blob(
|
||||
data->owner,
|
||||
&delta->old_file.oid,
|
||||
git_buf_cstr(data->path),
|
||||
delta->old_file.mode,
|
||||
data->can_symlink,
|
||||
opts);
|
||||
if (delta->status == GIT_DELTA_MODIFIED ||
|
||||
delta->status == GIT_DELTA_TYPECHANGE)
|
||||
{
|
||||
if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
|
||||
do_checkout = true;
|
||||
else if (opts->skipped_notify_cb != NULL)
|
||||
do_notify = !data->create_submodules;
|
||||
}
|
||||
else if (delta->status == GIT_DELTA_DELETED &&
|
||||
(opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
|
||||
do_checkout = true;
|
||||
|
||||
if (do_notify) {
|
||||
if (opts->skipped_notify_cb(
|
||||
delta->old_file.path, &delta->old_file.oid,
|
||||
delta->old_file.mode, opts->notify_payload))
|
||||
{
|
||||
giterr_clear();
|
||||
error = GIT_EUSER;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_checkout) {
|
||||
bool is_submodule = S_ISGITLINK(delta->old_file.mode);
|
||||
|
||||
if (!is_submodule && !data->create_submodules)
|
||||
error = checkout_blob(data, &delta->old_file);
|
||||
|
||||
else if (is_submodule && data->create_submodules)
|
||||
error = checkout_submodule(data, &delta->old_file);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (error)
|
||||
data->error = error; /* preserve real error */
|
||||
data->error = error;
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -278,7 +303,6 @@ int git_checkout_index(
|
||||
git_checkout_opts *opts,
|
||||
git_indexer_stats *stats)
|
||||
{
|
||||
git_index *index = NULL;
|
||||
git_diff_list *diff = NULL;
|
||||
git_indexer_stats dummy_stats;
|
||||
|
||||
@ -292,11 +316,13 @@ int git_checkout_index(
|
||||
|
||||
assert(repo);
|
||||
|
||||
if ((git_repository__ensure_not_bare(repo, "checkout")) < 0)
|
||||
return GIT_EBAREREPO;
|
||||
if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
|
||||
return error;
|
||||
|
||||
diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
|
||||
GIT_DIFF_DONT_SPLIT_TYPECHANGE;
|
||||
diff_opts.flags =
|
||||
GIT_DIFF_INCLUDE_UNTRACKED |
|
||||
GIT_DIFF_INCLUDE_TYPECHANGE |
|
||||
GIT_DIFF_SKIP_BINARY_CHECK;
|
||||
|
||||
if (opts && opts->paths.count > 0)
|
||||
diff_opts.pathspec = opts->paths;
|
||||
@ -313,11 +339,7 @@ int git_checkout_index(
|
||||
stats = &dummy_stats;
|
||||
|
||||
stats->processed = 0;
|
||||
|
||||
if ((git_repository_index(&index, repo)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
stats->total = git_index_entrycount(index);
|
||||
stats->total = (unsigned int)git_diff_num_deltas(diff) * 3 /* # passes */;
|
||||
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
@ -330,15 +352,33 @@ int git_checkout_index(
|
||||
if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL);
|
||||
/* Checkout is best performed with three passes through the diff.
|
||||
*
|
||||
* 1. First do removes, because we iterate in alphabetical order, thus
|
||||
* a new untracked directory will end up sorted *after* a blob that
|
||||
* should be checked out with the same name.
|
||||
* 2. Then checkout all blobs.
|
||||
* 3. Then checkout all submodules in case a new .gitmodules blob was
|
||||
* checked out during pass #2.
|
||||
*/
|
||||
|
||||
if (!(error = git_diff_foreach(
|
||||
diff, &data, checkout_remove_the_old, NULL, NULL)) &&
|
||||
!(error = git_diff_foreach(
|
||||
diff, &data, checkout_create_the_new, NULL, NULL)))
|
||||
{
|
||||
data.create_submodules = true;
|
||||
error = git_diff_foreach(
|
||||
diff, &data, checkout_create_the_new, NULL, NULL);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (error == GIT_EUSER)
|
||||
error = (data.error != 0) ? data.error : -1;
|
||||
|
||||
cleanup:
|
||||
git_index_free(index);
|
||||
git_diff_list_free(diff);
|
||||
git_buf_free(&workdir);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,7 @@ static int clone_internal(
|
||||
if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) {
|
||||
/* Failed to fetch; clean up */
|
||||
git_repository_free(repo);
|
||||
git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
} else {
|
||||
*out = repo;
|
||||
retcode = 0;
|
||||
|
100
src/diff.c
100
src/diff.c
@ -291,6 +291,36 @@ static int diff_delta__from_two(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static git_diff_delta *diff_delta__last_for_item(
|
||||
git_diff_list *diff,
|
||||
const git_index_entry *item)
|
||||
{
|
||||
git_diff_delta *delta = git_vector_last(&diff->deltas);
|
||||
if (!delta)
|
||||
return NULL;
|
||||
|
||||
switch (delta->status) {
|
||||
case GIT_DELTA_UNMODIFIED:
|
||||
case GIT_DELTA_DELETED:
|
||||
if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)
|
||||
return delta;
|
||||
break;
|
||||
case GIT_DELTA_ADDED:
|
||||
if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
|
||||
return delta;
|
||||
break;
|
||||
case GIT_DELTA_MODIFIED:
|
||||
if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
|
||||
git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
|
||||
return delta;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
|
||||
{
|
||||
size_t len = strlen(prefix);
|
||||
@ -368,6 +398,10 @@ static git_diff_list *git_diff_list_alloc(
|
||||
diff->opts.new_prefix = swap;
|
||||
}
|
||||
|
||||
/* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
|
||||
if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
|
||||
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
|
||||
|
||||
/* only copy pathspec if it is "interesting" so we can test
|
||||
* diff->pathspec.length > 0 to know if it is worth calling
|
||||
* fnmatch as we iterate.
|
||||
@ -537,7 +571,7 @@ static int maybe_modified(
|
||||
|
||||
/* if basic type of file changed, then split into delete and add */
|
||||
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
|
||||
if ((diff->opts.flags & GIT_DIFF_DONT_SPLIT_TYPECHANGE) != 0)
|
||||
if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE) != 0)
|
||||
status = GIT_DELTA_TYPECHANGE;
|
||||
else {
|
||||
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
|
||||
@ -590,7 +624,7 @@ static int maybe_modified(
|
||||
/* grab OID while we are here */
|
||||
if (git_oid_iszero(&nitem->oid)) {
|
||||
const git_oid *sm_oid = git_submodule_wd_oid(sub);
|
||||
if (sub != NULL) {
|
||||
if (sm_oid != NULL) {
|
||||
git_oid_cpy(&noid, sm_oid);
|
||||
use_noid = &noid;
|
||||
}
|
||||
@ -632,6 +666,24 @@ static int git_index_entry_cmp_icase(const void *a, const void *b)
|
||||
return strcasecmp(entry_a->path, entry_b->path);
|
||||
}
|
||||
|
||||
static bool entry_is_prefixed(
|
||||
const git_index_entry *item,
|
||||
git_iterator *prefix_iterator,
|
||||
const git_index_entry *prefix_item)
|
||||
{
|
||||
size_t pathlen;
|
||||
|
||||
if (!prefix_item ||
|
||||
ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path))
|
||||
return false;
|
||||
|
||||
pathlen = strlen(item->path);
|
||||
|
||||
return (item->path[pathlen - 1] == '/' ||
|
||||
prefix_item->path[pathlen] == '\0' ||
|
||||
prefix_item->path[pathlen] == '/');
|
||||
}
|
||||
|
||||
static int diff_from_iterators(
|
||||
git_repository *repo,
|
||||
const git_diff_options *opts, /**< can be NULL for defaults */
|
||||
@ -681,8 +733,24 @@ static int diff_from_iterators(
|
||||
|
||||
/* create DELETED records for old items not matched in new */
|
||||
if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
|
||||
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
|
||||
git_iterator_advance(old_iter, &oitem) < 0)
|
||||
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
|
||||
goto fail;
|
||||
|
||||
/* if we are generating TYPECHANGE records then check for that
|
||||
* instead of just generating a DELETE record
|
||||
*/
|
||||
if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
|
||||
entry_is_prefixed(oitem, new_iter, nitem))
|
||||
{
|
||||
/* this entry has become a tree! convert to TYPECHANGE */
|
||||
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
|
||||
if (last) {
|
||||
last->status = GIT_DELTA_TYPECHANGE;
|
||||
last->new_file.mode = GIT_FILEMODE_TREE;
|
||||
}
|
||||
}
|
||||
|
||||
if (git_iterator_advance(old_iter, &oitem) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -704,8 +772,7 @@ static int diff_from_iterators(
|
||||
* directories and it is not under an ignored directory.
|
||||
*/
|
||||
bool contains_tracked =
|
||||
(oitem &&
|
||||
!ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path));
|
||||
entry_is_prefixed(nitem, old_iter, oitem);
|
||||
bool recurse_untracked =
|
||||
(delta_type == GIT_DELTA_UNTRACKED &&
|
||||
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
|
||||
@ -761,8 +828,25 @@ static int diff_from_iterators(
|
||||
else if (new_iter->type != GIT_ITERATOR_WORKDIR)
|
||||
delta_type = GIT_DELTA_ADDED;
|
||||
|
||||
if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
|
||||
git_iterator_advance(new_iter, &nitem) < 0)
|
||||
if (diff_delta__from_one(diff, delta_type, nitem) < 0)
|
||||
goto fail;
|
||||
|
||||
/* if we are generating TYPECHANGE records then check for that
|
||||
* instead of just generating an ADD/UNTRACKED record
|
||||
*/
|
||||
if (delta_type != GIT_DELTA_IGNORED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
|
||||
entry_is_prefixed(nitem, old_iter, oitem))
|
||||
{
|
||||
/* this entry was a tree! convert to TYPECHANGE */
|
||||
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
|
||||
if (last) {
|
||||
last->status = GIT_DELTA_TYPECHANGE;
|
||||
last->old_file.mode = GIT_FILEMODE_TREE;
|
||||
}
|
||||
}
|
||||
|
||||
if (git_iterator_advance(new_iter, &nitem) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -533,6 +533,11 @@ static int diff_patch_load(
|
||||
if (delta->binary == 1)
|
||||
goto cleanup;
|
||||
|
||||
if (!ctxt->hunk_cb &&
|
||||
!ctxt->data_cb &&
|
||||
(ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)
|
||||
goto cleanup;
|
||||
|
||||
switch (delta->status) {
|
||||
case GIT_DELTA_ADDED:
|
||||
delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
|
||||
@ -698,8 +703,10 @@ static int diff_patch_generate(
|
||||
if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
|
||||
return 0;
|
||||
|
||||
if (ctxt)
|
||||
patch->ctxt = ctxt;
|
||||
if (!ctxt->file_cb && !ctxt->hunk_cb)
|
||||
return 0;
|
||||
|
||||
patch->ctxt = ctxt;
|
||||
|
||||
memset(&xdiff_callback, 0, sizeof(xdiff_callback));
|
||||
xdiff_callback.outf = diff_patch_cb;
|
||||
@ -1360,7 +1367,9 @@ int git_diff_get_patch(
|
||||
if (delta_ptr)
|
||||
*delta_ptr = delta;
|
||||
|
||||
if (!patch_ptr && delta->binary != -1)
|
||||
if (!patch_ptr &&
|
||||
(delta->binary != -1 ||
|
||||
(diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
|
||||
return 0;
|
||||
|
||||
diff_context_init(
|
||||
|
@ -323,10 +323,6 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
|
||||
{
|
||||
git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
|
||||
|
||||
assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
|
||||
|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
|
||||
|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
|
||||
|
||||
if (git_path_isdir(path->ptr) == true) {
|
||||
if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
|
||||
return -1;
|
||||
@ -359,15 +355,24 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
|
||||
int git_futils_rmdir_r(
|
||||
const char *path, const char *base, git_directory_removal_type removal_type)
|
||||
{
|
||||
int error;
|
||||
git_buf p = GIT_BUF_INIT;
|
||||
git_buf fullpath = GIT_BUF_INIT;
|
||||
|
||||
assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
|
||||
|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
|
||||
|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
|
||||
|
||||
/* build path and find "root" where we should start calling mkdir */
|
||||
if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
error = _rmdir_recurs_foreach(&removal_type, &fullpath);
|
||||
|
||||
git_buf_free(&fullpath);
|
||||
|
||||
error = git_buf_sets(&p, path);
|
||||
if (!error)
|
||||
error = _rmdir_recurs_foreach(&removal_type, &p);
|
||||
git_buf_free(&p);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -107,15 +107,17 @@ typedef enum {
|
||||
* Remove path and any files and directories beneath it.
|
||||
*
|
||||
* @param path Path to to top level directory to process.
|
||||
*
|
||||
* @param base Root for relative path.
|
||||
* @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
|
||||
* of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS
|
||||
* to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove
|
||||
* empty directories (no failure on file encounter).
|
||||
* of empty directories (will fail if any file is found),
|
||||
* GIT_DIRREMOVAL_FILES_AND_DIRS to remove a hierarchy of
|
||||
* files and folders,
|
||||
* GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove empty
|
||||
* directories (no failure on file encounter).
|
||||
*
|
||||
* @return 0 on success; -1 on error.
|
||||
*/
|
||||
extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type);
|
||||
extern int git_futils_rmdir_r(const char *path, const char *base, git_directory_removal_type removal_type);
|
||||
|
||||
/**
|
||||
* Create and open a temporary file with a `_git2_` suffix.
|
||||
|
@ -82,7 +82,7 @@ int git_iterator_for_nothing(git_iterator **iter)
|
||||
|
||||
typedef struct tree_iterator_frame tree_iterator_frame;
|
||||
struct tree_iterator_frame {
|
||||
tree_iterator_frame *next;
|
||||
tree_iterator_frame *next, *prev;
|
||||
git_tree *tree;
|
||||
char *start;
|
||||
unsigned int index;
|
||||
@ -91,7 +91,7 @@ struct tree_iterator_frame {
|
||||
typedef struct {
|
||||
git_iterator base;
|
||||
git_repository *repo;
|
||||
tree_iterator_frame *stack;
|
||||
tree_iterator_frame *stack, *tail;
|
||||
git_index_entry entry;
|
||||
git_buf path;
|
||||
bool path_has_filename;
|
||||
@ -119,8 +119,10 @@ static void tree_iterator__pop_frame(tree_iterator *ti)
|
||||
{
|
||||
tree_iterator_frame *tf = ti->stack;
|
||||
ti->stack = tf->next;
|
||||
if (ti->stack != NULL) /* don't free the initial tree */
|
||||
git_tree_free(tf->tree);
|
||||
if (ti->stack != NULL) {
|
||||
git_tree_free(tf->tree); /* don't free the initial tree */
|
||||
ti->stack->prev = NULL; /* disconnect prev */
|
||||
}
|
||||
git__free(tf);
|
||||
}
|
||||
|
||||
@ -221,6 +223,7 @@ static int tree_iterator__expand_tree(tree_iterator *ti)
|
||||
|
||||
tf->next = ti->stack;
|
||||
ti->stack = tf;
|
||||
tf->next->prev = tf;
|
||||
|
||||
te = tree_iterator__tree_entry(ti);
|
||||
}
|
||||
@ -312,7 +315,7 @@ int git_iterator_for_tree_range(
|
||||
ITERATOR_BASE_INIT(ti, tree, TREE);
|
||||
|
||||
ti->repo = repo;
|
||||
ti->stack = tree_iterator__alloc_frame(tree, ti->base.start);
|
||||
ti->stack = ti->tail = tree_iterator__alloc_frame(tree, ti->base.start);
|
||||
|
||||
if ((error = tree_iterator__expand_tree(ti)) < 0)
|
||||
git_iterator_free((git_iterator *)ti);
|
||||
@ -864,6 +867,45 @@ int git_iterator_current_tree_entry(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_iterator_current_parent_tree(
|
||||
git_iterator *iter,
|
||||
const char *parent_path,
|
||||
const git_tree **tree_ptr)
|
||||
{
|
||||
tree_iterator *ti = (tree_iterator *)iter;
|
||||
tree_iterator_frame *tf;
|
||||
const char *scan = parent_path;
|
||||
|
||||
if (iter->type != GIT_ITERATOR_TREE || ti->stack == NULL)
|
||||
goto notfound;
|
||||
|
||||
for (tf = ti->tail; tf != NULL; tf = tf->prev) {
|
||||
const git_tree_entry *te;
|
||||
|
||||
if (!*scan) {
|
||||
*tree_ptr = tf->tree;
|
||||
return 0;
|
||||
}
|
||||
|
||||
te = git_tree_entry_byindex(tf->tree, tf->index);
|
||||
|
||||
if (strncmp(scan, te->filename, te->filename_len) != 0)
|
||||
goto notfound;
|
||||
|
||||
scan += te->filename_len;
|
||||
|
||||
if (*scan) {
|
||||
if (*scan != '/')
|
||||
goto notfound;
|
||||
scan++;
|
||||
}
|
||||
}
|
||||
|
||||
notfound:
|
||||
*tree_ptr = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_iterator_current_is_ignored(git_iterator *iter)
|
||||
{
|
||||
return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
|
||||
|
@ -142,6 +142,9 @@ GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
|
||||
extern int git_iterator_current_tree_entry(
|
||||
git_iterator *iter, const git_tree_entry **tree_entry);
|
||||
|
||||
extern int git_iterator_current_parent_tree(
|
||||
git_iterator *iter, const char *parent_path, const git_tree **tree_ptr);
|
||||
|
||||
extern int git_iterator_current_is_ignored(git_iterator *iter);
|
||||
|
||||
/**
|
||||
|
@ -372,7 +372,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name)
|
||||
goto cleanup;
|
||||
|
||||
if (git_path_isdir(git_buf_cstr(&new_path)) &&
|
||||
(git_futils_rmdir_r(git_buf_cstr(&new_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
|
||||
(git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
|
||||
goto cleanup;
|
||||
|
||||
if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0)
|
||||
|
13
src/refs.c
13
src/refs.c
@ -262,14 +262,15 @@ static int loose_write(git_reference *ref)
|
||||
if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
|
||||
return -1;
|
||||
|
||||
/* Remove a possibly existing empty directory hierarchy
|
||||
/* Remove a possibly existing empty directory hierarchy
|
||||
* which name would collide with the reference name
|
||||
*/
|
||||
if (git_path_isdir(git_buf_cstr(&ref_path)) &&
|
||||
(git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
|
||||
git_buf_free(&ref_path);
|
||||
return -1;
|
||||
}
|
||||
if (git_path_isdir(git_buf_cstr(&ref_path)) &&
|
||||
git_futils_rmdir_r(git_buf_cstr(&ref_path), NULL,
|
||||
GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0) {
|
||||
git_buf_free(&ref_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
|
||||
git_buf_free(&ref_path);
|
||||
|
@ -100,7 +100,7 @@ int git_status_foreach_ext(
|
||||
memset(&diffopt, 0, sizeof(diffopt));
|
||||
memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
|
||||
|
||||
diffopt.flags = GIT_DIFF_DONT_SPLIT_TYPECHANGE;
|
||||
diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
|
||||
|
||||
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||
|
@ -180,9 +180,9 @@ void git_tree__free(git_tree *tree)
|
||||
git__free(tree);
|
||||
}
|
||||
|
||||
const git_oid *git_tree_id(git_tree *c)
|
||||
const git_oid *git_tree_id(const git_tree *c)
|
||||
{
|
||||
return git_object_id((git_object *)c);
|
||||
return git_object_id((const git_object *)c);
|
||||
}
|
||||
|
||||
git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
|
||||
@ -286,7 +286,7 @@ int git_tree__prefix_position(git_tree *tree, const char *path)
|
||||
return at_pos;
|
||||
}
|
||||
|
||||
unsigned int git_tree_entrycount(git_tree *tree)
|
||||
unsigned int git_tree_entrycount(const git_tree *tree)
|
||||
{
|
||||
assert(tree);
|
||||
return (unsigned int)tree->entries.length;
|
||||
|
72
tests-clar/checkout/typechange.c
Normal file
72
tests-clar/checkout/typechange.c
Normal file
@ -0,0 +1,72 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "git2/checkout.h"
|
||||
#include "path.h"
|
||||
#include "posix.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
static const char *g_typechange_oids[] = {
|
||||
"79b9f23e85f55ea36a472a902e875bc1121a94cb",
|
||||
"9bdb75b73836a99e3dbeea640a81de81031fdc29",
|
||||
"0e7ed140b514b8cae23254cb8656fe1674403aff",
|
||||
"9d0235c7a7edc0889a18f97a42ee6db9fe688447",
|
||||
"9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a",
|
||||
"1b63caae4a5ca96f78e8dfefc376c6a39a142475",
|
||||
"6eae26c90e8ccc4d16208972119c40635489c6f0",
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool g_typechange_empty[] = {
|
||||
true, false, false, false, false, false, true, true
|
||||
};
|
||||
|
||||
void test_checkout_typechange__initialize(void)
|
||||
{
|
||||
g_repo = cl_git_sandbox_init("typechanges");
|
||||
|
||||
cl_fixture_sandbox("submod2_target");
|
||||
p_rename("submod2_target/.gitted", "submod2_target/.git");
|
||||
}
|
||||
|
||||
void test_checkout_typechange__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
cl_fixture_cleanup("submod2_target");
|
||||
}
|
||||
|
||||
void test_checkout_typechange__checkout_typechanges(void)
|
||||
{
|
||||
int i;
|
||||
git_object *obj;
|
||||
git_checkout_opts opts = {0};
|
||||
|
||||
opts.checkout_strategy =
|
||||
GIT_CHECKOUT_REMOVE_UNTRACKED |
|
||||
GIT_CHECKOUT_CREATE_MISSING |
|
||||
GIT_CHECKOUT_OVERWRITE_MODIFIED;
|
||||
|
||||
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
|
||||
/* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */
|
||||
|
||||
cl_git_pass(git_checkout_tree(g_repo, obj, &opts, NULL));
|
||||
|
||||
git_object_free(obj);
|
||||
|
||||
if (!g_typechange_empty[i]) {
|
||||
cl_assert(git_path_isdir("typechanges"));
|
||||
cl_assert(git_path_exists("typechanges/a"));
|
||||
cl_assert(git_path_exists("typechanges/b"));
|
||||
cl_assert(git_path_exists("typechanges/c"));
|
||||
cl_assert(git_path_exists("typechanges/d"));
|
||||
cl_assert(git_path_exists("typechanges/e"));
|
||||
} else {
|
||||
cl_assert(git_path_isdir("typechanges"));
|
||||
cl_assert(!git_path_exists("typechanges/a"));
|
||||
cl_assert(!git_path_exists("typechanges/b"));
|
||||
cl_assert(!git_path_exists("typechanges/c"));
|
||||
cl_assert(!git_path_exists("typechanges/d"));
|
||||
cl_assert(!git_path_exists("typechanges/e"));
|
||||
}
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ void test_core_copy__file_in_dir(void)
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert(strlen(content) == (size_t)st.st_size);
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("an_dir", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("an_dir"));
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ void test_core_copy__tree(void)
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("t1", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("t1"));
|
||||
|
||||
/* copy with empty dirs, no links, yes dotfiles, no overwrite */
|
||||
@ -119,8 +119,8 @@ void test_core_copy__tree(void)
|
||||
cl_git_fail(git_path_lstat("t2/c/d/l1", &st));
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("t2", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(!git_path_isdir("t2"));
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r("src", GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
}
|
||||
|
@ -6,11 +6,11 @@
|
||||
static void cleanup_basic_dirs(void *ref)
|
||||
{
|
||||
GIT_UNUSED(ref);
|
||||
git_futils_rmdir_r("d0", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d1", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d2", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d3", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d4", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d0", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d1", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d2", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d3", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("d4", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
void test_core_mkdir__basic(void)
|
||||
@ -56,7 +56,7 @@ void test_core_mkdir__basic(void)
|
||||
static void cleanup_basedir(void *ref)
|
||||
{
|
||||
GIT_UNUSED(ref);
|
||||
git_futils_rmdir_r("base", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("base", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
void test_core_mkdir__with_base(void)
|
||||
@ -108,7 +108,7 @@ static void cleanup_chmod_root(void *ref)
|
||||
git__free(mode);
|
||||
}
|
||||
|
||||
git_futils_rmdir_r("r", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
git_futils_rmdir_r("r", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
|
||||
}
|
||||
|
||||
static void check_mode(mode_t expected, mode_t actual)
|
||||
|
@ -30,7 +30,7 @@ void test_core_rmdir__initialize(void)
|
||||
/* make sure empty dir can be deleted recusively */
|
||||
void test_core_rmdir__delete_recursive(void)
|
||||
{
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
}
|
||||
|
||||
/* make sure non-empty dir cannot be deleted recusively */
|
||||
@ -42,10 +42,10 @@ void test_core_rmdir__fail_to_delete_non_empty_dir(void)
|
||||
|
||||
cl_git_mkfile(git_buf_cstr(&file), "dummy");
|
||||
|
||||
cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
|
||||
cl_must_pass(p_unlink(file.ptr));
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
|
||||
|
||||
git_buf_free(&file);
|
||||
}
|
||||
@ -58,10 +58,10 @@ void test_core_rmdir__can_skip__non_empty_dir(void)
|
||||
|
||||
cl_git_mkfile(git_buf_cstr(&file), "dummy");
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
|
||||
cl_assert(git_path_exists(git_buf_cstr(&file)) == true);
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_assert(git_path_exists(empty_tmp_dir) == false);
|
||||
|
||||
git_buf_free(&file);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "diff_helpers.h"
|
||||
#include "iterator.h"
|
||||
#include "tree.h"
|
||||
|
||||
void test_diff_iterator__initialize(void)
|
||||
{
|
||||
@ -237,6 +238,103 @@ void test_diff_iterator__tree_range_empty_2(void)
|
||||
NULL, ".aaa_empty_before", 0, NULL);
|
||||
}
|
||||
|
||||
static void check_tree_entry(
|
||||
git_iterator *i,
|
||||
const char *oid,
|
||||
const char *oid_p,
|
||||
const char *oid_pp,
|
||||
const char *oid_ppp)
|
||||
{
|
||||
const git_index_entry *ie;
|
||||
const git_tree_entry *te;
|
||||
const git_tree *tree;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_iterator_current_tree_entry(i, &te));
|
||||
cl_assert(te);
|
||||
cl_assert(git_oid_streq(&te->oid, oid) == 0);
|
||||
|
||||
cl_git_pass(git_iterator_current(i, &ie));
|
||||
cl_git_pass(git_buf_sets(&path, ie->path));
|
||||
|
||||
if (oid_p) {
|
||||
git_buf_rtruncate_at_char(&path, '/');
|
||||
cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
|
||||
cl_assert(tree);
|
||||
cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0);
|
||||
}
|
||||
|
||||
if (oid_pp) {
|
||||
git_buf_rtruncate_at_char(&path, '/');
|
||||
cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
|
||||
cl_assert(tree);
|
||||
cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0);
|
||||
}
|
||||
|
||||
if (oid_ppp) {
|
||||
git_buf_rtruncate_at_char(&path, '/');
|
||||
cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
|
||||
cl_assert(tree);
|
||||
cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0);
|
||||
}
|
||||
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
void test_diff_iterator__tree_special_functions(void)
|
||||
{
|
||||
git_tree *t;
|
||||
git_iterator *i;
|
||||
const git_index_entry *entry;
|
||||
git_repository *repo = cl_git_sandbox_init("attr");
|
||||
int cases = 0;
|
||||
const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e";
|
||||
|
||||
t = resolve_commit_oid_to_tree(
|
||||
repo, "24fa9a9fc4e202313e24b648087495441dab432b");
|
||||
cl_assert(t != NULL);
|
||||
|
||||
cl_git_pass(git_iterator_for_tree_range(&i, repo, t, NULL, NULL));
|
||||
cl_git_pass(git_iterator_current(i, &entry));
|
||||
|
||||
while (entry != NULL) {
|
||||
if (strcmp(entry->path, "sub/file") == 0) {
|
||||
cases++;
|
||||
check_tree_entry(
|
||||
i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
|
||||
"ecb97df2a174987475ac816e3847fc8e9f6c596b",
|
||||
rootoid, NULL);
|
||||
}
|
||||
else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) {
|
||||
cases++;
|
||||
check_tree_entry(
|
||||
i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
|
||||
"4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485",
|
||||
"ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid);
|
||||
}
|
||||
else if (strcmp(entry->path, "subdir/.gitattributes") == 0) {
|
||||
cases++;
|
||||
check_tree_entry(
|
||||
i, "99eae476896f4907224978b88e5ecaa6c5bb67a9",
|
||||
"9fb40b6675dde60b5697afceae91b66d908c02d9",
|
||||
rootoid, NULL);
|
||||
}
|
||||
else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) {
|
||||
cases++;
|
||||
check_tree_entry(
|
||||
i, "dccada462d3df8ac6de596fb8c896aba9344f941",
|
||||
"2929de282ce999e95183aedac6451d3384559c4b",
|
||||
rootoid, NULL);
|
||||
}
|
||||
|
||||
cl_git_pass(git_iterator_advance(i, &entry));
|
||||
}
|
||||
|
||||
cl_assert_equal_i(4, cases);
|
||||
git_iterator_free(i);
|
||||
git_tree_free(t);
|
||||
}
|
||||
|
||||
/* -- INDEX ITERATOR TESTS -- */
|
||||
|
||||
static void index_iterator_test(
|
||||
|
@ -49,7 +49,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolut
|
||||
assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
|
||||
|
||||
git_buf_free(&full_path);
|
||||
cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
}
|
||||
|
||||
void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void)
|
||||
@ -65,5 +65,5 @@ void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_fi
|
||||
assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
|
||||
|
||||
git_buf_free(&full_path);
|
||||
cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ void test_repo_discover__0(void)
|
||||
ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path);
|
||||
ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path);
|
||||
|
||||
cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
git_repository_free(repo);
|
||||
git_buf_free(&ceiling_dirs_buf);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ void test_repo_open__cleanup(void)
|
||||
cl_git_sandbox_cleanup();
|
||||
|
||||
if (git_path_isdir("alternate"))
|
||||
git_futils_rmdir_r("alternate", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
git_futils_rmdir_r("alternate", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
}
|
||||
|
||||
void test_repo_open__bare_empty_repo(void)
|
||||
@ -202,8 +202,8 @@ void test_repo_open__bad_gitlinks(void)
|
||||
cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
|
||||
}
|
||||
|
||||
git_futils_rmdir_r("invalid", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
git_futils_rmdir_r("invalid2", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
git_futils_rmdir_r("invalid", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
git_futils_rmdir_r("invalid2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
|
Binary file not shown.
@ -1,32 +1,43 @@
|
||||
This is a test repo for libgit2 where tree entries have type changes
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
The key types that could be found in tree entries are:
|
||||
|
||||
1 - GIT_FILEMODE_NEW = 0000000
|
||||
2 - GIT_FILEMODE_TREE = 0040000
|
||||
3 - GIT_FILEMODE_BLOB = 0100644
|
||||
4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
|
||||
5 - GIT_FILEMODE_LINK = 0120000
|
||||
6 - GIT_FILEMODE_COMMIT = 0160000
|
||||
1. GIT_FILEMODE_NEW = 0000000 (i.e. file does not exist)
|
||||
2. GIT_FILEMODE_TREE = 0040000
|
||||
3. GIT_FILEMODE_BLOB = 0100644
|
||||
4. GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
|
||||
5. GIT_FILEMODE_LINK = 0120000
|
||||
6. GIT_FILEMODE_COMMIT = 0160000
|
||||
|
||||
I will try to have every type of transition somewhere in the history
|
||||
of this repo.
|
||||
|
||||
Commits
|
||||
-------
|
||||
Initial commit - a(1) b(1) c(1) d(1) e(1)
|
||||
79b9f23e85f55ea36a472a902e875bc1121a94cb
|
||||
Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)
|
||||
9bdb75b73836a99e3dbeea640a81de81031fdc29
|
||||
Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)
|
||||
0e7ed140b514b8cae23254cb8656fe1674403aff
|
||||
Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)
|
||||
9d0235c7a7edc0889a18f97a42ee6db9fe688447
|
||||
Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)
|
||||
9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a
|
||||
Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)
|
||||
1b63caae4a5ca96f78e8dfefc376c6a39a142475
|
||||
Matches "Changes #1" except README.md
|
||||
Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)
|
||||
6eae26c90e8ccc4d16208972119c40635489c6f0
|
||||
Matches "Initial commit" except README.md and .gitmodules
|
||||
|
||||
* `a(1--1) b(1--1) c(1--1) d(1--1) e(1--1)`
|
||||
**Initial commit**<br>
|
||||
`79b9f23e85f55ea36a472a902e875bc1121a94cb`
|
||||
* `a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)`
|
||||
**Create content**<br>
|
||||
`9bdb75b73836a99e3dbeea640a81de81031fdc29`
|
||||
* `a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)`
|
||||
**Changes #1**<br>
|
||||
`0e7ed140b514b8cae23254cb8656fe1674403aff`
|
||||
* `a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)`
|
||||
**Changes #2**<br>
|
||||
`9d0235c7a7edc0889a18f97a42ee6db9fe688447`
|
||||
* `a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)`
|
||||
**Changes #3**<br>
|
||||
`9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a`
|
||||
* `a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)`
|
||||
**Changes #4**<br>
|
||||
`1b63caae4a5ca96f78e8dfefc376c6a39a142475`<br>
|
||||
Matches **Changes #1** except README.md
|
||||
* `a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)`
|
||||
**Changes #5**<br>
|
||||
`6eae26c90e8ccc4d16208972119c40635489c6f0`<br>
|
||||
Matches **Initial commit** except README.md and .gitmodules
|
||||
|
@ -71,7 +71,7 @@ static int remove_file_cb(void *data, git_buf *file)
|
||||
return 0;
|
||||
|
||||
if (git_path_isdir(filename))
|
||||
cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
else
|
||||
cl_git_pass(p_unlink(git_buf_cstr(file)));
|
||||
|
||||
@ -314,7 +314,7 @@ void test_status_worktree__issue_592_3(void)
|
||||
repo = cl_git_sandbox_init("issue_592");
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
|
||||
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
|
||||
|
||||
@ -344,7 +344,7 @@ void test_status_worktree__issue_592_5(void)
|
||||
repo = cl_git_sandbox_init("issue_592");
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
|
||||
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
|
||||
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
|
||||
|
@ -50,7 +50,7 @@ void test_submodule_status__ignore_none(void)
|
||||
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_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
|
||||
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
|
||||
|
||||
@ -135,7 +135,7 @@ void test_submodule_status__ignore_untracked(void)
|
||||
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_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
|
||||
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
|
||||
|
||||
@ -195,7 +195,7 @@ void test_submodule_status__ignore_dirty(void)
|
||||
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_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
|
||||
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
|
||||
|
||||
@ -255,7 +255,7 @@ void test_submodule_status__ignore_all(void)
|
||||
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_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
|
||||
|
||||
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user