Commit e0b110ed by Russell Belfer

Merge pull request #744 from arrbee/fix-filemodes

Fix filemode comparison in diffs
parents 80c03754 ac971ecf
...@@ -185,9 +185,7 @@ int main(int argc, char *argv[]) ...@@ -185,9 +185,7 @@ int main(int argc, char *argv[])
/* open repo */ /* open repo */
check(git_repository_discover(path, sizeof(path), dir, 0, "/"), check(git_repository_open_ext(&repo, dir, 0, NULL),
"Could not discover repository");
check(git_repository_open(&repo, path),
"Could not open repository"); "Could not open repository");
if (treeish1) if (treeish1)
......
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
*/ */
GIT_BEGIN_DECL GIT_BEGIN_DECL
/**
* Flags for diff options. A combination of these flags can be passed
* in via the `flags` value in the `git_diff_options`.
*/
enum { enum {
GIT_DIFF_NORMAL = 0, GIT_DIFF_NORMAL = 0,
GIT_DIFF_REVERSE = (1 << 0), GIT_DIFF_REVERSE = (1 << 0),
...@@ -160,15 +164,16 @@ typedef int (*git_diff_hunk_fn)( ...@@ -160,15 +164,16 @@ typedef int (*git_diff_hunk_fn)(
* the file or hunk headers. * the file or hunk headers.
*/ */
enum { enum {
/* these values will be sent to `git_diff_data_fn` along with the line */ /* These values will be sent to `git_diff_data_fn` along with the line */
GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_CONTEXT = ' ',
GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_ADDITION = '+',
GIT_DIFF_LINE_DELETION = '-', GIT_DIFF_LINE_DELETION = '-',
GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */ GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */
GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */
/* these values will only be sent to a `git_diff_data_fn` when the content
* of a diff is being formatted (eg. through git_diff_print_patch() or /* The following values will only be sent to a `git_diff_data_fn` when
* git_diff_print_compact(), for instance). * the content of a diff is being formatted (eg. through
* git_diff_print_patch() or git_diff_print_compact(), for instance).
*/ */
GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_FILE_HDR = 'F',
GIT_DIFF_LINE_HUNK_HDR = 'H', GIT_DIFF_LINE_HUNK_HDR = 'H',
...@@ -206,6 +211,8 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); ...@@ -206,6 +211,8 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
/** /**
* Compute a difference between two tree objects. * Compute a difference between two tree objects.
* *
* This is equivalent to `git diff <treeish> <treeish>`
*
* @param repo The repository containing the trees. * @param repo The repository containing the trees.
* @param opts Structure with options to influence diff or NULL for defaults. * @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from. * @param old_tree A git_tree object to diff from.
...@@ -222,6 +229,9 @@ GIT_EXTERN(int) git_diff_tree_to_tree( ...@@ -222,6 +229,9 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
/** /**
* Compute a difference between a tree and the index. * Compute a difference between a tree and the index.
* *
* This is equivalent to `git diff --cached <treeish>` or if you pass
* the HEAD tree, then like `git diff --cached`.
*
* @param repo The repository containing the tree and index. * @param repo The repository containing the tree and index.
* @param opts Structure with options to influence diff or NULL for defaults. * @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from. * @param old_tree A git_tree object to diff from.
...@@ -236,6 +246,11 @@ GIT_EXTERN(int) git_diff_index_to_tree( ...@@ -236,6 +246,11 @@ GIT_EXTERN(int) git_diff_index_to_tree(
/** /**
* Compute a difference between the working directory and the index. * Compute a difference between the working directory and the index.
* *
* This matches the `git diff` command. See the note below on
* `git_diff_workdir_to_tree` for a discussion of the difference between
* `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
* using libgit2.
*
* @param repo The repository. * @param repo The repository.
* @param opts Structure with options to influence diff or NULL for defaults. * @param opts Structure with options to influence diff or NULL for defaults.
* @param diff A pointer to a git_diff_list pointer that will be allocated. * @param diff A pointer to a git_diff_list pointer that will be allocated.
...@@ -248,14 +263,24 @@ GIT_EXTERN(int) git_diff_workdir_to_index( ...@@ -248,14 +263,24 @@ GIT_EXTERN(int) git_diff_workdir_to_index(
/** /**
* Compute a difference between the working directory and a tree. * Compute a difference between the working directory and a tree.
* *
* This returns strictly the differences between the tree and the * This is *NOT* the same as `git diff <treeish>`. Running `git diff HEAD`
* files contained in the working directory, regardless of the state * or the like actually uses information from the index, along with the tree
* of files in the index. There is no direct equivalent in C git. * and workdir dir info.
* *
* This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those * This function returns strictly the differences between the tree and the
* commands diff the tree, the index, and the workdir. To emulate those * files contained in the working directory, regardless of the state of
* functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`, * files in the index. It may come as a surprise, but there is no direct
* then call `git_diff_merge` on the results. * equivalent in core git.
*
* To emulate `git diff <treeish>`, you should call both
* `git_diff_index_to_tree` and `git_diff_workdir_to_index`, then call
* `git_diff_merge` on the results. That will yield a `git_diff_list` that
* matches the git output.
*
* If this seems confusing, take the case of a file with a staged deletion
* where the file has then been put back into the working dir and modified.
* The tree-to-workdir diff for that file is 'modified', but core git would
* show status 'deleted' since there is a pending deletion in the index.
* *
* @param repo The repository containing the tree. * @param repo The repository containing the tree.
* @param opts Structure with options to influence diff or NULL for defaults. * @param opts Structure with options to influence diff or NULL for defaults.
...@@ -298,10 +323,23 @@ GIT_EXTERN(int) git_diff_merge( ...@@ -298,10 +323,23 @@ GIT_EXTERN(int) git_diff_merge(
/** /**
* Iterate over a diff list issuing callbacks. * Iterate over a diff list issuing callbacks.
* *
* If the hunk and/or line callbacks are not NULL, then this will calculate * This will iterate through all of the files described in a diff. You
* text diffs for all files it thinks are not binary. If those are both * should provide a file callback to learn about each file.
* NULL, then this will not bother with the text diffs, so it can be *
* efficient. * The "hunk" and "line" callbacks are optional, and the text diff of the
* files will only be calculated if they are not NULL. Of course, these
* callbacks will not be invoked for binary files on the diff list or for
* files whose only changed is a file mode change.
*
* @param diff A git_diff_list generated by one of the above functions.
* @param cb_data Reference pointer that will be passed to your callbacks.
* @param file_cb Callback function to make per file in the diff.
* @param hunk_cb Optional callback to make per hunk of text diff. This
* callback is called to describe a range of lines in the
* diff. It will not be issued for binary files.
* @param line_cb Optional callback to make per line of diff text. This
* same callback will be made for context lines, added, and
* removed lines, and even for a deleted trailing newline.
*/ */
GIT_EXTERN(int) git_diff_foreach( GIT_EXTERN(int) git_diff_foreach(
git_diff_list *diff, git_diff_list *diff,
...@@ -322,6 +360,14 @@ GIT_EXTERN(int) git_diff_print_compact( ...@@ -322,6 +360,14 @@ GIT_EXTERN(int) git_diff_print_compact(
* Iterate over a diff generating text output like "git diff". * Iterate over a diff generating text output like "git diff".
* *
* This is a super easy way to generate a patch from a diff. * This is a super easy way to generate a patch from a diff.
*
* @param diff A git_diff_list generated by one of the above functions.
* @param cb_data Reference pointer that will be passed to your callbacks.
* @param print_cb Callback function to output lines of the diff. This
* same function will be called for file headers, hunk
* headers, and diff lines. Fortunately, you can probably
* use various GIT_DIFF_LINE constants to determine what
* text you are given.
*/ */
GIT_EXTERN(int) git_diff_print_patch( GIT_EXTERN(int) git_diff_print_patch(
git_diff_list *diff, git_diff_list *diff,
...@@ -338,13 +384,14 @@ GIT_EXTERN(int) git_diff_print_patch( ...@@ -338,13 +384,14 @@ GIT_EXTERN(int) git_diff_print_patch(
/** /**
* Directly run a text diff on two blobs. * Directly run a text diff on two blobs.
* *
* Compared to a file, a blob lacks some contextual information. As such, the * Compared to a file, a blob lacks some contextual information. As such,
* `git_diff_file` parameters of the callbacks will be filled accordingly to the following: * the `git_diff_file` parameters of the callbacks will be filled
* `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid` * accordingly to the following: `mode` will be set to 0, `path` will be set
* will be set to 0. * to NULL. When dealing with a NULL blob, `oid` will be set to 0.
* *
* When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary * When at least one of the blobs being dealt with is binary, the
* attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made. * `git_diff_delta` binary attribute will be set to 1 and no call to the
* hunk_cb nor line_cb will be made.
*/ */
GIT_EXTERN(int) git_diff_blobs( GIT_EXTERN(int) git_diff_blobs(
git_blob *old_blob, git_blob *old_blob,
......
...@@ -102,7 +102,7 @@ enum { ...@@ -102,7 +102,7 @@ enum {
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0), GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1), GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3), GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
}; };
......
...@@ -130,37 +130,50 @@ fail: ...@@ -130,37 +130,50 @@ fail:
static git_diff_delta *diff_delta__merge_like_cgit( static git_diff_delta *diff_delta__merge_like_cgit(
const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
{ {
git_diff_delta *dup = diff_delta__dup(a, pool); git_diff_delta *dup;
if (!dup)
return NULL;
if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)
return dup;
git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);
dup->new_file.mode = b->new_file.mode;
dup->new_file.size = b->new_file.size;
dup->new_file.flags = b->new_file.flags;
/* Emulate C git for merging two diffs (a la 'git diff <sha>'). /* Emulate C git for merging two diffs (a la 'git diff <sha>').
* *
* When C git does a diff between the work dir and a tree, it actually * When C git does a diff between the work dir and a tree, it actually
* diffs with the index but uses the workdir contents. This emulates * diffs with the index but uses the workdir contents. This emulates
* those choices so we can emulate the type of diff. * those choices so we can emulate the type of diff.
*
* We have three file descriptions here, let's call them:
* f1 = a->old_file
* f2 = a->new_file AND b->old_file
* f3 = b->new_file
*/ */
if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {
if (dup->status == GIT_DELTA_DELETED) /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
/* preserve pending delete info */; if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
else if (b->status == GIT_DELTA_UNTRACKED || return diff_delta__dup(a, pool);
b->status == GIT_DELTA_IGNORED)
dup->status = b->status; /* otherwise, base this diff on the 'b' diff */
else if ((dup = diff_delta__dup(b, pool)) == NULL)
return NULL;
/* If 'a' status is uninteresting, then we're done */
if (a->status == GIT_DELTA_UNMODIFIED)
return dup;
assert(a->status != GIT_DELTA_UNMODIFIED);
assert(b->status != GIT_DELTA_UNMODIFIED);
/* A cgit exception is that the diff of a file that is only in the
* index (i.e. not in HEAD nor workdir) is given as empty.
*/
if (dup->status == GIT_DELTA_DELETED) {
if (a->status == GIT_DELTA_ADDED)
dup->status = GIT_DELTA_UNMODIFIED; dup->status = GIT_DELTA_UNMODIFIED;
/* else don't overwrite DELETE status */
} else {
dup->status = a->status;
} }
else if (dup->status == GIT_DELTA_UNMODIFIED ||
b->status == GIT_DELTA_DELETED) git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
dup->status = b->status; dup->old_file.mode = a->old_file.mode;
dup->old_file.size = a->old_file.size;
dup->old_file.flags = a->old_file.flags;
return dup; return dup;
} }
...@@ -214,7 +227,9 @@ static int diff_delta__from_two( ...@@ -214,7 +227,9 @@ static int diff_delta__from_two(
git_diff_list *diff, git_diff_list *diff,
git_delta_t status, git_delta_t status,
const git_index_entry *old_entry, const git_index_entry *old_entry,
uint32_t old_mode,
const git_index_entry *new_entry, const git_index_entry *new_entry,
uint32_t new_mode,
git_oid *new_oid) git_oid *new_oid)
{ {
git_diff_delta *delta; git_diff_delta *delta;
...@@ -224,19 +239,22 @@ static int diff_delta__from_two( ...@@ -224,19 +239,22 @@ static int diff_delta__from_two(
return 0; return 0;
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
const git_index_entry *temp = old_entry; uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry;
old_entry = new_entry; old_entry = new_entry;
new_entry = temp; new_entry = temp_entry;
old_mode = new_mode;
new_mode = temp_mode;
} }
delta = diff_delta__alloc(diff, status, old_entry->path); delta = diff_delta__alloc(diff, status, old_entry->path);
GITERR_CHECK_ALLOC(delta); GITERR_CHECK_ALLOC(delta);
delta->old_file.mode = old_entry->mode; delta->old_file.mode = old_mode;
git_oid_cpy(&delta->old_file.oid, &old_entry->oid); git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
delta->new_file.mode = new_entry->mode; delta->new_file.mode = new_mode;
git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
if (new_oid || !git_oid_iszero(&new_entry->oid)) if (new_oid || !git_oid_iszero(&new_entry->oid))
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
...@@ -300,7 +318,7 @@ static git_diff_list *git_diff_list_alloc( ...@@ -300,7 +318,7 @@ static git_diff_list *git_diff_list_alloc(
if (config_bool(cfg, "core.ignorestat", 0)) if (config_bool(cfg, "core.ignorestat", 0))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
if (config_bool(cfg, "core.filemode", 1)) if (config_bool(cfg, "core.filemode", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT; diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
if (config_bool(cfg, "core.trustctime", 1)) if (config_bool(cfg, "core.trustctime", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
...@@ -419,7 +437,7 @@ static int oid_for_workdir_item( ...@@ -419,7 +437,7 @@ static int oid_for_workdir_item(
return result; return result;
} }
#define EXEC_BIT_MASK 0000111 #define MODE_BITS_MASK 0000777
static int maybe_modified( static int maybe_modified(
git_iterator *old_iter, git_iterator *old_iter,
...@@ -443,13 +461,13 @@ static int maybe_modified( ...@@ -443,13 +461,13 @@ static int maybe_modified(
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK); nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
/* on platforms with no execmode, clear exec bit from comparisons */ /* on platforms with no execmode, just preserve old mode */
if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) { if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
omode = omode & ~EXEC_BIT_MASK; (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
nmode = nmode & ~EXEC_BIT_MASK; new_iter->type == GIT_ITERATOR_WORKDIR)
} nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
/* support "assume unchanged" (badly, b/c we still stat everything) */ /* support "assume unchanged" (poorly, b/c we still stat everything) */
if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
...@@ -471,8 +489,13 @@ static int maybe_modified( ...@@ -471,8 +489,13 @@ static int maybe_modified(
omode == nmode) omode == nmode)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* if we have a workdir item with an unknown oid, check deeper */ /* if modes match and we have an unknown OID and a workdir iterator,
else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) { * then check deeper for matching
*/
else if (omode == nmode &&
git_oid_iszero(&nitem->oid) &&
new_iter->type == GIT_ITERATOR_WORKDIR)
{
/* TODO: add check against index file st_mtime to avoid racy-git */ /* TODO: add check against index file st_mtime to avoid racy-git */
/* if they files look exactly alike, then we'll assume the same */ /* if they files look exactly alike, then we'll assume the same */
...@@ -517,7 +540,8 @@ static int maybe_modified( ...@@ -517,7 +540,8 @@ static int maybe_modified(
use_noid = &noid; use_noid = &noid;
} }
return diff_delta__from_two(diff, status, oitem, nitem, use_noid); return diff_delta__from_two(
diff, status, oitem, omode, nitem, nmode, use_noid);
} }
static int diff_from_iterators( static int diff_from_iterators(
...@@ -772,6 +796,12 @@ int git_diff_merge( ...@@ -772,6 +796,12 @@ int git_diff_merge(
git_vector_swap(&onto->deltas, &onto_new); git_vector_swap(&onto->deltas, &onto_new);
git_pool_swap(&onto->pool, &onto_pool); git_pool_swap(&onto->pool, &onto_pool);
onto->new_src = from->new_src; onto->new_src = from->new_src;
/* prefix strings also come from old pool, so recreate those.*/
onto->opts.old_prefix =
git_pool_strdup(&onto->pool, onto->opts.old_prefix);
onto->opts.new_prefix =
git_pool_strdup(&onto->pool, onto->opts.new_prefix);
} }
git_vector_foreach(&onto_new, i, delta) git_vector_foreach(&onto_new, i, delta)
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
enum { enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */ GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
}; };
...@@ -36,5 +36,8 @@ struct git_diff_list { ...@@ -36,5 +36,8 @@ struct git_diff_list {
uint32_t diffcaps; uint32_t diffcaps;
}; };
extern void git_diff__cleanup_modes(
uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
#endif #endif
...@@ -83,12 +83,13 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -83,12 +83,13 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0) info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)
return -1; return -1;
/* deal with adding and removing newline at EOF */ /* This should only happen if we are adding a line that does not
* have a newline at the end and the old code did. In that case,
* we have a ADD with a DEL_EOFNL as a pair.
*/
if (len == 3) { if (len == 3) {
if (origin == GIT_DIFF_LINE_ADDITION) origin = (origin == GIT_DIFF_LINE_ADDITION) ?
origin = GIT_DIFF_LINE_ADD_EOFNL; GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL;
else
origin = GIT_DIFF_LINE_DEL_EOFNL;
return info->line_cb( return info->line_cb(
info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size); info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);
...@@ -359,7 +360,7 @@ int git_diff_foreach( ...@@ -359,7 +360,7 @@ int git_diff_foreach(
/* map files */ /* map files */
if (delta->binary != 1 && if (delta->binary != 1 &&
(hunk_cb || line_cb) && (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) &&
(delta->status == GIT_DELTA_DELETED || (delta->status == GIT_DELTA_DELETED ||
delta->status == GIT_DELTA_MODIFIED)) delta->status == GIT_DELTA_MODIFIED))
{ {
...@@ -397,7 +398,9 @@ int git_diff_foreach( ...@@ -397,7 +398,9 @@ int git_diff_foreach(
/* since we did not have the definitive oid, we may have /* since we did not have the definitive oid, we may have
* incorrect status and need to skip this item. * incorrect status and need to skip this item.
*/ */
if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) { if (delta->old_file.mode == delta->new_file.mode &&
!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
{
delta->status = GIT_DELTA_UNMODIFIED; delta->status = GIT_DELTA_UNMODIFIED;
if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
goto cleanup; goto cleanup;
...@@ -420,7 +423,8 @@ int git_diff_foreach( ...@@ -420,7 +423,8 @@ int git_diff_foreach(
*/ */
if (file_cb != NULL) { if (file_cb != NULL) {
error = file_cb(data, delta, (float)info.index / diff->deltas.length); error = file_cb(
data, delta, (float)info.index / diff->deltas.length);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
} }
...@@ -433,6 +437,10 @@ int git_diff_foreach( ...@@ -433,6 +437,10 @@ int git_diff_foreach(
if (!old_data.len && !new_data.len) if (!old_data.len && !new_data.len)
goto cleanup; goto cleanup;
/* nothing to do if only diff was a mode change */
if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
goto cleanup;
assert(hunk_cb || line_cb); assert(hunk_cb || line_cb);
info.delta = delta; info.delta = delta;
......
...@@ -17,6 +17,10 @@ ...@@ -17,6 +17,10 @@
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#ifdef GIT_WIN32
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
#endif
/* /*
* Based on the Android implementation, BSD licensed. * Based on the Android implementation, BSD licensed.
* Check http://android.git.kernel.org/ * Check http://android.git.kernel.org/
...@@ -105,7 +109,7 @@ int git_path_dirname_r(git_buf *buffer, const char *path) ...@@ -105,7 +109,7 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
'C:/' here */ 'C:/' here */
if (len == 2 && isalpha(path[0]) && path[1] == ':') { if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
len = 3; len = 3;
goto Exit; goto Exit;
} }
...@@ -170,7 +174,7 @@ int git_path_root(const char *path) ...@@ -170,7 +174,7 @@ int git_path_root(const char *path)
#ifdef GIT_WIN32 #ifdef GIT_WIN32
/* Does the root of the path look like a windows drive ? */ /* Does the root of the path look like a windows drive ? */
if (isalpha(path[0]) && (path[1] == ':')) if (LOOKS_LIKE_DRIVE_PREFIX(path))
offset += 2; offset += 2;
/* Are we dealing with a windows network path? */ /* Are we dealing with a windows network path? */
...@@ -210,7 +214,7 @@ int git_path_prettify(git_buf *path_out, const char *path, const char *base) ...@@ -210,7 +214,7 @@ int git_path_prettify(git_buf *path_out, const char *path, const char *base)
giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
git_buf_clear(path_out); git_buf_clear(path_out);
return error; return error;
} }
......
...@@ -155,3 +155,27 @@ void cl_git_sandbox_cleanup(void) ...@@ -155,3 +155,27 @@ void cl_git_sandbox_cleanup(void)
} }
} }
bool cl_toggle_filemode(const char *filename)
{
struct stat st1, st2;
cl_must_pass(p_stat(filename, &st1));
cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
cl_must_pass(p_stat(filename, &st2));
return (st1.st_mode != st2.st_mode);
}
bool cl_is_chmod_supported(void)
{
static int _is_supported = -1;
if (_is_supported < 0) {
cl_git_mkfile("filemode.t", "Test if filemode can be modified");
_is_supported = cl_toggle_filemode("filemode.t");
cl_must_pass(p_unlink("filemode.t"));
}
return _is_supported;
}
...@@ -40,6 +40,9 @@ void cl_git_append2file(const char *filename, const char *new_content); ...@@ -40,6 +40,9 @@ void cl_git_append2file(const char *filename, const char *new_content);
void cl_git_rewritefile(const char *filename, const char *new_content); void cl_git_rewritefile(const char *filename, const char *new_content);
void cl_git_write2file(const char *filename, const char *new_content, int mode); void cl_git_write2file(const char *filename, const char *new_content, int mode);
bool cl_toggle_filemode(const char *filename);
bool cl_is_chmod_supported(void);
/* Environment wrappers */ /* Environment wrappers */
char *cl_getenv(const char *name); char *cl_getenv(const char *name);
int cl_setenv(const char *name, const char *value); int cl_setenv(const char *name, const char *value);
......
...@@ -85,11 +85,16 @@ int diff_line_fn( ...@@ -85,11 +85,16 @@ int diff_line_fn(
e->line_ctxt++; e->line_ctxt++;
break; break;
case GIT_DIFF_LINE_ADDITION: case GIT_DIFF_LINE_ADDITION:
case GIT_DIFF_LINE_ADD_EOFNL:
e->line_adds++; e->line_adds++;
break; break;
case GIT_DIFF_LINE_ADD_EOFNL:
assert(0);
break;
case GIT_DIFF_LINE_DELETION: case GIT_DIFF_LINE_DELETION:
e->line_dels++;
break;
case GIT_DIFF_LINE_DEL_EOFNL: case GIT_DIFF_LINE_DEL_EOFNL:
/* technically not a line delete, but we'll count it as such */
e->line_dels++; e->line_dels++;
break; break;
default: default:
......
...@@ -116,7 +116,7 @@ void test_diff_tree__options(void) ...@@ -116,7 +116,7 @@ void test_diff_tree__options(void)
{ 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 }, { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
{ 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 }, { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
{ 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 }, { 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
{ 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 48, 3 }, { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 },
/* c vs d tests */ /* c vs d tests */
{ 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 }, { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
{ 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 }, { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
......
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
...@@ -581,3 +581,69 @@ void test_status_worktree__space_in_filename(void) ...@@ -581,3 +581,69 @@ void test_status_worktree__space_in_filename(void)
git_index_free(index); git_index_free(index);
git_repository_free(repo); git_repository_free(repo);
} }
static const char *filemode_paths[] = {
"exec_off",
"exec_off2on_staged",
"exec_off2on_workdir",
"exec_off_untracked",
"exec_on",
"exec_on2off_staged",
"exec_on2off_workdir",
"exec_on_untracked",
};
static unsigned int filemode_statuses[] = {
GIT_STATUS_CURRENT,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_CURRENT,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW
};
static const size_t filemode_count = 8;
void test_status_worktree__filemode_changes(void)
{
git_repository *repo = cl_git_sandbox_init("filemodes");
status_entry_counts counts;
git_status_options opts;
git_config *cfg;
/* overwrite stored filemode with platform appropriate value */
cl_git_pass(git_repository_config(&cfg, repo));
if (cl_is_chmod_supported())
cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
else {
unsigned int i;
cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
/* won't trust filesystem mode diffs, so these will appear unchanged */
for (i = 0; i < filemode_count; ++i)
if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
filemode_statuses[i] = GIT_STATUS_CURRENT;
}
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
memset(&counts, 0, sizeof(counts));
counts.expected_entry_count = filemode_count;
counts.expected_paths = filemode_paths;
counts.expected_statuses = filemode_statuses;
cl_git_pass(
git_status_foreach_ext(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);
git_config_free(cfg);
}
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