Commit c09810ee by Russell Belfer

Merge pull request #1651 from arrbee/status_indexed_updates

Status indexed updates
parents 09c2f91c f4183347
......@@ -42,6 +42,7 @@ typedef enum {
GIT_STATUS_WT_MODIFIED = (1u << 8),
GIT_STATUS_WT_DELETED = (1u << 9),
GIT_STATUS_WT_TYPECHANGE = (1u << 10),
GIT_STATUS_WT_RENAMED = (1u << 11),
GIT_STATUS_IGNORED = (1u << 14),
} git_status_t;
......@@ -59,43 +60,19 @@ typedef int (*git_status_cb)(
const char *path, unsigned int status_flags, void *payload);
/**
* Gather file statuses and run a callback for each one.
*
* The callback is passed the path of the file, the status (a combination of
* the `git_status_t` values above) and the `payload` data pointer passed
* into this function.
*
* If the callback returns a non-zero value, this function will stop looping
* and return GIT_EUSER.
*
* @param repo A repository object
* @param callback The function to call on each file
* @param payload Pointer to pass through to callback function
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_status_foreach(
git_repository *repo,
git_status_cb callback,
void *payload);
/**
* For extended status, select the files on which to report status.
*
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the
* rough equivalent of `git status --porcelain` where each file
* will receive a callback indicating its status in the index and
* in the workdir.
* - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index
* side of status. The status of the index contents relative to
* the HEAD will be given.
* - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the
* workdir side of status, reporting the status of workdir content
* relative to the index.
* - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only
* followed by workdir-only, causing two callbacks to be issued
* per file (first index then workdir). This is slightly more
* efficient than making separate calls. This makes it easier to
* emulate the output of a plain `git status`.
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly
* matches `git status --porcelain` where each file gets a callback
* indicating its status in the index and in the working directory.
* - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index
* comparison, not looking at working directory changes.
* - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to
* working directory comparison, not comparing the index to the HEAD.
* - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only,
* issuing (up to) two callbacks per file (first index, then workdir).
* This is slightly more efficient than separate calls and can make it
* easier to emulate plain `git status` text output.
*/
typedef enum {
GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
......@@ -110,26 +87,30 @@ typedef enum {
* - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made
* on untracked files. These will only be made if the workdir files are
* included in the status "show" option.
* - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get
* callbacks. Again, these callbacks will only be made if the workdir
* files are included in the status "show" option. Right now, there is
* no option to include all files in directories that are ignored
* completely.
* - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files get callbacks.
* Again, these callbacks will only be made if the workdir files are
* included in the status "show" option.
* - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be
* made even on unmodified files.
* - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which
* appear to be submodules should just be skipped over.
* - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of
* untracked directories should be included in the status. Normally if
* an entire directory is new, then just the top-level directory will be
* included (with a trailing slash on the entry name). Given this flag,
* the directory itself will not be included, but all the files in it
* will.
* - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be
* skipped. This only applies if there are no pending typechanges to
* the submodule (either from or to another type).
* - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in
* untracked directories should be included. Normally if an entire
* directory is new, then just the top-level directory is included (with
* a trailing slash on the entry name). This flag says to include all
* of the individual files in the directory instead.
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
* will be treated as a literal path, and not as a pathspec.
* should be treated as a literal path, and not as a pathspec pattern.
* - 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.
* - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
* should be processed between the head and the index and enables
* the GIT_STATUS_INDEX_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename
* detection should be run between the index and the working directory
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
*
* Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
......@@ -137,13 +118,15 @@ typedef enum {
* together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline.
*/
typedef enum {
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_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_RENAMES_HEAD_TO_INDEX = (1u << 7),
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
} git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \
......@@ -178,6 +161,47 @@ typedef struct {
#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION}
/**
* A status entry, providing the differences between the file as it exists
* in HEAD and the index, and providing the differences between the index
* and the working directory.
*
* The `status` value provides the status flags for this file.
*
* The `head_to_index` value provides detailed information about the
* differences between the file in HEAD and the file in the index.
*
* The `index_to_workdir` value provides detailed information about the
* differences between the file in the index and the file in the
* working directory.
*/
typedef struct {
git_status_t status;
git_diff_delta *head_to_index;
git_diff_delta *index_to_workdir;
} git_status_entry;
/**
* Gather file statuses and run a callback for each one.
*
* The callback is passed the path of the file, the status (a combination of
* the `git_status_t` values above) and the `payload` data pointer passed
* into this function.
*
* If the callback returns a non-zero value, this function will stop looping
* and return GIT_EUSER.
*
* @param repo A repository object
* @param callback The function to call on each file
* @param payload Pointer to pass through to callback function
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_status_foreach(
git_repository *repo,
git_status_cb callback,
void *payload);
/**
* Gather file status information and run callbacks as requested.
*
* This is an extended version of the `git_status_foreach()` API that
......@@ -216,6 +240,49 @@ GIT_EXTERN(int) git_status_file(
const char *path);
/**
* Gather file status information and populate the `git_status_list`.
*
* @param out Pointer to store the status results in
* @param repo Repository object
* @param opts Status options structure
* @return 0 on success or error code
*/
GIT_EXTERN(int) git_status_list_new(
git_status_list **out,
git_repository *repo,
const git_status_options *opts);
/**
* Gets the count of status entries in this list.
*
* @param statuslist Existing status list object
* @return the number of status entries
*/
GIT_EXTERN(size_t) git_status_list_entrycount(
git_status_list *statuslist);
/**
* Get a pointer to one of the entries in the status list.
*
* The entry is not modifiable and should not be freed.
*
* @param statuslist Existing status list object
* @param idx Position of the entry
* @return Pointer to the entry; NULL if out of bounds
*/
GIT_EXTERN(const git_status_entry *) git_status_byindex(
git_status_list *statuslist,
size_t idx);
/**
* Free an existing status list
*
* @param statuslist Existing status list object
*/
GIT_EXTERN(void) git_status_list_free(
git_status_list *statuslist);
/**
* Test if the ignore rules apply to a given file.
*
* This function checks the ignore rules to see if they would apply to the
......
......@@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator;
/** Merge heads, the input to merge */
typedef struct git_merge_head git_merge_head;
/** Representation of a status collection */
typedef struct git_status_list git_status_list;
/** Basic type of any Git reference. */
typedef enum {
......
......@@ -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;
}
......@@ -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
......@@ -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;
......
......@@ -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...
......
......@@ -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 &&
......
......@@ -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;
......
/*
* 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
......@@ -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)
......
......@@ -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 {
......
......@@ -183,10 +183,10 @@ clar_run_test(
}
static void
clar_run_suite(const struct clar_suite *suite, const char *name)
clar_run_suite(const struct clar_suite *suite, const char *filter)
{
const struct clar_func *test = suite->tests;
size_t i, namelen;
size_t i, matchlen;
if (!suite->enabled)
return;
......@@ -200,21 +200,21 @@ clar_run_suite(const struct clar_suite *suite, const char *name)
_clar.active_suite = suite->name;
_clar.suite_errors = 0;
if (name) {
if (filter) {
size_t suitelen = strlen(suite->name);
namelen = strlen(name);
if (namelen <= suitelen) {
name = NULL;
matchlen = strlen(filter);
if (matchlen <= suitelen) {
filter = NULL;
} else {
name += suitelen;
while (*name == ':')
++name;
namelen = strlen(name);
filter += suitelen;
while (*filter == ':')
++filter;
matchlen = strlen(filter);
}
}
for (i = 0; i < suite->test_count; ++i) {
if (name && strncmp(test[i].name, name, namelen))
if (filter && strncmp(test[i].name, filter, matchlen))
continue;
_clar.active_test = test[i].name;
......@@ -230,7 +230,7 @@ clar_usage(const char *arg)
{
printf("Usage: %s [options]\n\n", arg);
printf("Options:\n");
printf(" -sname\tRun only the suite with `name`\n");
printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n");
printf(" -iname\tInclude the suite with `name`\n");
printf(" -xname\tExclude the suite with `name`\n");
printf(" -q \tOnly report tests that had an error\n");
......@@ -256,21 +256,20 @@ clar_parse_args(int argc, char **argv)
case 'x': { /* given suite name */
int offset = (argument[2] == '=') ? 3 : 2, found = 0;
char action = argument[1];
size_t j, len, cmplen;
size_t j, arglen, suitelen, cmplen;
argument += offset;
len = strlen(argument);
arglen = strlen(argument);
if (len == 0)
if (arglen == 0)
clar_usage(argv[0]);
for (j = 0; j < _clar_suite_count; ++j) {
cmplen = strlen(_clar_suites[j].name);
if (cmplen > len)
cmplen = len;
suitelen = strlen(_clar_suites[j].name);
cmplen = (arglen < suitelen) ? arglen : suitelen;
if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
int exact = !strcmp(argument, _clar_suites[j].name);
int exact = (arglen >= suitelen);
++found;
......@@ -419,7 +418,16 @@ void clar__assert_equal_s(
if (!match) {
char buf[4096];
snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
if (s1 && s2) {
int pos;
for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
/* find differing byte offset */;
snprint_eq(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", s1, s2, pos);
} else {
snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
}
clar__fail(file, line, err, buf, should_abort);
}
}
......
......@@ -18,9 +18,9 @@ static int
find_tmp_path(char *buffer, size_t length)
{
#ifndef _WIN32
static const size_t var_count = 4;
static const size_t var_count = 5;
static const char *env_vars[] = {
"TMPDIR", "TMP", "TEMP", "USERPROFILE"
"CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
};
size_t i;
......@@ -43,6 +43,12 @@ find_tmp_path(char *buffer, size_t length)
}
#else
DWORD env_len;
if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 &&
env_len < length)
return 0;
if (GetTempPath((DWORD)length, buffer))
return 0;
#endif
......@@ -61,9 +67,7 @@ static void clar_unsandbox(void)
if (_clar_path[0] == '\0')
return;
#ifdef _WIN32
chdir("..");
#endif
fs_rm(_clar_path);
}
......
......@@ -26,3 +26,16 @@ void test_core_string__1(void)
cl_assert(git__suffixcmp("zaz", "ac") > 0);
}
/* compare icase sorting with case equality */
void test_core_string__2(void)
{
cl_assert(git__strcasesort_cmp("", "") == 0);
cl_assert(git__strcasesort_cmp("foo", "foo") == 0);
cl_assert(git__strcasesort_cmp("foo", "bar") > 0);
cl_assert(git__strcasesort_cmp("bar", "foo") < 0);
cl_assert(git__strcasesort_cmp("foo", "FOO") > 0);
cl_assert(git__strcasesort_cmp("FOO", "foo") < 0);
cl_assert(git__strcasesort_cmp("foo", "BAR") > 0);
cl_assert(git__strcasesort_cmp("BAR", "foo") < 0);
cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0);
}
......@@ -899,6 +899,7 @@ void test_diff_rename__rejected_match_can_match_others(void)
cl_git_pass(
git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect));
git_diff_list_free(diff);
git_tree_free(tree);
git_index_free(index);
git_reference_free(head);
......@@ -906,3 +907,93 @@ void test_diff_rename__rejected_match_can_match_others(void)
git_buf_free(&one);
git_buf_free(&two);
}
void test_diff_rename__case_changes_are_split(void)
{
git_index *index;
git_tree *tree;
git_diff_list *diff = NULL;
diff_expects exp;
git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(
git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt"));
cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
cl_assert_equal_i(2, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
opts.flags = GIT_DIFF_FIND_ALL;
cl_git_pass(git_diff_find_similar(diff, &opts));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
cl_assert_equal_i(1, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
git_diff_list_free(diff);
git_index_free(index);
git_tree_free(tree);
}
void test_diff_rename__unmodified_can_be_renamed(void)
{
git_index *index;
git_tree *tree;
git_diff_list *diff = NULL;
diff_expects exp;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(
git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt"));
cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
cl_assert_equal_i(2, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
opts.flags = GIT_DIFF_FIND_ALL;
cl_git_pass(git_diff_find_similar(diff, &opts));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
cl_assert_equal_i(1, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
cl_assert_equal_i(1, exp.files);
cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
git_diff_list_free(diff);
git_index_free(index);
git_tree_free(tree);
}
......@@ -416,3 +416,46 @@ void test_index_tests__remove_directory(void)
git_repository_free(repo);
cl_fixture_cleanup("index_test");
}
void test_index_tests__preserves_case(void)
{
git_repository *repo;
git_index *index;
const git_index_entry *entry;
int index_caps;
cl_set_cleanup(&cleanup_myrepo, NULL);
cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
cl_git_pass(git_repository_index(&index, repo));
index_caps = git_index_caps(index);
cl_git_rewritefile("myrepo/test.txt", "hey there\n");
cl_git_pass(git_index_add_bypath(index, "test.txt"));
cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt"));
cl_git_rewritefile("myrepo/TEST.txt", "hello again\n");
cl_git_pass(git_index_add_bypath(index, "TEST.txt"));
if (index_caps & GIT_INDEXCAP_IGNORE_CASE)
cl_assert_equal_i(1, (int)git_index_entrycount(index));
else
cl_assert_equal_i(2, (int)git_index_entrycount(index));
/* Test access by path instead of index */
cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL);
/* The path should *not* have changed without an explicit remove */
cl_assert(git__strcmp(entry->path, "test.txt") == 0);
cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL);
if (index_caps & GIT_INDEXCAP_IGNORE_CASE)
/* The path should *not* have changed without an explicit remove */
cl_assert(git__strcmp(entry->path, "test.txt") == 0);
else
cl_assert(git__strcmp(entry->path, "TEST.txt") == 0);
git_index_free(index);
git_repository_free(repo);
}
......@@ -695,3 +695,51 @@ void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracke
/* Actually returns GIT_STATUS_IGNORED on Windows */
cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
}
void test_status_worktree__simple_delete(void)
{
git_repository *repo = cl_git_sandbox_init("renames");
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
int count;
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
count = 0;
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
cl_assert_equal_i(0, count);
cl_must_pass(p_unlink("renames/untimely.txt"));
count = 0;
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
cl_assert_equal_i(1, count);
}
void test_status_worktree__simple_delete_indexed(void)
{
git_repository *repo = cl_git_sandbox_init("renames");
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
git_status_list *status;
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
cl_git_pass(git_status_list_new(&status, repo, &opts));
cl_assert_equal_sz(0, git_status_list_entrycount(status));
git_status_list_free(status);
cl_must_pass(p_unlink("renames/untimely.txt"));
cl_git_pass(git_status_list_new(&status, repo, &opts));
cl_assert_equal_sz(1, git_status_list_entrycount(status));
cl_assert_equal_i(
GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
git_status_list_free(status);
}
......@@ -376,9 +376,12 @@ void test_submodule_status__iterator(void)
git_iterator_free(iter);
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
cl_git_pass(git_status_foreach_ext(g_repo, &opts, confirm_submodule_status, &exp));
cl_git_pass(git_status_foreach_ext(
g_repo, &opts, confirm_submodule_status, &exp));
}
void test_submodule_status__untracked_dirs_containing_ignored_files(void)
......
......@@ -113,24 +113,18 @@
{
mac-ssl-leak-1
Memcheck:Leak
fun:malloc
fun:CRYPTO_malloc
...
fun:ERR_load_strings
}
{
mac-ssl-leak-2
Memcheck:Leak
fun:malloc
fun:CRYPTO_malloc
...
fun:SSL_library_init
}
{
mac-ssl-leak-3
Memcheck:Leak
fun:malloc
fun:strdup
...
fun:si_module_with_name
fun:getaddrinfo
......@@ -144,6 +138,14 @@
fun:ssl3_get_server_certificate
}
{
mac-ssl-leak-5
Memcheck:Leak
fun:malloc
fun:CRYPTO_malloc
...
fun:ERR_put_error
}
{
clar-printf-buf
Memcheck:Leak
fun:malloc
......
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