blob: 297ac09dd40992542b4f9e481fc664f97319710e [file] [log] [blame]
/*
* Copyright (C) 2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "iterator.h"
#include "diff.h"
#include "diff_tree.h"
#include "git2/diff_tree.h"
#include "git2/oid.h"
#include "git2/config.h"
/**
* n-way tree differencing
*/
static int index_entry_cmp(git_index_entry *a, git_index_entry *b)
{
int diff;
assert (a && b);
/* Ignore tree changes */
if (S_ISDIR(a->mode) && S_ISDIR(b->mode))
return 0;
if ((diff = a->mode - b->mode) == 0)
diff = git_oid_cmp(&a->oid, &b->oid);
return diff;
}
int git_diff_tree_many(
git_repository *repo,
const git_tree **trees,
size_t trees_length,
uint32_t flags,
git_diff_tree_many_cb callback,
void *payload)
{
git_iterator **iterators;
git_index_entry **items = NULL, *best_cur_item, **cur_items;
git_vector_cmp entry_compare = git_index_entry__cmp;
int cur_item_modified;
size_t i;
int error = 0;
assert(repo && trees && callback);
iterators = git__calloc(trees_length, sizeof(git_iterator *));
GITERR_CHECK_ALLOC(iterators);
items = git__calloc(trees_length, sizeof(git_index_entry *));
GITERR_CHECK_ALLOC(items);
cur_items = git__calloc(trees_length, sizeof(git_index_entry *));
GITERR_CHECK_ALLOC(cur_items);
for (i = 0; i < trees_length; i++) {
if ((error = git_iterator_for_tree(&iterators[i], repo, trees[i])) < 0)
return -1;
}
/* Set up the iterators */
for (i = 0; i < trees_length; i++) {
if ((error = git_iterator_current(iterators[i],
(const git_index_entry **)&items[i])) < 0)
goto done;
}
while (true) {
memset(cur_items, 0x0, sizeof(git_index_entry *) * trees_length);
best_cur_item = NULL;
cur_item_modified = 0;
/* Find the next path(s) to consume from each iterator */
for (i = 0; i < trees_length; i++) {
if (items[i] == NULL) {
cur_item_modified = 1;
continue;
}
if (best_cur_item == NULL) {
best_cur_item = items[i];
cur_items[i] = items[i];
} else {
int path_diff = entry_compare(items[i], best_cur_item);
if (path_diff < 0) {
/*
* Found an item that sorts before our current item, make
* our current item this one.
*/
memset(cur_items, 0x0, sizeof(git_index_entry *) * trees_length);
cur_item_modified = 1;
best_cur_item = items[i];
cur_items[i] = items[i];
} else if (path_diff > 0) {
/* No entry for the current item, this is modified */
cur_item_modified = 1;
} else if (path_diff == 0) {
cur_items[i] = items[i];
if (!cur_item_modified && !(flags & GIT_DIFF_TREE_RETURN_UNMODIFIED))
cur_item_modified = index_entry_cmp(best_cur_item, items[i]);
}
}
}
if (best_cur_item == NULL)
break;
if (cur_item_modified || (flags & GIT_DIFF_TREE_RETURN_UNMODIFIED)) {
if (callback((const git_index_entry **)cur_items, payload)) {
error = GIT_EUSER;
goto done;
}
}
/* Advance each iterator that participated */
for (i = 0; i < trees_length; i++) {
if (cur_items[i] != NULL &&
(error = git_iterator_advance(iterators[i],
(const git_index_entry **)&items[i])) < 0)
goto done;
}
}
done:
for (i = 0; i < trees_length; i++)
git_iterator_free(iterators[i]);
git__free(iterators);
git__free(items);
git__free(cur_items);
return error;
}
/**
* Three-way tree differencing
*/
typedef enum {
INDEX_ANCESTOR = 0,
INDEX_OURS = 1,
INDEX_THEIRS = 2
} diff_tree_threeway_index;
struct diff_tree_threeway_data {
git_diff_tree_list *diff_tree;
const char *df_path;
const char *prev_path;
git_diff_tree_delta *prev_delta_tree;
};
static git_diff_tree_list *diff_tree__list_alloc(git_repository *repo)
{
git_diff_tree_list *diff_tree =
git__calloc(1, sizeof(git_diff_tree_list));
if (diff_tree == NULL)
return NULL;
diff_tree->repo = repo;
if (git_vector_init(&diff_tree->deltas, 0, git_diff_delta__cmp) < 0 ||
git_pool_init(&diff_tree->pool, 1, 0) < 0)
return NULL;
return diff_tree;
}
GIT_INLINE(const char *) diff_tree__path(const git_diff_tree_delta *delta_tree)
{
if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->ancestor))
return delta_tree->ancestor.file.path;
else if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->ours))
return delta_tree->ours.file.path;
else if (GIT_DIFF_TREE_FILE_EXISTS(delta_tree->theirs))
return delta_tree->theirs.file.path;
return NULL;
}
GIT_INLINE(bool) diff_tree__delta_added_or_modified(
const git_diff_tree_delta *delta_tree)
{
if (delta_tree->ours.status == GIT_DELTA_ADDED ||
delta_tree->ours.status == GIT_DELTA_MODIFIED ||
delta_tree->theirs.status == GIT_DELTA_ADDED ||
delta_tree->theirs.status == GIT_DELTA_MODIFIED)
return true;
return false;
}
GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child)
{
size_t child_len = strlen(child);
size_t parent_len = strlen(parent);
if (child_len < parent_len ||
strncmp(parent, child, parent_len) != 0)
return 0;
return (child[parent_len] == '/');
}
GIT_INLINE(int) diff_tree__compute_df_conflict(
struct diff_tree_threeway_data *threeway_data,
git_diff_tree_delta *delta_tree)
{
const char *cur_path = diff_tree__path(delta_tree);
/* Determine if this is a D/F conflict or the child of one */
if (threeway_data->df_path &&
path_is_prefixed(threeway_data->df_path, cur_path))
delta_tree->df_conflict = GIT_DIFF_TREE_DF_CHILD;
else if(threeway_data->df_path)
threeway_data->df_path = NULL;
else if (threeway_data->prev_path &&
diff_tree__delta_added_or_modified(threeway_data->prev_delta_tree) &&
diff_tree__delta_added_or_modified(delta_tree) &&
path_is_prefixed(threeway_data->prev_path, cur_path)) {
delta_tree->df_conflict = GIT_DIFF_TREE_DF_CHILD;
threeway_data->prev_delta_tree->df_conflict = GIT_DIFF_TREE_DF_DIRECTORY_FILE;
threeway_data->df_path = threeway_data->prev_path;
}
threeway_data->prev_path = cur_path;
threeway_data->prev_delta_tree = delta_tree;
return 0;
}
GIT_INLINE(int) diff_tree__compute_conflict(
git_diff_tree_delta *delta_tree)
{
if (delta_tree->ours.status == GIT_DELTA_ADDED &&
delta_tree->theirs.status == GIT_DELTA_ADDED)
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_ADDED;
else if (delta_tree->ours.status == GIT_DELTA_MODIFIED &&
delta_tree->theirs.status == GIT_DELTA_MODIFIED)
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_MODIFIED;
else if (delta_tree->ours.status == GIT_DELTA_DELETED &&
delta_tree->theirs.status == GIT_DELTA_DELETED)
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_BOTH_DELETED;
else if (delta_tree->ours.status == GIT_DELTA_MODIFIED &&
delta_tree->theirs.status == GIT_DELTA_DELETED)
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_MODIFY_DELETE;
else if (delta_tree->ours.status == GIT_DELTA_DELETED &&
delta_tree->theirs.status == GIT_DELTA_MODIFIED)
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_MODIFY_DELETE;
else
delta_tree->conflict = GIT_DIFF_TREE_CONFLICT_NONE;
return 0;
}
static git_diff_tree_delta *diff_tree__delta_from_entries(
struct diff_tree_threeway_data *threeway_data,
const git_index_entry **entries)
{
git_diff_tree_delta *delta_tree;
git_diff_tree_entry *tree_entries[3];
size_t i;
if ((delta_tree = git_pool_malloc(&threeway_data->diff_tree->pool, sizeof(git_diff_tree_delta))) == NULL)
return NULL;
tree_entries[INDEX_ANCESTOR] = &delta_tree->ancestor;
tree_entries[INDEX_OURS] = &delta_tree->ours;
tree_entries[INDEX_THEIRS] = &delta_tree->theirs;
for (i = 0; i < 3; i++) {
if (entries[i] == NULL)
continue;
if ((tree_entries[i]->file.path = git_pool_strdup(&threeway_data->diff_tree->pool, entries[i]->path)) == NULL)
return NULL;
git_oid_cpy(&tree_entries[i]->file.oid, &entries[i]->oid);
tree_entries[i]->file.size = entries[i]->file_size;
tree_entries[i]->file.mode = entries[i]->mode;
tree_entries[i]->file.flags |= GIT_DIFF_FILE_VALID_OID;
}
for (i = 1; i < 3; i++) {
if (entries[INDEX_ANCESTOR] == NULL && entries[i] == NULL)
continue;
if (entries[INDEX_ANCESTOR] == NULL && entries[i] != NULL)
tree_entries[i]->status |= GIT_DELTA_ADDED;
else if (entries[INDEX_ANCESTOR] != NULL && entries[i] == NULL)
tree_entries[i]->status |= GIT_DELTA_DELETED;
else if (S_ISDIR(entries[INDEX_ANCESTOR]->mode) ^ S_ISDIR(entries[i]->mode))
tree_entries[i]->status |= GIT_DELTA_TYPECHANGE;
else if(S_ISLNK(entries[INDEX_ANCESTOR]->mode) ^ S_ISLNK(entries[i]->mode))
tree_entries[i]->status |= GIT_DELTA_TYPECHANGE;
else if (git_oid_cmp(&entries[INDEX_ANCESTOR]->oid, &entries[i]->oid) ||
entries[INDEX_ANCESTOR]->mode != entries[i]->mode)
tree_entries[i]->status |= GIT_DELTA_MODIFIED;
}
return delta_tree;
}
static int diff_tree__create_delta(const git_index_entry **tree_items, void *payload)
{
struct diff_tree_threeway_data *threeway_data = payload;
git_diff_tree_delta *delta_tree;
assert(tree_items && threeway_data);
if ((delta_tree = diff_tree__delta_from_entries(threeway_data, tree_items)) == NULL ||
diff_tree__compute_conflict(delta_tree) < 0 ||
diff_tree__compute_df_conflict(threeway_data, delta_tree) < 0 ||
git_vector_insert(&threeway_data->diff_tree->deltas, delta_tree) < 0)
return -1;
return 0;
}
int git_diff_tree(git_diff_tree_list **out,
git_repository *repo,
const git_tree *ancestor_tree,
const git_tree *our_tree,
const git_tree *their_tree,
uint32_t flags)
{
struct diff_tree_threeway_data threeway_data;
git_diff_tree_list *diff_tree;
git_tree const *trees[3];
int error = 0;
assert(out && repo && ancestor_tree && our_tree && their_tree);
*out = NULL;
diff_tree = diff_tree__list_alloc(repo);
GITERR_CHECK_ALLOC(diff_tree);
memset(&threeway_data, 0x0, sizeof(struct diff_tree_threeway_data));
threeway_data.diff_tree = diff_tree;
trees[INDEX_ANCESTOR] = ancestor_tree;
trees[INDEX_OURS] = our_tree;
trees[INDEX_THEIRS] = their_tree;
if ((error = git_diff_tree_many(repo, trees, 3, flags, diff_tree__create_delta, &threeway_data)) < 0)
git_diff_tree_list_free(diff_tree);
if (error >= 0)
*out = diff_tree;
return error;
}
int git_diff_tree_foreach(
git_diff_tree_list *diff_tree,
git_diff_tree_delta_cb callback,
void *payload)
{
git_diff_tree_delta *delta;
size_t i;
int error = 0;
assert (diff_tree && callback);
git_vector_foreach(&diff_tree->deltas, i, delta) {
if (callback(delta, payload) != 0) {
error = GIT_EUSER;
break;
}
}
return error;
}
void git_diff_tree_list_free(git_diff_tree_list *diff_tree)
{
if (!diff_tree)
return;
git_vector_free(&diff_tree->deltas);
git_pool_clear(&diff_tree->pool);
git__free(diff_tree);
}