summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/diff.h19
-rw-r--r--src/diff_patch.c42
-rw-r--r--src/diff_patch.h2
-rw-r--r--src/diff_tform.c81
-rw-r--r--src/stash.c29
5 files changed, 101 insertions, 72 deletions
diff --git a/src/diff.h b/src/diff.h
index 2a35fd9ac..a202a086c 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -123,6 +123,25 @@ extern int git_diff_find_similar__calc_similarity(
extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
+/* Merge two `git_diff`s according to the callback given by `cb`. */
+
+typedef git_diff_delta *(*git_diff__merge_cb)(
+ const git_diff_delta *left,
+ const git_diff_delta *right,
+ git_pool *pool);
+
+extern int git_diff__merge(
+ git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
+
+extern git_diff_delta *git_diff__merge_like_cgit(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool);
+
+/* Duplicate a `git_diff_delta` out of the `git_pool` */
+extern git_diff_delta *git_diff__delta_dup(
+ const git_diff_delta *d, git_pool *pool);
+
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
diff --git a/src/diff_patch.c b/src/diff_patch.c
index 60648f8d7..ec4979a52 100644
--- a/src/diff_patch.c
+++ b/src/diff_patch.c
@@ -121,6 +121,35 @@ GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file)
return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
}
+static bool diff_patch_diffable(git_patch *patch)
+{
+ size_t olen, nlen;
+
+ if (patch->delta->status == GIT_DELTA_UNMODIFIED)
+ return false;
+
+ /* if we've determined this to be binary (and we are not showing binary
+ * data) then we have skipped loading the map data. instead, query the
+ * file data itself.
+ */
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
+ (patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
+ olen = (size_t)patch->ofile.file->size;
+ nlen = (size_t)patch->nfile.file->size;
+ } else {
+ olen = patch->ofile.map.len;
+ nlen = patch->nfile.map.len;
+ }
+
+ /* if both sides are empty, files are identical */
+ if (!olen && !nlen)
+ return false;
+
+ /* otherwise, check the file sizes and the oid */
+ return (olen != nlen ||
+ !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
+}
+
static int diff_patch_load(git_patch *patch, git_diff_output *output)
{
int error = 0;
@@ -186,18 +215,7 @@ cleanup:
diff_patch_update_binary(patch);
if (!error) {
- bool skip_binary =
- (patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
- (patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0;
-
- /* patch is diffable only for non-binary, modified files where
- * at least one side has data and the data actually changed
- */
- if (!skip_binary &&
- patch->delta->status != GIT_DELTA_UNMODIFIED &&
- (patch->ofile.map.len || patch->nfile.map.len) &&
- (patch->ofile.map.len != patch->nfile.map.len ||
- !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)))
+ if (diff_patch_diffable(patch))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED;
diff --git a/src/diff_patch.h b/src/diff_patch.h
index f6ce57ddd..7b4dacdde 100644
--- a/src/diff_patch.h
+++ b/src/diff_patch.h
@@ -24,7 +24,9 @@ enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2),
+ /* the two sides are different */
GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
+ /* the difference between the two sides has been computed */
GIT_DIFF_PATCH_DIFFED = (1 << 4),
GIT_DIFF_PATCH_FLATTENED = (1 << 5),
};
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 041592fbf..92647e330 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -15,7 +15,7 @@
#include "fileops.h"
#include "config.h"
-static git_diff_delta *diff_delta__dup(
+git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool)
{
git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
@@ -46,7 +46,7 @@ fail:
return NULL;
}
-static git_diff_delta *diff_delta__merge_like_cgit(
+git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool)
@@ -67,23 +67,24 @@ static git_diff_delta *diff_delta__merge_like_cgit(
/* If one of the diffs is a conflict, just dup it */
if (b->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(b, pool);
+ return git_diff__delta_dup(b, pool);
if (a->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(a, pool);
+ return git_diff__delta_dup(a, pool);
/* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
- return diff_delta__dup(a, pool);
+ return git_diff__delta_dup(a, pool);
/* otherwise, base this diff on the 'b' diff */
- if ((dup = diff_delta__dup(b, pool)) == NULL)
+ if ((dup = git_diff__delta_dup(b, pool)) == NULL)
return NULL;
/* If 'a' status is uninteresting, then we're done */
- if (a->status == GIT_DELTA_UNMODIFIED)
+ if (a->status == GIT_DELTA_UNMODIFIED ||
+ a->status == GIT_DELTA_UNTRACKED ||
+ a->status == GIT_DELTA_UNREADABLE)
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
@@ -108,48 +109,8 @@ static git_diff_delta *diff_delta__merge_like_cgit(
return dup;
}
-static git_diff_delta *diff_delta__merge_like_cgit_reversed(
- const git_diff_delta *a,
- const git_diff_delta *b,
- git_pool *pool)
-{
- git_diff_delta *dup;
-
- /* reversed version of above logic */
-
- if (a->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(a, pool);
- if (b->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(b, pool);
-
- if (a->status == GIT_DELTA_UNMODIFIED)
- return diff_delta__dup(b, pool);
-
- if ((dup = diff_delta__dup(a, pool)) == NULL)
- return NULL;
-
- if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED || b->status == GIT_DELTA_UNREADABLE)
- return dup;
-
- if (dup->status == GIT_DELTA_DELETED) {
- if (b->status == GIT_DELTA_ADDED) {
- dup->status = GIT_DELTA_UNMODIFIED;
- dup->nfiles = 2;
- }
- } else {
- dup->status = b->status;
- dup->nfiles = b->nfiles;
- }
-
- git_oid_cpy(&dup->old_file.id, &b->old_file.id);
- dup->old_file.mode = b->old_file.mode;
- dup->old_file.size = b->old_file.size;
- dup->old_file.flags = b->old_file.flags;
-
- return dup;
-}
-
-int git_diff_merge(git_diff *onto, const git_diff *from)
+int git_diff__merge(
+ git_diff *onto, const git_diff *from, git_diff__merge_cb cb)
{
int error = 0;
git_pool onto_pool;
@@ -185,15 +146,16 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
if (cmp < 0) {
- delta = diff_delta__dup(o, &onto_pool);
+ delta = git_diff__delta_dup(o, &onto_pool);
i++;
} else if (cmp > 0) {
- delta = diff_delta__dup(f, &onto_pool);
+ delta = git_diff__delta_dup(f, &onto_pool);
j++;
} else {
- delta = reversed ?
- diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) :
- diff_delta__merge_like_cgit(o, f, &onto_pool);
+ const git_diff_delta *left = reversed ? f : o;
+ const git_diff_delta *right = reversed ? o : f;
+
+ delta = cb(left, right, &onto_pool);
i++;
j++;
}
@@ -201,7 +163,7 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
/* the ignore rules for the target may not match the source
* or the result of a merged delta could be skippable...
*/
- if (git_diff_delta__should_skip(&onto->opts, delta)) {
+ if (delta && git_diff_delta__should_skip(&onto->opts, delta)) {
git__free(delta);
continue;
}
@@ -232,6 +194,11 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
return error;
}
+int git_diff_merge(git_diff *onto, const git_diff *from)
+{
+ return git_diff__merge(onto, from, git_diff__merge_like_cgit);
+}
+
int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p)
{
@@ -380,7 +347,7 @@ static int insert_delete_side_of_split(
git_diff *diff, git_vector *onto, const git_diff_delta *delta)
{
/* make new record for DELETED side of split */
- git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool);
GITERR_CHECK_ALLOC(deleted);
deleted->status = GIT_DELTA_DELETED;
diff --git a/src/stash.c b/src/stash.c
index 8f512d4ad..9010c476d 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -22,6 +22,7 @@
#include "signature.h"
#include "iterator.h"
#include "merge.h"
+#include "diff.h"
static int create_error(int error, const char *msg)
{
@@ -292,6 +293,25 @@ cleanup:
return error;
}
+static git_diff_delta *stash_delta_merge(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool)
+{
+ /* Special case for stash: if a file is deleted in the index, but exists
+ * in the working tree, we need to stash the workdir copy for the workdir.
+ */
+ if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) {
+ git_diff_delta *dup = git_diff__delta_dup(b, pool);
+
+ if (dup)
+ dup->status = GIT_DELTA_MODIFIED;
+ return dup;
+ }
+
+ return git_diff__merge_like_cgit(a, b, pool);
+}
+
static int build_workdir_tree(
git_tree **tree_out,
git_index *index,
@@ -299,17 +319,19 @@ static int build_workdir_tree(
{
git_repository *repo = git_index_owner(index);
git_tree *b_tree = NULL;
- git_diff *diff = NULL;
+ git_diff *diff = NULL, *idx_to_wd = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
struct stash_update_rules data = {0};
int error;
- opts.flags = GIT_DIFF_IGNORE_SUBMODULES;
+ opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED;
if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
goto cleanup;
- if ((error = git_diff_tree_to_workdir(&diff, repo, b_tree, &opts)) < 0)
+ if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 ||
+ (error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 ||
+ (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0)
goto cleanup;
data.include_changed = true;
@@ -320,6 +342,7 @@ static int build_workdir_tree(
error = build_tree_from_index(tree_out, index);
cleanup:
+ git_diff_free(idx_to_wd);
git_diff_free(diff);
git_tree_free(b_tree);