Commit a2e895be by Russell Belfer

Continue implementation of git-diff

* Implemented git_diff_index_to_tree
* Reworked git_diff_options structure to handle more options
* Made most of the options in git_diff_options actually work
* Reorganized code a bit to remove some redundancy
* Added option parsing to examples/diff.c to test most options
parent 5a2f097f
......@@ -3,12 +3,13 @@
CC = gcc
CFLAGS = -g -I../include -I../src
LFLAGS = -L../build -lgit2 -lz
APPS = general showindex diff
all: general showindex diff
all: $(APPS)
% : %.c
$(CC) -o $@ $(CFLAGS) $< $(LFLAGS)
clean:
$(RM) general showindex diff
$(RM) $(APPS)
$(RM) -r *.dSYM
......@@ -87,46 +87,123 @@ int printer(void *data, char usage, const char *line)
return 0;
}
int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
{
size_t len = strlen(pattern);
uint16_t strval;
char *endptr = NULL;
if (strncmp(arg, pattern, len))
return 0;
strval = strtoul(arg + len, &endptr, 0);
if (endptr == arg)
return 0;
*val = strval;
return 1;
}
int check_str_param(const char *arg, const char *pattern, char **val)
{
size_t len = strlen(pattern);
if (strncmp(arg, pattern, len))
return 0;
*val = (char *)(arg + len);
return 1;
}
void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
else if (message)
fprintf(stderr, "%s\n", message);
fprintf(stderr, "usage: diff <tree-oid> <tree-oid>\n");
exit(1);
}
int main(int argc, char *argv[])
{
char path[GIT_PATH_MAX];
git_repository *repo = NULL;
git_tree *a, *b;
git_tree *t1 = NULL, *t2 = NULL;
git_diff_options opts = {0};
git_diff_list *diff;
char *dir = ".";
int color = -1;
int i, color = -1, compact = 0;
char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL;
if (argc != 3) {
fprintf(stderr, "usage: diff <tree-oid> <tree-oid>\n");
exit(1);
/* parse arguments as copied from git-diff */
for (i = 1; i < argc; ++i) {
a = argv[i];
if (a[0] != '-') {
if (treeish1 == NULL)
treeish1 = a;
else if (treeish2 == NULL)
treeish2 = a;
else
usage("Only one or two tree identifiers can be provided", NULL);
}
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
!strcmp(a, "--patch"))
compact = 0;
else if (!strcmp(a, "--name-status"))
compact = 1;
else if (!strcmp(a, "--color"))
color = 0;
else if (!strcmp(a, "--no-color"))
color = -1;
else if (!strcmp(a, "-R"))
opts.flags |= GIT_DIFF_REVERSE;
else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
opts.flags |= GIT_DIFF_FORCE_TEXT;
else if (!strcmp(a, "--ignore-space-at-eol"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
opts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
else if (!check_uint16_param(a, "-U", &opts.context_lines) &&
!check_uint16_param(a, "--unified=", &opts.context_lines) &&
!check_uint16_param(a, "--inter-hunk-context=",
&opts.interhunk_lines) &&
!check_str_param(a, "--src-prefix=", &opts.src_prefix) &&
!check_str_param(a, "--dst-prefix=", &opts.dst_prefix))
usage("Unknown arg", a);
}
if (!treeish1)
usage("Must provide at least one tree identifier (for now)", NULL);
/* open repo */
check(git_repository_discover(path, sizeof(path), dir, 0, "/"),
"Could not discover repository");
check(git_repository_open(&repo, path),
"Could not open repository");
check(resolve_to_tree(repo, argv[1], &a), "Looking up first tree");
check(resolve_to_tree(repo, argv[2], &b), "Looking up second tree");
check(git_diff_tree_to_tree(repo, &opts, a, b, &diff), "Generating diff");
fputs(colors[0], stdout);
check(git_diff_print_compact(diff, &color, printer), "Displaying diff summary");
check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
if (treeish2)
check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree");
fprintf(stdout, "--\n");
if (!treeish2)
check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Generating diff");
else
check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Generating diff");
color = 0;
if (color >= 0)
fputs(colors[0], stdout);
check(git_diff_print_patch(diff, &color, printer), "Displaying diff");
if (compact)
check(git_diff_print_compact(diff, &color, printer), "Displaying diff summary");
else
check(git_diff_print_patch(diff, &color, printer), "Displaying diff");
fputs(colors[0], stdout);
if (color >= 0)
fputs(colors[0], stdout);
git_diff_list_free(diff);
git_tree_free(a);
git_tree_free(b);
git_tree_free(t1);
git_tree_free(t2);
git_repository_free(repo);
return 0;
......
......@@ -29,17 +29,33 @@
*/
GIT_BEGIN_DECL
enum {
GIT_DIFF_NORMAL = 0,
GIT_DIFF_REVERSE = (1 << 0),
GIT_DIFF_FORCE_TEXT = (1 << 1),
GIT_DIFF_IGNORE_WHITESPACE = (1 << 2),
GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3),
GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4),
GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),
GIT_DIFF_PATIENCE = (1 << 6)
};
/**
* Structure describing options about how the diff should be executed.
*
* Setting all values of the structure to zero will yield the default
* values. Similarly, passing NULL for the options structure will
* give the defaults. The default values are marked below.
*
* @todo Most of the parameters here are not actually supported at this time.
*/
typedef struct {
int context_lines;
int interhunk_lines;
int ignore_whitespace;
int force_text; /**< generate text diffs even for binaries */
git_strarray pathspec;
uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */
uint16_t context_lines; /**< defaults to 3 */
uint16_t interhunk_lines; /**< defaults to 3 */
char *src_prefix; /**< defaults to "a" */
char *dst_prefix; /**< defaults to "b" */
git_strarray pathspec; /**< defaults to show all paths */
} git_diff_options;
/**
......@@ -158,7 +174,7 @@ typedef int (*git_diff_output_fn)(
*/
GIT_EXTERN(int) git_diff_tree_to_tree(
git_repository *repo,
const git_diff_options *opts,
const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old,
git_tree *new,
git_diff_list **diff);
......@@ -169,7 +185,7 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
*/
GIT_EXTERN(int) git_diff_index_to_tree(
git_repository *repo,
const git_diff_options *opts,
const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old,
git_diff_list **diff);
......@@ -179,7 +195,7 @@ GIT_EXTERN(int) git_diff_index_to_tree(
*/
GIT_EXTERN(int) git_diff_workdir_to_tree(
git_repository *repo,
const git_diff_options *opts,
const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old,
git_diff_list **diff);
......@@ -189,7 +205,7 @@ GIT_EXTERN(int) git_diff_workdir_to_tree(
*/
GIT_EXTERN(int) git_diff_workdir_to_index(
git_repository *repo,
const git_diff_options *opts,
const git_diff_options *opts, /**< can be NULL for defaults */
git_diff_list **diff);
/**
......
......@@ -14,8 +14,11 @@
struct git_diff_list {
git_repository *repo;
git_diff_options opts;
git_buf pfx;
git_vector files; /* vector of git_diff_file_delta */
/* the following are just used while processing the diff list */
git_buf pfx;
git_status_t mode;
};
#endif
......
......@@ -22,7 +22,7 @@ void test_diff_blob__0(void)
{
git_blob *a, *b, *c, *d;
git_oid a_oid, b_oid, c_oid, d_oid;
git_diff_options opts;
git_diff_options opts = {0};
diff_expects exp;
/* tests/resources/attr/root_test1 */
......@@ -44,8 +44,7 @@ void test_diff_blob__0(void)
/* Doing the equivalent of a `git diff -U1` on these files */
opts.context_lines = 1;
opts.interhunk_lines = 0;
opts.ignore_whitespace = 0;
opts.interhunk_lines = 1;
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_blobs(
......
......@@ -82,3 +82,23 @@ int diff_line_fn(
}
return 0;
}
git_tree *resolve_commit_oid_to_tree(
git_repository *repo,
const char *partial_oid)
{
size_t len = strlen(partial_oid);
git_oid oid;
git_object *obj;
git_tree *tree;
if (git_oid_fromstrn(&oid, partial_oid, len) == 0)
git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY);
cl_assert(obj);
if (git_object_type(obj) == GIT_OBJ_TREE)
return (git_tree *)obj;
cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT);
cl_git_pass(git_commit_tree(&tree, (git_commit *)obj));
git_object_free(obj);
return tree;
}
......@@ -38,3 +38,4 @@ extern int diff_line_fn(
char line_origin,
const char *content,
size_t content_len);
#include "clar_libgit2.h"
#include "diff_helpers.h"
static git_repository *g_repo = NULL;
void test_diff_index__initialize(void)
{
cl_fixture_sandbox("status");
cl_git_pass(p_rename("status/.gitted", "status/.git"));
cl_git_pass(git_repository_open(&g_repo, "status/.git"));
}
void test_diff_index__cleanup(void)
{
git_repository_free(g_repo);
g_repo = NULL;
cl_fixture_cleanup("status");
}
void test_diff_index__0(void)
{
/* grabbed a couple of commit oids from the history of the attr repo */
const char *a_commit = "26a125ee1bf"; /* the current HEAD */
const char *b_commit = "0017bd4ab1ec3"; /* the start */
git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
git_diff_options opts = {0};
git_diff_list *diff = NULL;
diff_expects exp;
cl_assert(a);
cl_assert(b);
opts.context_lines = 1;
opts.interhunk_lines = 1;
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
/* to generate these values:
* - cd to tests/resources/status,
* - mv .gitted .git
* - git diff --name-status --cached 26a125ee1bf
* - git diff -U1 --cached 26a125ee1bf
* - mv .git .gitted
*/
cl_assert(exp.files == 8);
cl_assert(exp.file_adds == 3);
cl_assert(exp.file_dels == 2);
cl_assert(exp.file_mods == 3);
cl_assert(exp.hunks == 8);
cl_assert(exp.lines == 11);
cl_assert(exp.line_ctxt == 3);
cl_assert(exp.line_adds == 6);
cl_assert(exp.line_dels == 2);
git_diff_list_free(diff);
diff = NULL;
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
/* to generate these values:
* - cd to tests/resources/status,
* - mv .gitted .git
* - git diff --name-status --cached 0017bd4ab1ec3
* - git diff -U1 --cached 0017bd4ab1ec3
* - mv .git .gitted
*/
cl_assert(exp.files == 12);
cl_assert(exp.file_adds == 7);
cl_assert(exp.file_dels == 2);
cl_assert(exp.file_mods == 3);
cl_assert(exp.hunks == 12);
cl_assert(exp.lines == 16);
cl_assert(exp.line_ctxt == 3);
cl_assert(exp.line_adds == 11);
cl_assert(exp.line_dels == 2);
git_diff_list_free(diff);
diff = NULL;
git_tree_free(a);
git_tree_free(b);
}
......@@ -18,34 +18,16 @@ void test_diff_tree__cleanup(void)
cl_fixture_cleanup("attr");
}
static git_tree *resolve_commit_oid_to_tree(const char *partial_oid)
{
size_t len = strlen(partial_oid);
git_oid oid;
git_object *obj;
git_tree *tree;
if (git_oid_fromstrn(&oid, partial_oid, len) == 0)
git_object_lookup_prefix(&obj, g_repo, &oid, len, GIT_OBJ_ANY);
cl_assert(obj);
if (git_object_type(obj) == GIT_OBJ_TREE)
return (git_tree *)obj;
cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT);
cl_git_pass(git_commit_tree(&tree, (git_commit *)obj));
git_object_free(obj);
return tree;
}
void test_diff_tree__0(void)
{
/* grabbed a couple of commit oids from the history of the attr repo */
const char *a_commit = "605812a";
const char *b_commit = "370fe9ec22";
const char *c_commit = "f5b0af1fb4f5c";
git_tree *a = resolve_commit_oid_to_tree(a_commit);
git_tree *b = resolve_commit_oid_to_tree(b_commit);
git_tree *c = resolve_commit_oid_to_tree(c_commit);
git_diff_options opts;
git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
git_tree *c = resolve_commit_oid_to_tree(g_repo, c_commit);
git_diff_options opts = {0};
git_diff_list *diff = NULL;
diff_expects exp;
......@@ -53,8 +35,7 @@ void test_diff_tree__0(void)
cl_assert(b);
opts.context_lines = 1;
opts.interhunk_lines = 0;
opts.ignore_whitespace = 0;
opts.interhunk_lines = 1;
memset(&exp, 0, sizeof(exp));
......
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