summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-10-17 14:14:51 -0700
committerRussell Belfer <rb@github.com>2012-10-17 14:14:51 -0700
commit4c47a8bcfe03c42096b74d4af06ab95fb95fd211 (patch)
treec79eb8290ef110faff68ae2d5746455095508e1e /src/diff.c
parent6012e86839bbdfc98085662c22f8e34b6f6abefb (diff)
parent52a61bb8047f431bf363bd9327d0f34884437c83 (diff)
downloadlibgit2-4c47a8bcfe03c42096b74d4af06ab95fb95fd211.tar.gz
Merge pull request #968 from arrbee/diff-support-typechange
Support TYPECHANGE records in status and adjust checkout accordingly
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c124
1 files changed, 105 insertions, 19 deletions
diff --git a/src/diff.c b/src/diff.c
index 8ab8af3a1..7f500b8e8 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -291,6 +291,36 @@ static int diff_delta__from_two(
return 0;
}
+static git_diff_delta *diff_delta__last_for_item(
+ git_diff_list *diff,
+ const git_index_entry *item)
+{
+ git_diff_delta *delta = git_vector_last(&diff->deltas);
+ if (!delta)
+ return NULL;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_ADDED:
+ if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_MODIFIED:
+ if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
+ git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+ return delta;
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
{
size_t len = strlen(prefix);
@@ -368,6 +398,10 @@ static git_diff_list *git_diff_list_alloc(
diff->opts.new_prefix = swap;
}
+ /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
+ if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
+ diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
+
/* only copy pathspec if it is "interesting" so we can test
* diff->pathspec.length > 0 to know if it is worth calling
* fnmatch as we iterate.
@@ -508,6 +542,7 @@ static int maybe_modified(
git_delta_t status = GIT_DELTA_MODIFIED;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
+ bool new_is_workdir = (new_iter->type == GIT_ITERATOR_WORKDIR);
GIT_UNUSED(old_iter);
@@ -515,15 +550,14 @@ static int maybe_modified(
return 0;
/* on platforms with no symlinks, preserve mode of existing symlinks */
- if (S_ISLNK(omode) && S_ISREG(nmode) &&
- !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS) &&
- new_iter->type == GIT_ITERATOR_WORKDIR)
+ if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
+ !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = omode;
/* on platforms with no execmode, just preserve old mode */
if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
(nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
- new_iter->type == GIT_ITERATOR_WORKDIR)
+ new_is_workdir)
nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
/* support "assume unchanged" (poorly, b/c we still stat everything) */
@@ -537,10 +571,14 @@ static int maybe_modified(
/* if basic type of file changed, then split into delete and add */
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
- diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
- return -1;
- return 0;
+ if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE) != 0)
+ status = GIT_DELTA_TYPECHANGE;
+ else {
+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
+ diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
+ return -1;
+ return 0;
+ }
}
/* if oids and modes match, then file is unmodified */
@@ -551,9 +589,7 @@ static int maybe_modified(
/* if we have an unknown OID and a workdir iterator, then check some
* circumstances that can accelerate things or need special handling
*/
- else if (git_oid_iszero(&nitem->oid) &&
- new_iter->type == GIT_ITERATOR_WORKDIR)
- {
+ else if (git_oid_iszero(&nitem->oid) && new_is_workdir) {
/* TODO: add check against index file st_mtime to avoid racy-git */
/* if the stat data looks exactly alike, then assume the same */
@@ -588,7 +624,7 @@ static int maybe_modified(
/* grab OID while we are here */
if (git_oid_iszero(&nitem->oid)) {
const git_oid *sm_oid = git_submodule_wd_oid(sub);
- if (sub != NULL) {
+ if (sm_oid != NULL) {
git_oid_cpy(&noid, sm_oid);
use_noid = &noid;
}
@@ -600,7 +636,7 @@ static int maybe_modified(
/* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now
*/
- if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
+ if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1;
else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
@@ -630,6 +666,24 @@ static int git_index_entry_cmp_icase(const void *a, const void *b)
return strcasecmp(entry_a->path, entry_b->path);
}
+static bool entry_is_prefixed(
+ const git_index_entry *item,
+ git_iterator *prefix_iterator,
+ const git_index_entry *prefix_item)
+{
+ size_t pathlen;
+
+ if (!prefix_item ||
+ ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path))
+ return false;
+
+ pathlen = strlen(item->path);
+
+ return (item->path[pathlen - 1] == '/' ||
+ prefix_item->path[pathlen] == '\0' ||
+ prefix_item->path[pathlen] == '/');
+}
+
static int diff_from_iterators(
git_repository *repo,
const git_diff_options *opts, /**< can be NULL for defaults */
@@ -679,8 +733,24 @@ static int diff_from_iterators(
/* create DELETED records for old items not matched in new */
if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
- git_iterator_advance(old_iter, &oitem) < 0)
+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
+ goto fail;
+
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating a DELETE record
+ */
+ if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+ entry_is_prefixed(oitem, new_iter, nitem))
+ {
+ /* this entry has become a tree! convert to TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->new_file.mode = GIT_FILEMODE_TREE;
+ }
+ }
+
+ if (git_iterator_advance(old_iter, &oitem) < 0)
goto fail;
}
@@ -702,8 +772,7 @@ static int diff_from_iterators(
* directories and it is not under an ignored directory.
*/
bool contains_tracked =
- (oitem &&
- !ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path));
+ entry_is_prefixed(nitem, old_iter, oitem);
bool recurse_untracked =
(delta_type == GIT_DELTA_UNTRACKED &&
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
@@ -759,8 +828,25 @@ static int diff_from_iterators(
else if (new_iter->type != GIT_ITERATOR_WORKDIR)
delta_type = GIT_DELTA_ADDED;
- if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
- git_iterator_advance(new_iter, &nitem) < 0)
+ if (diff_delta__from_one(diff, delta_type, nitem) < 0)
+ goto fail;
+
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating an ADD/UNTRACKED record
+ */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+ entry_is_prefixed(nitem, old_iter, oitem))
+ {
+ /* this entry was a tree! convert to TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->old_file.mode = GIT_FILEMODE_TREE;
+ }
+ }
+
+ if (git_iterator_advance(new_iter, &nitem) < 0)
goto fail;
}