mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-28 15:58:29 +00:00
Merge pull request #1439 from arrbee/recurse-ignored-dirs
Several diff and status fixes
This commit is contained in:
commit
86d24ce40c
@ -123,7 +123,7 @@ typedef enum {
|
||||
* will be marked with only a single entry in the diff list; this flag
|
||||
* adds all files under the directory as IGNORED entries, too.
|
||||
*/
|
||||
GIT_DIFF_RECURSE_IGNORED_DIRS = (1 << 10),
|
||||
GIT_DIFF_RECURSE_IGNORED_DIRS = (1 << 18),
|
||||
} git_diff_option_t;
|
||||
|
||||
/**
|
||||
|
@ -51,7 +51,7 @@ GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str);
|
||||
*
|
||||
* @param out oid structure the result is written into.
|
||||
* @param str input hex string; must be at least 4 characters
|
||||
* long and null-terminated.
|
||||
* long and null-terminated.
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str);
|
||||
|
@ -127,20 +127,30 @@ typedef enum {
|
||||
* will.
|
||||
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
|
||||
* will be treated as a literal path, and not as a pathspec.
|
||||
* - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of
|
||||
* ignored directories should be included in the status. This is like
|
||||
* doing `git ls-files -o -i --exclude-standard` with core git.
|
||||
*
|
||||
* Calling `git_status_foreach()` is like calling the extended version
|
||||
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
|
||||
* and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS.
|
||||
* and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS. Those options are bundled
|
||||
* together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline.
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
|
||||
GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
|
||||
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
|
||||
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
|
||||
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
|
||||
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5),
|
||||
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0),
|
||||
GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1),
|
||||
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2),
|
||||
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3),
|
||||
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),
|
||||
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),
|
||||
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
|
||||
} git_status_opt_t;
|
||||
|
||||
#define GIT_STATUS_OPT_DEFAULTS \
|
||||
(GIT_STATUS_OPT_INCLUDE_IGNORED | \
|
||||
GIT_STATUS_OPT_INCLUDE_UNTRACKED | \
|
||||
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS)
|
||||
|
||||
/**
|
||||
* Options to control how `git_status_foreach_ext()` will issue callbacks.
|
||||
*
|
||||
|
96
src/diff.c
96
src/diff.c
@ -12,6 +12,9 @@
|
||||
#include "filter.h"
|
||||
#include "pathspec.h"
|
||||
|
||||
#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
|
||||
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
|
||||
|
||||
static git_diff_delta *diff_delta__alloc(
|
||||
git_diff_list *diff,
|
||||
git_delta_t status,
|
||||
@ -29,7 +32,7 @@ static git_diff_delta *diff_delta__alloc(
|
||||
|
||||
delta->new_file.path = delta->old_file.path;
|
||||
|
||||
if (diff->opts.flags & GIT_DIFF_REVERSE) {
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
|
||||
switch (status) {
|
||||
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
|
||||
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
|
||||
@ -63,17 +66,22 @@ static int diff_delta__from_one(
|
||||
int notify_res;
|
||||
|
||||
if (status == GIT_DELTA_IGNORED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
|
||||
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
|
||||
return 0;
|
||||
|
||||
if (status == GIT_DELTA_UNTRACKED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
|
||||
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
|
||||
return 0;
|
||||
|
||||
if (entry->mode == GIT_FILEMODE_COMMIT &&
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
|
||||
return 0;
|
||||
|
||||
if (!git_pathspec_match_path(
|
||||
&diff->pathspec, entry->path,
|
||||
(diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
|
||||
(diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec))
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
|
||||
&matched_pathspec))
|
||||
return 0;
|
||||
|
||||
delta = diff_delta__alloc(diff, status, entry->path);
|
||||
@ -124,10 +132,15 @@ static int diff_delta__from_two(
|
||||
int notify_res;
|
||||
|
||||
if (status == GIT_DELTA_UNMODIFIED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
|
||||
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
|
||||
return 0;
|
||||
|
||||
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
|
||||
if (old_entry->mode == GIT_FILEMODE_COMMIT &&
|
||||
new_entry->mode == GIT_FILEMODE_COMMIT &&
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
|
||||
return 0;
|
||||
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
|
||||
uint32_t temp_mode = old_mode;
|
||||
const git_index_entry *temp_entry = old_entry;
|
||||
old_entry = new_entry;
|
||||
@ -149,7 +162,7 @@ static int diff_delta__from_two(
|
||||
delta->new_file.mode = new_mode;
|
||||
|
||||
if (new_oid) {
|
||||
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0)
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
|
||||
git_oid_cpy(&delta->old_file.oid, new_oid);
|
||||
else
|
||||
git_oid_cpy(&delta->new_file.oid, new_oid);
|
||||
@ -316,14 +329,14 @@ static git_diff_list *git_diff_list_alloc(
|
||||
if (!diff->opts.old_prefix || !diff->opts.new_prefix)
|
||||
goto fail;
|
||||
|
||||
if (diff->opts.flags & GIT_DIFF_REVERSE) {
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
|
||||
const char *swap = diff->opts.old_prefix;
|
||||
diff->opts.old_prefix = diff->opts.new_prefix;
|
||||
diff->opts.new_prefix = swap;
|
||||
}
|
||||
|
||||
/* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
|
||||
if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
|
||||
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
|
||||
|
||||
return diff;
|
||||
@ -452,8 +465,9 @@ static int maybe_modified(
|
||||
|
||||
if (!git_pathspec_match_path(
|
||||
&diff->pathspec, oitem->path,
|
||||
(diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
|
||||
(diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec))
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
|
||||
&matched_pathspec))
|
||||
return 0;
|
||||
|
||||
/* on platforms with no symlinks, preserve mode of existing symlinks */
|
||||
@ -478,7 +492,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_INCLUDE_TYPECHANGE) != 0)
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
|
||||
status = GIT_DELTA_TYPECHANGE;
|
||||
else {
|
||||
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
|
||||
@ -515,7 +529,7 @@ static int maybe_modified(
|
||||
int err;
|
||||
git_submodule *sub;
|
||||
|
||||
if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
|
||||
status = GIT_DELTA_UNMODIFIED;
|
||||
else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {
|
||||
if (err == GIT_EEXISTS)
|
||||
@ -543,6 +557,11 @@ static int maybe_modified(
|
||||
}
|
||||
}
|
||||
|
||||
/* if mode is GITLINK and submodules are ignored, then skip */
|
||||
else if (S_ISGITLINK(nmode) &&
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
|
||||
status = GIT_DELTA_UNMODIFIED;
|
||||
|
||||
/* if we got here and decided that the files are modified, but we
|
||||
* haven't calculated the OID of the new item, then calculate it now
|
||||
*/
|
||||
@ -553,7 +572,13 @@ static int maybe_modified(
|
||||
return -1;
|
||||
use_noid = &noid;
|
||||
}
|
||||
if (omode == nmode && git_oid_equal(&oitem->oid, use_noid))
|
||||
|
||||
/* if oid matches, then mark unmodified (except submodules, where
|
||||
* the filesystem content may be modified even if the oid still
|
||||
* matches between the index and the workdir HEAD)
|
||||
*/
|
||||
if (omode == nmode && !S_ISGITLINK(omode) &&
|
||||
git_oid_equal(&oitem->oid, use_noid))
|
||||
status = GIT_DELTA_UNMODIFIED;
|
||||
}
|
||||
|
||||
@ -626,7 +651,7 @@ int git_diff__from_iterators(
|
||||
if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
|
||||
goto fail;
|
||||
|
||||
if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {
|
||||
if (git_iterator_set_ignore_case(old_iter, true) < 0 ||
|
||||
git_iterator_set_ignore_case(new_iter, true) < 0)
|
||||
goto fail;
|
||||
@ -648,7 +673,7 @@ int git_diff__from_iterators(
|
||||
/* 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 &&
|
||||
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
|
||||
entry_is_prefixed(diff, nitem, oitem))
|
||||
{
|
||||
/* this entry has become a tree! convert to TYPECHANGE */
|
||||
@ -663,7 +688,7 @@ int git_diff__from_iterators(
|
||||
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
|
||||
*/
|
||||
if (S_ISDIR(nitem->mode) &&
|
||||
!(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS))
|
||||
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
|
||||
{
|
||||
if (git_iterator_advance(&nitem, new_iter) < 0)
|
||||
goto fail;
|
||||
@ -691,27 +716,35 @@ int git_diff__from_iterators(
|
||||
* it or if the user requested the contents of untracked
|
||||
* directories and it is not under an ignored directory.
|
||||
*/
|
||||
bool recurse_untracked =
|
||||
bool recurse_into_dir =
|
||||
(delta_type == GIT_DELTA_UNTRACKED &&
|
||||
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
|
||||
(delta_type == GIT_DELTA_IGNORED &&
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
|
||||
|
||||
/* do not advance into directories that contain a .git file */
|
||||
if (!contains_oitem && recurse_untracked) {
|
||||
if (!contains_oitem && recurse_into_dir) {
|
||||
git_buf *full = NULL;
|
||||
if (git_iterator_current_workdir_path(&full, new_iter) < 0)
|
||||
goto fail;
|
||||
if (git_path_contains_dir(full, DOT_GIT))
|
||||
recurse_untracked = false;
|
||||
recurse_into_dir = false;
|
||||
}
|
||||
|
||||
if (contains_oitem || recurse_untracked) {
|
||||
/* if this directory is ignored, remember it as the
|
||||
* "ignore_prefix" for processing contained items
|
||||
*/
|
||||
if (delta_type == GIT_DELTA_UNTRACKED &&
|
||||
git_iterator_current_is_ignored(new_iter))
|
||||
git_buf_sets(&ignore_prefix, nitem->path);
|
||||
/* if directory is ignored, remember ignore_prefix */
|
||||
if ((contains_oitem || recurse_into_dir) &&
|
||||
delta_type == GIT_DELTA_UNTRACKED &&
|
||||
git_iterator_current_is_ignored(new_iter))
|
||||
{
|
||||
git_buf_sets(&ignore_prefix, nitem->path);
|
||||
delta_type = GIT_DELTA_IGNORED;
|
||||
|
||||
/* skip recursion if we've just learned this is ignored */
|
||||
if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))
|
||||
recurse_into_dir = false;
|
||||
}
|
||||
|
||||
if (contains_oitem || recurse_into_dir) {
|
||||
/* advance into directory */
|
||||
error = git_iterator_advance_into(&nitem, new_iter);
|
||||
|
||||
@ -744,7 +777,8 @@ int git_diff__from_iterators(
|
||||
* checked before container directory exclusions are used to
|
||||
* skip the file.
|
||||
*/
|
||||
else if (delta_type == GIT_DELTA_IGNORED) {
|
||||
else if (delta_type == GIT_DELTA_IGNORED &&
|
||||
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {
|
||||
if (git_iterator_advance(&nitem, new_iter) < 0)
|
||||
goto fail;
|
||||
continue; /* ignored parent directory, so skip completely */
|
||||
@ -763,7 +797,7 @@ int git_diff__from_iterators(
|
||||
* instead of just generating an ADDED/UNTRACKED record
|
||||
*/
|
||||
if (delta_type != GIT_DELTA_IGNORED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
|
||||
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
|
||||
contains_oitem)
|
||||
{
|
||||
/* this entry was prefixed with a tree - make TYPECHANGE */
|
||||
|
65
src/status.c
65
src/status.c
@ -80,22 +80,37 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
|
||||
typedef struct {
|
||||
git_status_cb cb;
|
||||
void *payload;
|
||||
const git_status_options *opts;
|
||||
} status_user_callback;
|
||||
|
||||
static int status_invoke_cb(
|
||||
git_diff_delta *i2h, git_diff_delta *w2i, void *payload)
|
||||
git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
|
||||
{
|
||||
status_user_callback *usercb = payload;
|
||||
const char *path = NULL;
|
||||
unsigned int status = 0;
|
||||
|
||||
if (w2i) {
|
||||
path = w2i->old_file.path;
|
||||
status |= workdir_delta2status(w2i->status);
|
||||
if (i2w) {
|
||||
path = i2w->old_file.path;
|
||||
status |= workdir_delta2status(i2w->status);
|
||||
}
|
||||
if (i2h) {
|
||||
path = i2h->old_file.path;
|
||||
status |= index_delta2status(i2h->status);
|
||||
if (h2i) {
|
||||
path = h2i->old_file.path;
|
||||
status |= index_delta2status(h2i->status);
|
||||
}
|
||||
|
||||
/* if excluding submodules and this is a submodule everywhere */
|
||||
if (usercb->opts &&
|
||||
(usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
|
||||
{
|
||||
bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED);
|
||||
bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED);
|
||||
bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED);
|
||||
|
||||
if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) &&
|
||||
(!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) &&
|
||||
(!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return usercb->cb(path, status, usercb->payload);
|
||||
@ -109,7 +124,7 @@ int git_status_foreach_ext(
|
||||
{
|
||||
int err = 0;
|
||||
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
|
||||
git_diff_list *idx2head = NULL, *wd2idx = NULL;
|
||||
git_diff_list *head2idx = NULL, *idx2wd = NULL;
|
||||
git_tree *head = NULL;
|
||||
git_status_show_t show =
|
||||
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
|
||||
@ -142,34 +157,42 @@ int git_status_foreach_ext(
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
|
||||
if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
|
||||
/* TODO: support EXCLUDE_SUBMODULES flag */
|
||||
if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
|
||||
if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
|
||||
|
||||
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
|
||||
(err = git_diff_tree_to_index(&idx2head, repo, head, NULL, &diffopt)) < 0)
|
||||
goto cleanup;
|
||||
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
|
||||
err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
|
||||
if (err < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
|
||||
(err = git_diff_index_to_workdir(&wd2idx, repo, NULL, &diffopt)) < 0)
|
||||
goto cleanup;
|
||||
if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
|
||||
err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
|
||||
if (err < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
usercb.cb = cb;
|
||||
usercb.payload = payload;
|
||||
usercb.opts = opts;
|
||||
|
||||
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
|
||||
if ((err = git_diff__paired_foreach(
|
||||
idx2head, NULL, status_invoke_cb, &usercb)) < 0)
|
||||
head2idx, NULL, status_invoke_cb, &usercb)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
git_diff_list_free(idx2head);
|
||||
idx2head = NULL;
|
||||
git_diff_list_free(head2idx);
|
||||
head2idx = NULL;
|
||||
}
|
||||
|
||||
err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb);
|
||||
err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
|
||||
|
||||
cleanup:
|
||||
git_tree_free(head);
|
||||
git_diff_list_free(idx2head);
|
||||
git_diff_list_free(wd2idx);
|
||||
git_diff_list_free(head2idx);
|
||||
git_diff_list_free(idx2wd);
|
||||
|
||||
if (err == GIT_EUSER)
|
||||
giterr_clear();
|
||||
|
@ -694,7 +694,7 @@ int git_submodule_open(
|
||||
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 (!error) {
|
||||
if (!git_reference_name_to_id(
|
||||
&submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
|
||||
submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
|
||||
|
@ -42,6 +42,16 @@ int diff_file_cb(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int diff_print_file_cb(
|
||||
const git_diff_delta *delta,
|
||||
float progress,
|
||||
void *payload)
|
||||
{
|
||||
fprintf(stderr, "%c %s\n",
|
||||
git_diff_status_char(delta->status), delta->old_file.path);
|
||||
return diff_file_cb(delta, progress, payload);
|
||||
}
|
||||
|
||||
int diff_hunk_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_range *range,
|
||||
|
@ -30,6 +30,11 @@ extern int diff_file_cb(
|
||||
float progress,
|
||||
void *cb_data);
|
||||
|
||||
extern int diff_print_file_cb(
|
||||
const git_diff_delta *delta,
|
||||
float progress,
|
||||
void *cb_data);
|
||||
|
||||
extern int diff_hunk_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_range *range,
|
||||
|
@ -953,16 +953,31 @@ void test_diff_workdir__submodules(void)
|
||||
cl_git_pass(git_diff_foreach(
|
||||
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
|
||||
|
||||
/* the following differs from "git diff 873585" by two "untracked" file
|
||||
* because the diff list includes the "not" and "not-submodule" dirs which
|
||||
* are not displayed in the text diff.
|
||||
/* so "git diff 873585" returns:
|
||||
* M .gitmodules
|
||||
* A just_a_dir/contents
|
||||
* A just_a_file
|
||||
* A sm_added_and_uncommited
|
||||
* A sm_changed_file
|
||||
* A sm_changed_head
|
||||
* A sm_changed_index
|
||||
* A sm_changed_untracked_file
|
||||
* M sm_missing_commits
|
||||
* A sm_unchanged
|
||||
* which is a little deceptive because of the difference between the
|
||||
* "git diff <treeish>" results from "git_diff_tree_to_workdir". The
|
||||
* only significant difference is that those Added items will show up
|
||||
* as Untracked items in the pure libgit2 diff.
|
||||
*
|
||||
* Then add in the two extra untracked items "not" and "not-submodule"
|
||||
* to get the 12 files reported here.
|
||||
*/
|
||||
|
||||
cl_assert_equal_i(11, exp.files);
|
||||
cl_assert_equal_i(12, exp.files);
|
||||
|
||||
cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
|
||||
cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
|
||||
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
|
||||
cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
|
||||
cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
|
||||
cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]);
|
||||
|
||||
|
@ -199,23 +199,26 @@ void test_status_ignore__subdirectories(void)
|
||||
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "ignore_me"));
|
||||
cl_assert(ignored);
|
||||
|
||||
|
||||
/* So, interestingly, as per the comment in diff_from_iterators() the
|
||||
* following file is ignored, but in a way so that it does not show up
|
||||
* in status even if INCLUDE_IGNORED is used. This actually matches
|
||||
* core git's behavior - if you follow these steps and try running "git
|
||||
* status -uall --ignored" then the following file and directory will
|
||||
* not show up in the output at all.
|
||||
/* I've changed libgit2 so that the behavior here now differs from
|
||||
* core git but seems to make more sense. In core git, the following
|
||||
* items are skipped completed, even if --ignored is passed to status.
|
||||
* It you mirror these steps and run "git status -uall --ignored" then
|
||||
* you will not see "test/ignore_me/" in the results.
|
||||
*
|
||||
* However, we had a couple reports of this as a bug, plus there is a
|
||||
* similar circumstance where we were differing for core git when you
|
||||
* used a rooted path for an ignore, so I changed this behavior.
|
||||
*/
|
||||
|
||||
cl_git_pass(
|
||||
git_futils_mkdir_r("empty_standard_repo/test/ignore_me", NULL, 0775));
|
||||
cl_git_pass(git_futils_mkdir_r(
|
||||
"empty_standard_repo/test/ignore_me", NULL, 0775));
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!");
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/test/ignore_me/file2", "Me, too!");
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
|
||||
cl_assert_equal_i(2, st.count);
|
||||
cl_assert_equal_i(3, st.count);
|
||||
|
||||
cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file"));
|
||||
cl_assert(st.status == GIT_STATUS_IGNORED);
|
||||
@ -225,6 +228,91 @@ void test_status_ignore__subdirectories(void)
|
||||
cl_assert(ignored);
|
||||
}
|
||||
|
||||
void test_status_ignore__subdirectories_recursion(void)
|
||||
{
|
||||
/* Let's try again with recursing into ignored dirs turned on */
|
||||
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
||||
status_entry_counts counts;
|
||||
static const char *paths_r[] = {
|
||||
".gitignore",
|
||||
"ignore_also/file",
|
||||
"ignore_me",
|
||||
"test/ignore_me/and_me/file",
|
||||
"test/ignore_me/file",
|
||||
"test/ignore_me/file2",
|
||||
};
|
||||
static const unsigned int statuses_r[] = {
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
};
|
||||
static const char *paths_nr[] = {
|
||||
".gitignore",
|
||||
"ignore_also/",
|
||||
"ignore_me",
|
||||
"test/ignore_me/",
|
||||
};
|
||||
static const unsigned int statuses_nr[] = {
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_IGNORED,
|
||||
};
|
||||
|
||||
g_repo = cl_git_sandbox_init("empty_standard_repo");
|
||||
|
||||
cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n");
|
||||
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/ignore_me", "I'm going to be ignored!");
|
||||
cl_git_pass(git_futils_mkdir_r(
|
||||
"empty_standard_repo/test/ignore_me", NULL, 0775));
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!");
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/test/ignore_me/file2", "Me, too!");
|
||||
cl_git_pass(git_futils_mkdir_r(
|
||||
"empty_standard_repo/test/ignore_me/and_me", NULL, 0775));
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/test/ignore_me/and_me/file", "Deeply ignored");
|
||||
cl_git_pass(git_futils_mkdir_r(
|
||||
"empty_standard_repo/ignore_also", NULL, 0775));
|
||||
cl_git_mkfile(
|
||||
"empty_standard_repo/ignore_also/file", "I'm going to be ignored!");
|
||||
|
||||
memset(&counts, 0x0, sizeof(status_entry_counts));
|
||||
counts.expected_entry_count = 6;
|
||||
counts.expected_paths = paths_r;
|
||||
counts.expected_statuses = statuses_r;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
|
||||
|
||||
cl_git_pass(git_status_foreach_ext(
|
||||
g_repo, &opts, cb_status__normal, &counts));
|
||||
|
||||
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||
|
||||
|
||||
memset(&counts, 0x0, sizeof(status_entry_counts));
|
||||
counts.expected_entry_count = 4;
|
||||
counts.expected_paths = paths_nr;
|
||||
counts.expected_statuses = statuses_nr;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS;
|
||||
|
||||
cl_git_pass(git_status_foreach_ext(
|
||||
g_repo, &opts, cb_status__normal, &counts));
|
||||
|
||||
cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
|
||||
cl_assert_equal_i(0, counts.wrong_status_flags_count);
|
||||
cl_assert_equal_i(0, counts.wrong_sorted_path);
|
||||
}
|
||||
|
||||
void test_status_ignore__adding_internal_ignores(void)
|
||||
{
|
||||
int ignored;
|
||||
|
@ -47,3 +47,51 @@ int cb_status__single(const char *p, unsigned int s, void *payload)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cb_status__print(
|
||||
const char *path, unsigned int status_flags, void *payload)
|
||||
{
|
||||
char istatus = ' ', wstatus = ' ';
|
||||
int icount = 0, wcount = 0;
|
||||
|
||||
if (status_flags & GIT_STATUS_INDEX_NEW) {
|
||||
istatus = 'A'; icount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_INDEX_MODIFIED) {
|
||||
istatus = 'M'; icount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_INDEX_DELETED) {
|
||||
istatus = 'D'; icount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_INDEX_RENAMED) {
|
||||
istatus = 'R'; icount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) {
|
||||
istatus = 'T'; icount++;
|
||||
}
|
||||
|
||||
if (status_flags & GIT_STATUS_WT_NEW) {
|
||||
wstatus = 'A'; wcount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_WT_MODIFIED) {
|
||||
wstatus = 'M'; wcount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_WT_DELETED) {
|
||||
wstatus = 'D'; wcount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_WT_TYPECHANGE) {
|
||||
wstatus = 'T'; wcount++;
|
||||
}
|
||||
if (status_flags & GIT_STATUS_IGNORED) {
|
||||
wstatus = 'I'; wcount++;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%c%c %s (%d/%d%s)\n",
|
||||
istatus, wstatus, path, icount, wcount,
|
||||
(icount > 1 || wcount > 1) ? " INVALID COMBO" : "");
|
||||
|
||||
if (payload)
|
||||
*((int *)payload) += 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -30,4 +30,8 @@ typedef struct {
|
||||
|
||||
extern int cb_status__single(const char *p, unsigned int s, void *payload);
|
||||
|
||||
/* cb_status__print takes optional payload of "int *" */
|
||||
|
||||
extern int cb_status__print(const char *p, unsigned int s, void *payload);
|
||||
|
||||
#endif
|
||||
|
@ -71,31 +71,34 @@ static unsigned int expected_status[] = {
|
||||
GIT_STATUS_WT_NEW
|
||||
};
|
||||
|
||||
static int
|
||||
cb_status__match(const char *p, unsigned int s, void *payload)
|
||||
static int cb_status__match(const char *p, unsigned int s, void *payload)
|
||||
{
|
||||
volatile int *index = (int *)payload;
|
||||
status_entry_counts *counts = payload;
|
||||
int idx = counts->entry_count++;
|
||||
|
||||
cl_assert_equal_s(expected_files[*index], p);
|
||||
cl_assert(expected_status[*index] == s);
|
||||
(*index)++;
|
||||
cl_assert_equal_s(counts->expected_paths[idx], p);
|
||||
cl_assert(counts->expected_statuses[idx] == s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_status_submodules__1(void)
|
||||
{
|
||||
int index = 0;
|
||||
status_entry_counts counts;
|
||||
|
||||
cl_assert(git_path_isdir("submodules/.git"));
|
||||
cl_assert(git_path_isdir("submodules/testrepo/.git"));
|
||||
cl_assert(git_path_isfile("submodules/.gitmodules"));
|
||||
|
||||
memset(&counts, 0, sizeof(counts));
|
||||
counts.expected_paths = expected_files;
|
||||
counts.expected_statuses = expected_status;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach(g_repo, cb_status__match, &index)
|
||||
git_status_foreach(g_repo, cb_status__match, &counts)
|
||||
);
|
||||
|
||||
cl_assert_equal_i(6, index);
|
||||
cl_assert_equal_i(6, counts.entry_count);
|
||||
}
|
||||
|
||||
void test_status_submodules__single_file(void)
|
||||
@ -104,3 +107,113 @@ void test_status_submodules__single_file(void)
|
||||
cl_git_pass( git_status_file(&status, g_repo, "testrepo") );
|
||||
cl_assert(!status);
|
||||
}
|
||||
|
||||
void test_status_submodules__moved_head(void)
|
||||
{
|
||||
git_submodule *sm;
|
||||
git_repository *smrepo;
|
||||
git_oid oid;
|
||||
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
||||
status_entry_counts counts;
|
||||
static const char *expected_files_with_sub[] = {
|
||||
".gitmodules",
|
||||
"added",
|
||||
"deleted",
|
||||
"ignored",
|
||||
"modified",
|
||||
"testrepo",
|
||||
"untracked"
|
||||
};
|
||||
static unsigned int expected_status_with_sub[] = {
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_INDEX_NEW,
|
||||
GIT_STATUS_INDEX_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_NEW
|
||||
};
|
||||
|
||||
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
|
||||
cl_git_pass(git_submodule_open(&smrepo, sm));
|
||||
|
||||
/* move submodule HEAD to c47800c7266a2be04c571c04d5a6614691ea99bd */
|
||||
cl_git_pass(
|
||||
git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
|
||||
cl_git_pass(git_repository_set_head_detached(smrepo, &oid));
|
||||
|
||||
/* first do a normal status, which should now include the submodule */
|
||||
|
||||
memset(&counts, 0, sizeof(counts));
|
||||
counts.expected_paths = expected_files_with_sub;
|
||||
counts.expected_statuses = expected_status_with_sub;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
|
||||
cl_assert_equal_i(7, counts.entry_count);
|
||||
|
||||
/* try again with EXCLUDE_SUBMODULES which should skip it */
|
||||
|
||||
memset(&counts, 0, sizeof(counts));
|
||||
counts.expected_paths = expected_files;
|
||||
counts.expected_statuses = expected_status;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
|
||||
cl_assert_equal_i(6, counts.entry_count);
|
||||
}
|
||||
|
||||
void test_status_submodules__dirty_workdir_only(void)
|
||||
{
|
||||
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
|
||||
status_entry_counts counts;
|
||||
static const char *expected_files_with_sub[] = {
|
||||
".gitmodules",
|
||||
"added",
|
||||
"deleted",
|
||||
"ignored",
|
||||
"modified",
|
||||
"testrepo",
|
||||
"untracked"
|
||||
};
|
||||
static unsigned int expected_status_with_sub[] = {
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_INDEX_NEW,
|
||||
GIT_STATUS_INDEX_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_NEW
|
||||
};
|
||||
|
||||
cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
|
||||
cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");
|
||||
|
||||
/* first do a normal status, which should now include the submodule */
|
||||
|
||||
memset(&counts, 0, sizeof(counts));
|
||||
counts.expected_paths = expected_files_with_sub;
|
||||
counts.expected_statuses = expected_status_with_sub;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
|
||||
cl_assert_equal_i(7, counts.entry_count);
|
||||
|
||||
/* try again with EXCLUDE_SUBMODULES which should skip it */
|
||||
|
||||
memset(&counts, 0, sizeof(counts));
|
||||
counts.expected_paths = expected_files;
|
||||
counts.expected_statuses = expected_status;
|
||||
|
||||
opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
|
||||
cl_assert_equal_i(6, counts.entry_count);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user