summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/diff.c108
-rw-r--r--src/diff.h10
-rw-r--r--src/diff_patch.c6
-rw-r--r--src/diff_print.c109
-rw-r--r--src/diff_tform.c2
-rw-r--r--src/index.c5
-rw-r--r--src/status.c386
-rw-r--r--src/status.h23
-rw-r--r--src/util.c22
-rw-r--r--src/util.h2
10 files changed, 500 insertions, 173 deletions
diff --git a/src/diff.c b/src/diff.c
index 3bfe149e3..fa2c5c71d 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -134,6 +134,7 @@ static int diff_delta__from_two(
{
git_diff_delta *delta;
int notify_res;
+ const char *canonical_path = old_entry->path;
if (status == GIT_DELTA_UNMODIFIED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
@@ -153,7 +154,7 @@ static int diff_delta__from_two(
new_mode = temp_mode;
}
- delta = diff_delta__alloc(diff, status, old_entry->path);
+ delta = diff_delta__alloc(diff, status, canonical_path);
GITERR_CHECK_ALLOC(delta);
git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
@@ -253,6 +254,13 @@ int git_diff_delta__cmp(const void *a, const void *b)
return val ? val : ((int)da->status - (int)db->status);
}
+int git_diff_delta__casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta)
{
@@ -356,6 +364,8 @@ static git_diff_list *diff_list_alloc(
diff->strncomp = git__strncasecmp;
diff->pfxcomp = git__prefixcmp_icase;
diff->entrycomp = git_index_entry__cmp_icase;
+
+ diff->deltas._cmp = git_diff_delta__casecmp;
}
return diff;
@@ -1119,17 +1129,40 @@ int git_diff_tree_to_index(
const git_diff_options *opts)
{
int error = 0;
+ bool reset_index_ignore_case = false;
assert(diff && repo);
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error;
+ if (index->ignore_case) {
+ git_index__set_ignore_case(index, false);
+ reset_index_ignore_case = true;
+ }
+
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
git_iterator_for_index(&b, index, 0, pfx, pfx)
);
+ if (reset_index_ignore_case) {
+ git_index__set_ignore_case(index, true);
+
+ if (!error) {
+ git_diff_list *d = *diff;
+
+ d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+ d->strcomp = git__strcasecmp;
+ d->strncomp = git__strncasecmp;
+ d->pfxcomp = git__prefixcmp_icase;
+ d->entrycomp = git_index_entry__cmp_icase;
+
+ d->deltas._cmp = git_diff_delta__casecmp;
+ git_vector_sort(&d->deltas);
+ }
+ }
+
return error;
}
@@ -1196,51 +1229,78 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
}
int git_diff__paired_foreach(
- git_diff_list *idx2head,
- git_diff_list *wd2idx,
- int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ git_diff_list *head2idx,
+ git_diff_list *idx2wd,
+ int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
void *payload)
{
int cmp;
- git_diff_delta *i2h, *w2i;
+ git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max;
- int (*strcomp)(const char *, const char *);
-
- i_max = idx2head ? idx2head->deltas.length : 0;
- j_max = wd2idx ? wd2idx->deltas.length : 0;
+ int (*strcomp)(const char *, const char *) = git__strcmp;
+ bool icase_mismatch;
- /* Get appropriate strcmp function */
- strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL;
+ i_max = head2idx ? head2idx->deltas.length : 0;
+ j_max = idx2wd ? idx2wd->deltas.length : 0;
- /* Assert both iterators use matching ignore-case. If this function ever
- * supports merging diffs that are not sorted by the same function, then
- * it will need to spool and sort on one of the results before merging
- */
- if (idx2head && wd2idx) {
- assert(idx2head->strcomp == wd2idx->strcomp);
+ /* At some point, tree-to-index diffs will probably never ignore case,
+ * even if that isn't true now. Index-to-workdir diffs may or may not
+ * ignore case, but the index filename for the idx2wd diff should
+ * still be using the canonical case-preserving name.
+ *
+ * Therefore the main thing we need to do here is make sure the diffs
+ * are traversed in a compatible order. To do this, we temporarily
+ * resort a mismatched diff to get the order correct.
+ */
+ icase_mismatch =
+ (head2idx != NULL && idx2wd != NULL &&
+ ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE));
+
+ /* force case-sensitive delta sort */
+ if (icase_mismatch) {
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
+ head2idx->deltas._cmp = git_diff_delta__cmp;
+ git_vector_sort(&head2idx->deltas);
+ } else {
+ idx2wd->deltas._cmp = git_diff_delta__cmp;
+ git_vector_sort(&idx2wd->deltas);
+ }
}
+ else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
+ strcomp = git__strcasecmp;
for (i = 0, j = 0; i < i_max || j < j_max; ) {
- i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
- w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
+ h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
+ i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
- cmp = !w2i ? -1 : !i2h ? 1 :
- strcomp(i2h->old_file.path, w2i->old_file.path);
+ cmp = !i2w ? -1 : !h2i ? 1 :
+ strcomp(h2i->new_file.path, i2w->old_file.path);
if (cmp < 0) {
- if (cb(i2h, NULL, payload))
+ if (cb(h2i, NULL, payload))
return GIT_EUSER;
i++;
} else if (cmp > 0) {
- if (cb(NULL, w2i, payload))
+ if (cb(NULL, i2w, payload))
return GIT_EUSER;
j++;
} else {
- if (cb(i2h, w2i, payload))
+ if (cb(h2i, i2w, payload))
return GIT_EUSER;
i++; j++;
}
}
+ /* restore case-insensitive delta sort */
+ if (icase_mismatch) {
+ if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
+ head2idx->deltas._cmp = git_diff_delta__casecmp;
+ git_vector_sort(&head2idx->deltas);
+ } else {
+ idx2wd->deltas._cmp = git_diff_delta__casecmp;
+ git_vector_sort(&idx2wd->deltas);
+ }
+ }
+
return 0;
}
diff --git a/src/diff.h b/src/diff.h
index ad12e7731..6ef03ee7c 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -74,6 +74,7 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff);
extern int git_diff_delta__cmp(const void *a, const void *b);
+extern int git_diff_delta__casecmp(const void *a, const void *b);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
@@ -94,17 +95,16 @@ extern int git_diff__paired_foreach(
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
void *payload);
-int git_diff_find_similar__hashsig_for_file(
+extern int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p);
-int git_diff_find_similar__hashsig_for_buf(
+extern int git_diff_find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
-void git_diff_find_similar__hashsig_free(void *sig, void *payload);
+extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
-int git_diff_find_similar__calc_similarity(
+extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload);
-
#endif
diff --git a/src/diff_patch.c b/src/diff_patch.c
index a1e1fe84c..40cb3371a 100644
--- a/src/diff_patch.c
+++ b/src/diff_patch.c
@@ -175,10 +175,11 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output)
goto cleanup;
}
- /* if we were previously missing an oid, reassess UNMODIFIED state */
+ /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */
if (incomplete_data &&
patch->ofile.file.mode == patch->nfile.file.mode &&
- git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid))
+ git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid) &&
+ patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->delta->status = GIT_DELTA_UNMODIFIED;
cleanup:
@@ -284,6 +285,7 @@ int git_diff_foreach(
git_xdiff_init(&xo, &diff->opts);
git_vector_foreach(&diff->deltas, idx, patch.delta) {
+
/* check flags against patch status */
if (git_diff_delta__should_skip(&diff->opts, patch.delta))
continue;
diff --git a/src/diff_print.c b/src/diff_print.c
index 244aa6e1d..6fc7425eb 100644
--- a/src/diff_print.c
+++ b/src/diff_print.c
@@ -41,7 +41,7 @@ static int diff_print_info_init(
return 0;
}
-static char pick_suffix(int mode)
+static char diff_pick_suffix(int mode)
{
if (S_ISDIR(mode))
return '/';
@@ -76,10 +76,11 @@ static int callback_error(void)
return GIT_EUSER;
}
-static int print_compact(
+static int diff_print_one_compact(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
+ git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
GIT_UNUSED(progress);
@@ -87,34 +88,35 @@ static int print_compact(
if (code == ' ')
return 0;
- old_suffix = pick_suffix(delta->old_file.mode);
- new_suffix = pick_suffix(delta->new_file.mode);
+ old_suffix = diff_pick_suffix(delta->old_file.mode);
+ new_suffix = diff_pick_suffix(delta->new_file.mode);
- git_buf_clear(pi->buf);
+ git_buf_clear(out);
if (delta->old_file.path != delta->new_file.path &&
pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)
- git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
+ git_buf_printf(out, "%c\t%s%c -> %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode &&
delta->old_file.mode != 0 && delta->new_file.mode != 0)
- git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
+ git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code,
delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);
else if (old_suffix != ' ')
- git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
+ git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
else
- git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path);
+ git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
- if (git_buf_oom(pi->buf))
+ if (git_buf_oom(out))
return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ git_buf_cstr(out), git_buf_len(out), pi->payload))
return callback_error();
return 0;
}
+/* print a git_diff_list to a print callback in compact format */
int git_diff_print_compact(
git_diff_list *diff,
git_diff_data_cb print_cb,
@@ -125,17 +127,18 @@ int git_diff_print_compact(
diff_print_info pi;
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
- error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
+ error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi);
git_buf_free(&buf);
return error;
}
-static int print_raw(
+static int diff_print_one_raw(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
+ git_buf *out = pi->buf;
char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
@@ -144,36 +147,37 @@ static int print_raw(
if (code == ' ')
return 0;
- git_buf_clear(pi->buf);
+ git_buf_clear(out);
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
git_buf_printf(
- pi->buf, ":%06o %06o %s... %s... %c",
+ out, ":%06o %06o %s... %s... %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
if (delta->similarity > 0)
- git_buf_printf(pi->buf, "%03u", delta->similarity);
+ git_buf_printf(out, "%03u", delta->similarity);
if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED)
git_buf_printf(
- pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
+ out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
else
git_buf_printf(
- pi->buf, "\t%s\n", delta->old_file.path ?
+ out, "\t%s\n", delta->old_file.path ?
delta->old_file.path : delta->new_file.path);
- if (git_buf_oom(pi->buf))
+ if (git_buf_oom(out))
return -1;
if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
- git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
+ git_buf_cstr(out), git_buf_len(out), pi->payload))
return callback_error();
return 0;
}
+/* print a git_diff_list to a print callback in raw output format */
int git_diff_print_raw(
git_diff_list *diff,
git_diff_data_cb print_cb,
@@ -184,15 +188,16 @@ int git_diff_print_raw(
diff_print_info pi;
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
- error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi);
+ error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi);
git_buf_free(&buf);
return error;
}
-static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
+static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
{
+ git_buf *out = pi->buf;
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
@@ -200,27 +205,27 @@ static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
/* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
- git_buf_printf(pi->buf, "index %s..%s %o\n",
+ git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode);
} else {
if (delta->old_file.mode == 0) {
- git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode);
+ git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
} else if (delta->new_file.mode == 0) {
- git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
} else {
- git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode);
- git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode);
+ git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
}
- git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);
+ git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
}
- if (git_buf_oom(pi->buf))
+ if (git_buf_oom(out))
return -1;
return 0;
}
-static int print_patch_file(
+static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
@@ -247,7 +252,7 @@ static int print_patch_file(
git_buf_clear(pi->buf);
git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
- if (print_oid_range(pi, delta) < 0)
+ if (diff_print_oid_range(pi, delta) < 0)
return -1;
if (git_oid_iszero(&delta->old_file.oid)) {
@@ -288,7 +293,7 @@ static int print_patch_file(
return 0;
}
-static int print_patch_hunk(
+static int diff_print_patch_hunk(
const git_diff_delta *d,
const git_diff_range *r,
const char *header,
@@ -311,7 +316,7 @@ static int print_patch_hunk(
return 0;
}
-static int print_patch_line(
+static int diff_print_patch_line(
const git_diff_delta *delta,
const git_diff_range *range,
char line_origin, /* GIT_DIFF_LINE value from above */
@@ -343,6 +348,7 @@ static int print_patch_line(
return 0;
}
+/* print a git_diff_list to an output callback in patch format */
int git_diff_print_patch(
git_diff_list *diff,
git_diff_data_cb print_cb,
@@ -354,27 +360,15 @@ int git_diff_print_patch(
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
error = git_diff_foreach(
- diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
+ diff, diff_print_patch_file, diff_print_patch_hunk,
+ diff_print_patch_line, &pi);
git_buf_free(&buf);
return error;
}
-
-static int print_to_buffer_cb(
- const git_diff_delta *delta,
- const git_diff_range *range,
- char line_origin,
- const char *content,
- size_t content_len,
- void *payload)
-{
- git_buf *output = payload;
- GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
- return git_buf_put(output, content, content_len);
-}
-
+/* print a git_diff_patch to an output callback */
int git_diff_patch_print(
git_diff_patch *patch,
git_diff_data_cb print_cb,
@@ -389,13 +383,28 @@ int git_diff_patch_print(
if (!(error = diff_print_info_init(
&pi, &temp, git_diff_patch__diff(patch), print_cb, payload)))
error = git_diff_patch__invoke_callbacks(
- patch, print_patch_file, print_patch_hunk, print_patch_line, &pi);
+ patch, diff_print_patch_file, diff_print_patch_hunk,
+ diff_print_patch_line, &pi);
git_buf_free(&temp);
return error;
}
+static int diff_print_to_buffer_cb(
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len,
+ void *payload)
+{
+ git_buf *output = payload;
+ GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
+ return git_buf_put(output, content, content_len);
+}
+
+/* print a git_diff_patch to a string buffer */
int git_diff_patch_to_str(
char **string,
git_diff_patch *patch)
@@ -403,7 +412,7 @@ int git_diff_patch_to_str(
int error;
git_buf output = GIT_BUF_INIT;
- error = git_diff_patch_print(patch, print_to_buffer_cb, &output);
+ error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output);
/* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
* meaning a memory allocation failure, so just map to -1...
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 94fa035f2..64746e7dd 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -483,7 +483,7 @@ static int similarity_measure(
if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
return 0;
- /* if exact match is requested, force calculation of missing OIDs */
+ /* if exact match is requested, force calculation of missing OIDs now */
if (exact_match) {
if (git_oid_iszero(&a_file->oid) &&
diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
diff --git a/src/index.c b/src/index.c
index 4f0c70135..560a257e7 100644
--- a/src/index.c
+++ b/src/index.c
@@ -734,8 +734,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
if (!replace || !existing)
return git_vector_insert(&index->entries, entry);
- /* exists, replace it */
- git__free((*existing)->path);
+ /* exists, replace it (preserving name from existing entry) */
+ git__free(entry->path);
+ entry->path = (*existing)->path;
git__free(*existing);
*existing = entry;
diff --git a/src/status.c b/src/status.c
index 712e0d515..375100a89 100644
--- a/src/status.c
+++ b/src/status.c
@@ -11,6 +11,7 @@
#include "hash.h"
#include "vector.h"
#include "tree.h"
+#include "status.h"
#include "git2/status.h"
#include "repository.h"
#include "ignore.h"
@@ -19,11 +20,11 @@
#include "git2/diff.h"
#include "diff.h"
-static unsigned int index_delta2status(git_delta_t index_status)
+static unsigned int index_delta2status(const git_diff_delta *head2idx)
{
- unsigned int st = GIT_STATUS_CURRENT;
+ git_status_t st = GIT_STATUS_CURRENT;
- switch (index_status) {
+ switch (head2idx->status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_COPIED:
st = GIT_STATUS_INDEX_NEW;
@@ -36,6 +37,9 @@ static unsigned int index_delta2status(git_delta_t index_status)
break;
case GIT_DELTA_RENAMED:
st = GIT_STATUS_INDEX_RENAMED;
+
+ if (!git_oid_equal(&head2idx->old_file.oid, &head2idx->new_file.oid))
+ st |= GIT_STATUS_INDEX_MODIFIED;
break;
case GIT_DELTA_TYPECHANGE:
st = GIT_STATUS_INDEX_TYPECHANGE;
@@ -47,13 +51,13 @@ static unsigned int index_delta2status(git_delta_t index_status)
return st;
}
-static unsigned int workdir_delta2status(git_delta_t workdir_status)
+static unsigned int workdir_delta2status(
+ git_diff_list *diff, git_diff_delta *idx2wd)
{
- unsigned int st = GIT_STATUS_CURRENT;
+ git_status_t st = GIT_STATUS_CURRENT;
- switch (workdir_status) {
+ switch (idx2wd->status) {
case GIT_DELTA_ADDED:
- case GIT_DELTA_RENAMED:
case GIT_DELTA_COPIED:
case GIT_DELTA_UNTRACKED:
st = GIT_STATUS_WT_NEW;
@@ -67,6 +71,31 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
case GIT_DELTA_IGNORED:
st = GIT_STATUS_IGNORED;
break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_WT_RENAMED;
+
+ if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) {
+ /* if OIDs don't match, we might need to calculate them now to
+ * discern between RENAMED vs RENAMED+MODIFED
+ */
+ if (git_oid_iszero(&idx2wd->old_file.oid) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode,
+ idx2wd->old_file.size, &idx2wd->old_file.oid))
+ idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (git_oid_iszero(&idx2wd->new_file.oid) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode,
+ idx2wd->new_file.size, &idx2wd->new_file.oid))
+ idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;
+
+ if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid))
+ st |= GIT_STATUS_WT_MODIFIED;
+ }
+ break;
case GIT_DELTA_TYPECHANGE:
st = GIT_STATUS_WT_TYPECHANGE;
break;
@@ -77,142 +106,321 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
return st;
}
-typedef struct {
- git_status_cb cb;
- void *payload;
- const git_status_options *opts;
-} status_user_callback;
-
-static int status_invoke_cb(
- git_diff_delta *h2i, git_diff_delta *i2w, void *payload)
+static bool status_is_included(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
{
- status_user_callback *usercb = payload;
- const char *path = NULL;
- unsigned int status = 0;
+ if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
+ return 1;
- if (i2w) {
- path = i2w->old_file.path;
- status |= workdir_delta2status(i2w->status);
+ /* if excluding submodules and this is a submodule everywhere */
+ if (head2idx) {
+ if (head2idx->status != GIT_DELTA_ADDED &&
+ head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (head2idx->status != GIT_DELTA_DELETED &&
+ head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
}
- if (h2i) {
- path = h2i->old_file.path;
- status |= index_delta2status(h2i->status);
+ if (idx2wd) {
+ if (idx2wd->status != GIT_DELTA_ADDED &&
+ idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (idx2wd->status != GIT_DELTA_DELETED &&
+ idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
}
- /* 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;
+ /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
+ return 0;
+}
+
+static git_status_t status_compute(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ if (head2idx)
+ st |= index_delta2status(head2idx);
+
+ if (idx2wd)
+ st |= workdir_delta2status(status->idx2wd, idx2wd);
+
+ return st;
+}
+
+static int status_collect(
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd,
+ void *payload)
+{
+ git_status_list *status = payload;
+ git_status_entry *status_entry;
+
+ if (!status_is_included(status, head2idx, idx2wd))
+ return 0;
+
+ status_entry = git__malloc(sizeof(git_status_entry));
+ GITERR_CHECK_ALLOC(status_entry);
+
+ status_entry->status = status_compute(status, head2idx, idx2wd);
+ status_entry->head_to_index = head2idx;
+ status_entry->index_to_workdir = idx2wd;
+
+ return git_vector_insert(&status->paired, status_entry);
+}
+
+GIT_INLINE(int) status_entry_cmp_base(
+ const void *a,
+ const void *b,
+ int (*strcomp)(const char *a, const char *b))
+{
+ const git_status_entry *entry_a = a;
+ const git_status_entry *entry_b = b;
+ const git_diff_delta *delta_a, *delta_b;
+
+ delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
+ entry_a->head_to_index;
+ delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
+ entry_b->head_to_index;
+
+ if (!delta_a && delta_b)
+ return -1;
+ if (delta_a && !delta_b)
+ return 1;
+ if (!delta_a && !delta_b)
+ return 0;
+
+ return strcomp(delta_a->new_file.path, delta_b->new_file.path);
+}
+
+static int status_entry_icmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcasecmp);
+}
+
+static int status_entry_cmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcmp);
+}
+
+static git_status_list *git_status_list_alloc(git_index *index)
+{
+ git_status_list *status = NULL;
+ int (*entrycmp)(const void *a, const void *b);
+
+ if (!(status = git__calloc(1, sizeof(git_status_list))))
+ return NULL;
+
+ entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
+
+ if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
+ git__free(status);
+ return NULL;
}
- return usercb->cb(path, status, usercb->payload);
+ return status;
}
-int git_status_foreach_ext(
+/*
+static int newfile_cmp(const void *a, const void *b)
+{
+ const git_diff_delta *delta_a = a;
+ const git_diff_delta *delta_b = b;
+
+ return git__strcmp(delta_a->new_file.path, delta_b->new_file.path);
+}
+
+static int newfile_casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *delta_a = a;
+ const git_diff_delta *delta_b = b;
+
+ return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path);
+}
+*/
+
+int git_status_list_new(
+ git_status_list **out,
git_repository *repo,
- const git_status_options *opts,
- git_status_cb cb,
- void *payload)
+ const git_status_options *opts)
{
- int err = 0;
+ git_index *index = NULL;
+ git_status_list *status = NULL;
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
- git_diff_list *head2idx = NULL, *idx2wd = NULL;
+ git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- status_user_callback usercb;
+ int error = 0;
+ unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
+ *out = NULL;
+
GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
- if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_repository__ensure_not_bare(repo, "status")) < 0)
- return err;
+ if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0)
+ return error;
/* if there is no HEAD, that's okay - we'll make an empty iterator */
- if (((err = git_repository_head_tree(&head, repo)) < 0) &&
- !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))
- return err;
+ if (((error = git_repository_head_tree(&head, repo)) < 0) &&
+ error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) {
+ git_index_free(index); /* release index */
+ return error;
+ }
+
+ status = git_status_list_alloc(index);
+ GITERR_CHECK_ALLOC(status);
- memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ if (opts) {
+ memcpy(&status->opts, opts, sizeof(git_status_options));
+ memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ }
diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
- if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
- if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
+ if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
- if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
+ if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
- if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ if ((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)
+ if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+ findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED;
+
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
- err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);
- if (err < 0)
- goto cleanup;
+ if ((error = git_diff_tree_to_index(
+ &status->head2idx, repo, head, NULL, &diffopt)) < 0)
+ goto done;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
+ (error = git_diff_find_similar(status->head2idx, NULL)) < 0)
+ goto done;
}
if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
- err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);
- if (err < 0)
- goto cleanup;
- }
+ if ((error = git_diff_index_to_workdir(
+ &status->idx2wd, repo, NULL, &diffopt)) < 0)
+ goto done;
- usercb.cb = cb;
- usercb.payload = payload;
- usercb.opts = opts;
+ if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
+ (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0)
+ goto done;
+ }
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
- if ((err = git_diff__paired_foreach(
- head2idx, NULL, status_invoke_cb, &usercb)) < 0)
- goto cleanup;
+ if ((error = git_diff__paired_foreach(
+ status->head2idx, NULL, status_collect, status)) < 0)
+ goto done;
+
+ git_diff_list_free(status->head2idx);
+ status->head2idx = NULL;
+ }
+
+ if ((error = git_diff__paired_foreach(
+ status->head2idx, status->idx2wd, status_collect, status)) < 0)
+ goto done;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 ||
+ (flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0)
+ git_vector_sort(&status->paired);
- git_diff_list_free(head2idx);
- head2idx = NULL;
+done:
+ if (error < 0) {
+ git_status_list_free(status);
+ status = NULL;
}
- err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);
+ *out = status;
-cleanup:
git_tree_free(head);
- git_diff_list_free(head2idx);
- git_diff_list_free(idx2wd);
+ git_index_free(index);
+
+ return error;
+}
+
+size_t git_status_list_entrycount(git_status_list *status)
+{
+ assert(status);
+
+ return status->paired.length;
+}
- if (err == GIT_EUSER)
- giterr_clear();
+const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
+{
+ assert(status);
- return err;
+ return git_vector_get(&status->paired, i);
}
-int git_status_foreach(
+void git_status_list_free(git_status_list *status)
+{
+ git_status_entry *status_entry;
+ size_t i;
+
+ if (status == NULL)
+ return;
+
+ git_diff_list_free(status->head2idx);
+ git_diff_list_free(status->idx2wd);
+
+ git_vector_foreach(&status->paired, i, status_entry)
+ git__free(status_entry);
+
+ git_vector_free(&status->paired);
+
+ git__memzero(status, sizeof(*status));
+ git__free(status);
+}
+
+int git_status_foreach_ext(
git_repository *repo,
- git_status_cb callback,
+ const git_status_options *opts,
+ git_status_cb cb,
void *payload)
{
- git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *status;
+ const git_status_entry *status_entry;
+ size_t i;
+ int error = 0;
- opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
- GIT_STATUS_OPT_INCLUDE_UNTRACKED |
- GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+ if ((error = git_status_list_new(&status, repo, opts)) < 0)
+ return error;
+
+ git_vector_foreach(&status->paired, i, status_entry) {
+ const char *path = status_entry->head_to_index ?
+ status_entry->head_to_index->old_file.path :
+ status_entry->index_to_workdir->old_file.path;
+
+ if (cb(path, status_entry->status, payload) != 0) {
+ error = GIT_EUSER;
+ giterr_clear();
+ break;
+ }
+ }
+
+ git_status_list_free(status);
+
+ return error;
+}
- return git_status_foreach_ext(repo, &opts, callback, payload);
+int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
+{
+ return git_status_foreach_ext(repo, NULL, cb, payload);
}
struct status_file_info {
@@ -264,7 +472,7 @@ int git_status_file(
if (index->ignore_case)
sfi.fnm_flags = FNM_CASEFOLD;
- opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 000000000..b58e0ebd6
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_status_h__
+#define INCLUDE_status_h__
+
+#include "diff.h"
+#include "git2/status.h"
+#include "git2/diff.h"
+
+struct git_status_list {
+ git_status_options opts;
+
+ git_diff_list *head2idx;
+ git_diff_list *idx2wd;
+
+ git_vector paired;
+};
+
+#endif
diff --git a/src/util.c b/src/util.c
index da15a039d..c543a3d21 100644
--- a/src/util.c
+++ b/src/util.c
@@ -279,6 +279,28 @@ int git__strcasecmp(const char *a, const char *b)
return (tolower(*a) - tolower(*b));
}
+int git__strcasesort_cmp(const char *a, const char *b)
+{
+ int cmp = 0;
+
+ while (*a && *b) {
+ if (*a != *b) {
+ if (tolower(*a) != tolower(*b))
+ break;
+ /* use case in sort order even if not in equivalence */
+ if (!cmp)
+ cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b);
+ }
+
+ ++a, ++b;
+ }
+
+ if (*a || *b)
+ return tolower(*a) - tolower(*b);
+
+ return cmp;
+}
+
int git__strncmp(const char *a, const char *b, size_t sz)
{
while (sz && *a && *b && *a == *b)
diff --git a/src/util.h b/src/util.h
index 1ef9e65b5..e0088399c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -194,6 +194,8 @@ extern int git__strcasecmp(const char *a, const char *b);
extern int git__strncmp(const char *a, const char *b, size_t sz);
extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+extern int git__strcasesort_cmp(const char *a, const char *b);
+
#include "thread-utils.h"
typedef struct {