Commit 86d24ce4 by Vicent Martí

Merge pull request #1439 from arrbee/recurse-ignored-dirs

Several diff and status fixes
parents d2a4a54b ccfa6805
...@@ -123,7 +123,7 @@ typedef enum { ...@@ -123,7 +123,7 @@ typedef enum {
* will be marked with only a single entry in the diff list; this flag * will be marked with only a single entry in the diff list; this flag
* adds all files under the directory as IGNORED entries, too. * 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; } git_diff_option_t;
/** /**
......
...@@ -127,20 +127,30 @@ typedef enum { ...@@ -127,20 +127,30 @@ typedef enum {
* will. * will.
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
* will be treated as a literal path, and not as a pathspec. * 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 * Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, * 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 { typedef enum {
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0), GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0),
GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1), GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1),
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2),
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3), GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3),
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5), GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
} git_status_opt_t; } 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. * Options to control how `git_status_foreach_ext()` will issue callbacks.
* *
......
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
#include "filter.h" #include "filter.h"
#include "pathspec.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( static git_diff_delta *diff_delta__alloc(
git_diff_list *diff, git_diff_list *diff,
git_delta_t status, git_delta_t status,
...@@ -29,7 +32,7 @@ static git_diff_delta *diff_delta__alloc( ...@@ -29,7 +32,7 @@ static git_diff_delta *diff_delta__alloc(
delta->new_file.path = delta->old_file.path; 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) { switch (status) {
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
...@@ -63,17 +66,22 @@ static int diff_delta__from_one( ...@@ -63,17 +66,22 @@ static int diff_delta__from_one(
int notify_res; int notify_res;
if (status == GIT_DELTA_IGNORED && if (status == GIT_DELTA_IGNORED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
return 0; return 0;
if (status == GIT_DELTA_UNTRACKED && 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; return 0;
if (!git_pathspec_match_path( if (!git_pathspec_match_path(
&diff->pathspec, entry->path, &diff->pathspec, entry->path,
(diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
(diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec)) DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
&matched_pathspec))
return 0; return 0;
delta = diff_delta__alloc(diff, status, entry->path); delta = diff_delta__alloc(diff, status, entry->path);
...@@ -124,10 +132,15 @@ static int diff_delta__from_two( ...@@ -124,10 +132,15 @@ static int diff_delta__from_two(
int notify_res; int notify_res;
if (status == GIT_DELTA_UNMODIFIED && if (status == GIT_DELTA_UNMODIFIED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 0; 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; uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry; const git_index_entry *temp_entry = old_entry;
old_entry = new_entry; old_entry = new_entry;
...@@ -149,7 +162,7 @@ static int diff_delta__from_two( ...@@ -149,7 +162,7 @@ static int diff_delta__from_two(
delta->new_file.mode = new_mode; delta->new_file.mode = new_mode;
if (new_oid) { 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); git_oid_cpy(&delta->old_file.oid, new_oid);
else else
git_oid_cpy(&delta->new_file.oid, new_oid); git_oid_cpy(&delta->new_file.oid, new_oid);
...@@ -316,14 +329,14 @@ static git_diff_list *git_diff_list_alloc( ...@@ -316,14 +329,14 @@ static git_diff_list *git_diff_list_alloc(
if (!diff->opts.old_prefix || !diff->opts.new_prefix) if (!diff->opts.old_prefix || !diff->opts.new_prefix)
goto fail; 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; const char *swap = diff->opts.old_prefix;
diff->opts.old_prefix = diff->opts.new_prefix; diff->opts.old_prefix = diff->opts.new_prefix;
diff->opts.new_prefix = swap; diff->opts.new_prefix = swap;
} }
/* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ /* 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; diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
return diff; return diff;
...@@ -452,8 +465,9 @@ static int maybe_modified( ...@@ -452,8 +465,9 @@ static int maybe_modified(
if (!git_pathspec_match_path( if (!git_pathspec_match_path(
&diff->pathspec, oitem->path, &diff->pathspec, oitem->path,
(diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
(diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec)) DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),
&matched_pathspec))
return 0; return 0;
/* on platforms with no symlinks, preserve mode of existing symlinks */ /* on platforms with no symlinks, preserve mode of existing symlinks */
...@@ -478,7 +492,7 @@ static int maybe_modified( ...@@ -478,7 +492,7 @@ static int maybe_modified(
/* if basic type of file changed, then split into delete and add */ /* if basic type of file changed, then split into delete and add */
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { 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; status = GIT_DELTA_TYPECHANGE;
else { else {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
...@@ -515,7 +529,7 @@ static int maybe_modified( ...@@ -515,7 +529,7 @@ static int maybe_modified(
int err; int err;
git_submodule *sub; 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; status = GIT_DELTA_UNMODIFIED;
else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) { else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {
if (err == GIT_EEXISTS) if (err == GIT_EEXISTS)
...@@ -543,6 +557,11 @@ static int maybe_modified( ...@@ -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 /* 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 * haven't calculated the OID of the new item, then calculate it now
*/ */
...@@ -553,7 +572,13 @@ static int maybe_modified( ...@@ -553,7 +572,13 @@ static int maybe_modified(
return -1; return -1;
use_noid = &noid; 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; status = GIT_DELTA_UNMODIFIED;
} }
...@@ -626,7 +651,7 @@ int git_diff__from_iterators( ...@@ -626,7 +651,7 @@ int git_diff__from_iterators(
if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0) if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
goto fail; 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 || if (git_iterator_set_ignore_case(old_iter, true) < 0 ||
git_iterator_set_ignore_case(new_iter, true) < 0) git_iterator_set_ignore_case(new_iter, true) < 0)
goto fail; goto fail;
...@@ -648,7 +673,7 @@ int git_diff__from_iterators( ...@@ -648,7 +673,7 @@ int git_diff__from_iterators(
/* if we are generating TYPECHANGE records then check for that /* if we are generating TYPECHANGE records then check for that
* instead of just generating a DELETE record * 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)) entry_is_prefixed(diff, nitem, oitem))
{ {
/* this entry has become a tree! convert to TYPECHANGE */ /* this entry has become a tree! convert to TYPECHANGE */
...@@ -663,7 +688,7 @@ int git_diff__from_iterators( ...@@ -663,7 +688,7 @@ int git_diff__from_iterators(
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them... * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
*/ */
if (S_ISDIR(nitem->mode) && 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) if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail; goto fail;
...@@ -691,27 +716,35 @@ int git_diff__from_iterators( ...@@ -691,27 +716,35 @@ int git_diff__from_iterators(
* it or if the user requested the contents of untracked * it or if the user requested the contents of untracked
* directories and it is not under an ignored directory. * directories and it is not under an ignored directory.
*/ */
bool recurse_untracked = bool recurse_into_dir =
(delta_type == GIT_DELTA_UNTRACKED && (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 */ /* 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; git_buf *full = NULL;
if (git_iterator_current_workdir_path(&full, new_iter) < 0) if (git_iterator_current_workdir_path(&full, new_iter) < 0)
goto fail; goto fail;
if (git_path_contains_dir(full, DOT_GIT)) if (git_path_contains_dir(full, DOT_GIT))
recurse_untracked = false; recurse_into_dir = false;
} }
if (contains_oitem || recurse_untracked) { /* if directory is ignored, remember ignore_prefix */
/* if this directory is ignored, remember it as the if ((contains_oitem || recurse_into_dir) &&
* "ignore_prefix" for processing contained items delta_type == GIT_DELTA_UNTRACKED &&
*/
if (delta_type == GIT_DELTA_UNTRACKED &&
git_iterator_current_is_ignored(new_iter)) git_iterator_current_is_ignored(new_iter))
{
git_buf_sets(&ignore_prefix, nitem->path); 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 */ /* advance into directory */
error = git_iterator_advance_into(&nitem, new_iter); error = git_iterator_advance_into(&nitem, new_iter);
...@@ -744,7 +777,8 @@ int git_diff__from_iterators( ...@@ -744,7 +777,8 @@ int git_diff__from_iterators(
* checked before container directory exclusions are used to * checked before container directory exclusions are used to
* skip the file. * 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) if (git_iterator_advance(&nitem, new_iter) < 0)
goto fail; goto fail;
continue; /* ignored parent directory, so skip completely */ continue; /* ignored parent directory, so skip completely */
...@@ -763,7 +797,7 @@ int git_diff__from_iterators( ...@@ -763,7 +797,7 @@ int git_diff__from_iterators(
* instead of just generating an ADDED/UNTRACKED record * instead of just generating an ADDED/UNTRACKED record
*/ */
if (delta_type != GIT_DELTA_IGNORED && 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) contains_oitem)
{ {
/* this entry was prefixed with a tree - make TYPECHANGE */ /* this entry was prefixed with a tree - make TYPECHANGE */
......
...@@ -80,22 +80,37 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) ...@@ -80,22 +80,37 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
typedef struct { typedef struct {
git_status_cb cb; git_status_cb cb;
void *payload; void *payload;
const git_status_options *opts;
} status_user_callback; } status_user_callback;
static int status_invoke_cb( 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; status_user_callback *usercb = payload;
const char *path = NULL; const char *path = NULL;
unsigned int status = 0; unsigned int status = 0;
if (w2i) { if (i2w) {
path = w2i->old_file.path; path = i2w->old_file.path;
status |= workdir_delta2status(w2i->status); status |= workdir_delta2status(i2w->status);
} }
if (i2h) { if (h2i) {
path = i2h->old_file.path; path = h2i->old_file.path;
status |= index_delta2status(i2h->status); 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); return usercb->cb(path, status, usercb->payload);
...@@ -109,7 +124,7 @@ int git_status_foreach_ext( ...@@ -109,7 +124,7 @@ int git_status_foreach_ext(
{ {
int err = 0; int err = 0;
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; 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_tree *head = NULL;
git_status_show_t show = git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
...@@ -142,34 +157,42 @@ int git_status_foreach_ext( ...@@ -142,34 +157,42 @@ int git_status_foreach_ext(
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; 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 (show != GIT_STATUS_SHOW_WORKDIR_ONLY && if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
(err = git_diff_tree_to_index(&idx2head, repo, head, NULL, &diffopt)) < 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
if (err < 0)
goto cleanup; goto cleanup;
}
if (show != GIT_STATUS_SHOW_INDEX_ONLY && if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
(err = git_diff_index_to_workdir(&wd2idx, repo, NULL, &diffopt)) < 0) err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
if (err < 0)
goto cleanup; goto cleanup;
}
usercb.cb = cb; usercb.cb = cb;
usercb.payload = payload; usercb.payload = payload;
usercb.opts = opts;
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
if ((err = git_diff__paired_foreach( if ((err = git_diff__paired_foreach(
idx2head, NULL, status_invoke_cb, &usercb)) < 0) head2idx, NULL, status_invoke_cb, &usercb)) < 0)
goto cleanup; goto cleanup;
git_diff_list_free(idx2head); git_diff_list_free(head2idx);
idx2head = NULL; 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: cleanup:
git_tree_free(head); git_tree_free(head);
git_diff_list_free(idx2head); git_diff_list_free(head2idx);
git_diff_list_free(wd2idx); git_diff_list_free(idx2wd);
if (err == GIT_EUSER) if (err == GIT_EUSER)
giterr_clear(); giterr_clear();
......
...@@ -694,7 +694,7 @@ int git_submodule_open( ...@@ -694,7 +694,7 @@ int git_submodule_open(
git_buf_free(&path); git_buf_free(&path);
/* if we have opened the submodule successfully, let's grab the HEAD OID */ /* 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( if (!git_reference_name_to_id(
&submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) &submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
......
...@@ -42,6 +42,16 @@ int diff_file_cb( ...@@ -42,6 +42,16 @@ int diff_file_cb(
return 0; 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( int diff_hunk_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_range *range, const git_diff_range *range,
......
...@@ -30,6 +30,11 @@ extern int diff_file_cb( ...@@ -30,6 +30,11 @@ extern int diff_file_cb(
float progress, float progress,
void *cb_data); void *cb_data);
extern int diff_print_file_cb(
const git_diff_delta *delta,
float progress,
void *cb_data);
extern int diff_hunk_cb( extern int diff_hunk_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_range *range, const git_diff_range *range,
......
...@@ -953,16 +953,31 @@ void test_diff_workdir__submodules(void) ...@@ -953,16 +953,31 @@ void test_diff_workdir__submodules(void)
cl_git_pass(git_diff_foreach( cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
/* the following differs from "git diff 873585" by two "untracked" file /* so "git diff 873585" returns:
* because the diff list includes the "not" and "not-submodule" dirs which * M .gitmodules
* are not displayed in the text diff. * 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_ADDED]);
cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); 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(0, exp.file_status[GIT_DELTA_IGNORED]);
cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]); cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]);
......
...@@ -199,23 +199,26 @@ void test_status_ignore__subdirectories(void) ...@@ -199,23 +199,26 @@ void test_status_ignore__subdirectories(void)
cl_git_pass(git_status_should_ignore(&ignored, g_repo, "ignore_me")); cl_git_pass(git_status_should_ignore(&ignored, g_repo, "ignore_me"));
cl_assert(ignored); cl_assert(ignored);
/* I've changed libgit2 so that the behavior here now differs from
/* So, interestingly, as per the comment in diff_from_iterators() the * core git but seems to make more sense. In core git, the following
* following file is ignored, but in a way so that it does not show up * items are skipped completed, even if --ignored is passed to status.
* in status even if INCLUDE_IGNORED is used. This actually matches * It you mirror these steps and run "git status -uall --ignored" then
* core git's behavior - if you follow these steps and try running "git * you will not see "test/ignore_me/" in the results.
* status -uall --ignored" then the following file and directory will *
* not show up in the output at all. * 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(
cl_git_pass( "empty_standard_repo/test/ignore_me", NULL, 0775));
git_futils_mkdir_r("empty_standard_repo/test/ignore_me", NULL, 0775));
cl_git_mkfile( cl_git_mkfile(
"empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); "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)); memset(&st, 0, sizeof(st));
cl_git_pass(git_status_foreach(g_repo, cb_status__single, &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_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file"));
cl_assert(st.status == GIT_STATUS_IGNORED); cl_assert(st.status == GIT_STATUS_IGNORED);
...@@ -225,6 +228,91 @@ void test_status_ignore__subdirectories(void) ...@@ -225,6 +228,91 @@ void test_status_ignore__subdirectories(void)
cl_assert(ignored); 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) void test_status_ignore__adding_internal_ignores(void)
{ {
int ignored; int ignored;
......
...@@ -47,3 +47,51 @@ int cb_status__single(const char *p, unsigned int s, void *payload) ...@@ -47,3 +47,51 @@ int cb_status__single(const char *p, unsigned int s, void *payload)
return 0; 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 { ...@@ -30,4 +30,8 @@ typedef struct {
extern int cb_status__single(const char *p, unsigned int s, void *payload); 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 #endif
...@@ -71,31 +71,34 @@ static unsigned int expected_status[] = { ...@@ -71,31 +71,34 @@ static unsigned int expected_status[] = {
GIT_STATUS_WT_NEW GIT_STATUS_WT_NEW
}; };
static int static int cb_status__match(const char *p, unsigned int s, void *payload)
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_equal_s(counts->expected_paths[idx], p);
cl_assert(expected_status[*index] == s); cl_assert(counts->expected_statuses[idx] == s);
(*index)++;
return 0; return 0;
} }
void test_status_submodules__1(void) 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/.git"));
cl_assert(git_path_isdir("submodules/testrepo/.git")); cl_assert(git_path_isdir("submodules/testrepo/.git"));
cl_assert(git_path_isfile("submodules/.gitmodules")); 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( 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) void test_status_submodules__single_file(void)
...@@ -104,3 +107,113 @@ 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_git_pass( git_status_file(&status, g_repo, "testrepo") );
cl_assert(!status); 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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment