mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-04 07:15:20 +00:00
Update diff to use iterators
This is a major reorganization of the diff code. This changes the diff functions to use the iterators for traversing the content. This allowed a lot of code to be simplified. Also, this moved the functions relating to outputting a diff into a new file (diff_output.c). This includes a number of other changes - adding utility functions, extending iterators, etc. plus more tests for the diff code. This also takes the example diff.c program much further in terms of emulating git-diff command line options.
This commit is contained in:
parent
760db29c45
commit
74fa4bfae3
@ -116,7 +116,7 @@ void usage(const char *message, const char *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");
|
||||
fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ int main(int argc, char *argv[])
|
||||
git_tree *t1 = NULL, *t2 = NULL;
|
||||
git_diff_options opts = {0};
|
||||
git_diff_list *diff;
|
||||
int i, color = -1, compact = 0;
|
||||
int i, color = -1, compact = 0, cached = 0;
|
||||
char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL;
|
||||
|
||||
/* parse arguments as copied from git-diff */
|
||||
@ -146,6 +146,8 @@ int main(int argc, char *argv[])
|
||||
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
|
||||
!strcmp(a, "--patch"))
|
||||
compact = 0;
|
||||
else if (!strcmp(a, "--cached"))
|
||||
cached = 1;
|
||||
else if (!strcmp(a, "--name-status"))
|
||||
compact = 1;
|
||||
else if (!strcmp(a, "--color"))
|
||||
@ -162,6 +164,10 @@ int main(int argc, char *argv[])
|
||||
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 (!strcmp(a, "--ignored"))
|
||||
opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
|
||||
else if (!strcmp(a, "--untracked"))
|
||||
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
|
||||
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=",
|
||||
@ -171,9 +177,6 @@ int main(int argc, char *argv[])
|
||||
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, "/"),
|
||||
@ -181,20 +184,40 @@ int main(int argc, char *argv[])
|
||||
check(git_repository_open(&repo, path),
|
||||
"Could not open repository");
|
||||
|
||||
check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
|
||||
if (treeish1)
|
||||
check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
|
||||
if (treeish2)
|
||||
check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree");
|
||||
|
||||
if (!treeish2)
|
||||
check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Generating diff");
|
||||
/* <sha1> <sha2> */
|
||||
/* <sha1> --cached */
|
||||
/* <sha1> */
|
||||
/* --cached */
|
||||
/* nothing */
|
||||
|
||||
if (t1 && t2)
|
||||
check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Diff");
|
||||
else if (t1 && cached)
|
||||
check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
|
||||
else if (t1) {
|
||||
git_diff_list *diff2;
|
||||
check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
|
||||
check(git_diff_workdir_to_index(repo, &opts, &diff2), "Diff");
|
||||
check(git_diff_merge(diff, diff2), "Merge diffs");
|
||||
git_diff_list_free(diff2);
|
||||
}
|
||||
else if (cached) {
|
||||
check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD");
|
||||
check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
|
||||
}
|
||||
else
|
||||
check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Generating diff");
|
||||
check(git_diff_workdir_to_index(repo, &opts, &diff), "Diff");
|
||||
|
||||
if (color >= 0)
|
||||
fputs(colors[0], stdout);
|
||||
|
||||
if (compact)
|
||||
check(git_diff_print_compact(diff, &color, printer), "Displaying diff summary");
|
||||
check(git_diff_print_compact(diff, &color, printer), "Displaying diff");
|
||||
else
|
||||
check(git_diff_print_patch(diff, &color, printer), "Displaying diff");
|
||||
|
||||
|
@ -37,7 +37,9 @@ enum {
|
||||
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)
|
||||
GIT_DIFF_PATIENCE = (1 << 6),
|
||||
GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
|
||||
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8)
|
||||
};
|
||||
|
||||
/**
|
||||
@ -63,6 +65,26 @@ typedef struct {
|
||||
*/
|
||||
typedef struct git_diff_list git_diff_list;
|
||||
|
||||
enum {
|
||||
GIT_DIFF_FILE_VALID_OID = (1 << 0),
|
||||
GIT_DIFF_FILE_FREE_PATH = (1 << 1),
|
||||
GIT_DIFF_FILE_BINARY = (1 << 2),
|
||||
GIT_DIFF_FILE_NOT_BINARY = (1 << 3),
|
||||
GIT_DIFF_FILE_FREE_DATA = (1 << 4),
|
||||
GIT_DIFF_FILE_UNMAP_DATA = (1 << 5)
|
||||
};
|
||||
|
||||
/**
|
||||
* Description of one side of a diff.
|
||||
*/
|
||||
typedef struct {
|
||||
git_oid oid;
|
||||
char *path;
|
||||
uint16_t mode;
|
||||
git_off_t size;
|
||||
unsigned int flags;
|
||||
} git_diff_file;
|
||||
|
||||
/**
|
||||
* Description of changes to one file.
|
||||
*
|
||||
@ -77,17 +99,11 @@ typedef struct git_diff_list git_diff_list;
|
||||
* It will just use the git attributes for those files.
|
||||
*/
|
||||
typedef struct {
|
||||
git_diff_file old;
|
||||
git_diff_file new;
|
||||
git_status_t status; /**< value from tree.h */
|
||||
unsigned int old_attr;
|
||||
unsigned int new_attr;
|
||||
git_oid old_oid;
|
||||
git_oid new_oid;
|
||||
git_blob *old_blob;
|
||||
git_blob *new_blob;
|
||||
const char *path;
|
||||
const char *new_path; /**< NULL unless status is RENAMED or COPIED */
|
||||
int similarity; /**< for RENAMED and COPIED, value from 0 to 100 */
|
||||
int binary; /**< files in diff are binary? */
|
||||
unsigned int similarity; /**< for RENAMED and COPIED, value from 0 to 100 */
|
||||
int binary;
|
||||
} git_diff_delta;
|
||||
|
||||
/**
|
||||
@ -169,8 +185,19 @@ typedef int (*git_diff_output_fn)(
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
/**
|
||||
* Deallocate a diff list.
|
||||
*/
|
||||
GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
|
||||
|
||||
/**
|
||||
* Compute a difference between two tree objects.
|
||||
*
|
||||
* @param repo The repository containing the trees.
|
||||
* @param opts Structure with options to influence diff or NULL for defaults.
|
||||
* @param old A git_tree object to diff from.
|
||||
* @param new A git_tree object to diff to.
|
||||
* @param diff A pointer to a git_diff_list pointer that will be allocated.
|
||||
*/
|
||||
GIT_EXTERN(int) git_diff_tree_to_tree(
|
||||
git_repository *repo,
|
||||
@ -181,7 +208,11 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
|
||||
|
||||
/**
|
||||
* Compute a difference between a tree and the index.
|
||||
* @todo NOT IMPLEMENTED
|
||||
*
|
||||
* @param repo The repository containing the tree and index.
|
||||
* @param opts Structure with options to influence diff or NULL for defaults.
|
||||
* @param old A git_tree object to diff from.
|
||||
* @param diff A pointer to a git_diff_list pointer that will be allocated.
|
||||
*/
|
||||
GIT_EXTERN(int) git_diff_index_to_tree(
|
||||
git_repository *repo,
|
||||
@ -189,9 +220,34 @@ GIT_EXTERN(int) git_diff_index_to_tree(
|
||||
git_tree *old,
|
||||
git_diff_list **diff);
|
||||
|
||||
/**
|
||||
* Compute a difference between the working directory and the index.
|
||||
*
|
||||
* @param repo The repository.
|
||||
* @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.
|
||||
*/
|
||||
GIT_EXTERN(int) git_diff_workdir_to_index(
|
||||
git_repository *repo,
|
||||
const git_diff_options *opts, /**< can be NULL for defaults */
|
||||
git_diff_list **diff);
|
||||
|
||||
/**
|
||||
* Compute a difference between the working directory and a tree.
|
||||
* @todo NOT IMPLEMENTED
|
||||
*
|
||||
* This returns strictly the differences between the tree and the
|
||||
* files contained in the working directory, regardless of the state
|
||||
* of files in the index. There is no direct equivalent in C git.
|
||||
*
|
||||
* This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those
|
||||
* commands diff the tree, the index, and the workdir. To emulate those
|
||||
* functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`,
|
||||
* then call `git_diff_merge` on the results.
|
||||
*
|
||||
* @param repo The repository containing the tree.
|
||||
* @param opts Structure with options to influence diff or NULL for defaults.
|
||||
* @param old A git_tree object to diff from.
|
||||
* @param diff A pointer to a git_diff_list pointer that will be allocated.
|
||||
*/
|
||||
GIT_EXTERN(int) git_diff_workdir_to_tree(
|
||||
git_repository *repo,
|
||||
@ -200,18 +256,21 @@ GIT_EXTERN(int) git_diff_workdir_to_tree(
|
||||
git_diff_list **diff);
|
||||
|
||||
/**
|
||||
* Compute a difference between the working directory and the index.
|
||||
* @todo NOT IMPLEMENTED
|
||||
* Merge one diff list into another.
|
||||
*
|
||||
* This merges items from the "from" list into the "onto" list. The
|
||||
* resulting diff list will have all items that appear in either list.
|
||||
* If an item appears in both lists, then it will be "merged" to appear
|
||||
* as if the old version was from the "onto" list and the new version
|
||||
* is from the "from" list (with the exception that if the item has a
|
||||
* pending DELETE in the middle, then it will show as deleted).
|
||||
*
|
||||
* @param onto Diff to merge into.
|
||||
* @param from Diff to merge.
|
||||
*/
|
||||
GIT_EXTERN(int) git_diff_workdir_to_index(
|
||||
git_repository *repo,
|
||||
const git_diff_options *opts, /**< can be NULL for defaults */
|
||||
git_diff_list **diff);
|
||||
|
||||
/**
|
||||
* Deallocate a diff list.
|
||||
*/
|
||||
GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
|
||||
GIT_EXTERN(int) git_diff_merge(
|
||||
git_diff_list *onto,
|
||||
const git_diff_list *from);
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
@ -159,6 +159,11 @@ GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int le
|
||||
*/
|
||||
GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str);
|
||||
|
||||
/**
|
||||
* Check is an oid is all zeros.
|
||||
*/
|
||||
GIT_EXTERN(int) git_oid_iszero(const git_oid *a);
|
||||
|
||||
/**
|
||||
* OID Shortener object
|
||||
*/
|
||||
|
@ -31,7 +31,7 @@ GIT_BEGIN_DECL
|
||||
#define GIT_STATUS_WT_MODIFIED (1 << 4)
|
||||
#define GIT_STATUS_WT_DELETED (1 << 5)
|
||||
|
||||
#define GIT_STATUS_IGNORED (1 << 6)
|
||||
#define GIT_STATUS_WT_IGNORED (1 << 6)
|
||||
|
||||
/**
|
||||
* Gather file statuses and run a callback for each one.
|
||||
|
1441
src/diff.c
1441
src/diff.c
File diff suppressed because it is too large
Load Diff
10
src/diff.h
10
src/diff.h
@ -10,15 +10,15 @@
|
||||
#include <stdio.h>
|
||||
#include "vector.h"
|
||||
#include "buffer.h"
|
||||
#include "iterator.h"
|
||||
#include "repository.h"
|
||||
|
||||
struct git_diff_list {
|
||||
git_repository *repo;
|
||||
git_diff_options opts;
|
||||
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 status;
|
||||
git_vector deltas; /* vector of git_diff_file_delta */
|
||||
git_iterator_type_t old_src;
|
||||
git_iterator_type_t new_src;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
722
src/diff_output.c
Normal file
722
src/diff_output.c
Normal file
@ -0,0 +1,722 @@
|
||||
/*
|
||||
* 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 "git2/diff.h"
|
||||
#include "git2/attr.h"
|
||||
#include "git2/blob.h"
|
||||
#include "xdiff/xdiff.h"
|
||||
#include <ctype.h>
|
||||
#include "diff.h"
|
||||
#include "map.h"
|
||||
#include "fileops.h"
|
||||
|
||||
typedef struct {
|
||||
git_diff_list *diff;
|
||||
void *cb_data;
|
||||
git_diff_hunk_fn hunk_cb;
|
||||
git_diff_line_fn line_cb;
|
||||
unsigned int index;
|
||||
git_diff_delta *delta;
|
||||
} diff_output_info;
|
||||
|
||||
static int read_next_int(const char **str, int *value)
|
||||
{
|
||||
const char *scan = *str;
|
||||
int v = 0, digits = 0;
|
||||
/* find next digit */
|
||||
for (scan = *str; *scan && !isdigit(*scan); scan++);
|
||||
/* parse next number */
|
||||
for (; isdigit(*scan); scan++, digits++)
|
||||
v = (v * 10) + (*scan - '0');
|
||||
*str = scan;
|
||||
*value = v;
|
||||
return (digits > 0) ? GIT_SUCCESS : GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
|
||||
{
|
||||
int err = GIT_SUCCESS;
|
||||
diff_output_info *info = priv;
|
||||
|
||||
if (len == 1 && info->hunk_cb) {
|
||||
git_diff_range range = { -1, 0, -1, 0 };
|
||||
|
||||
/* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
|
||||
if (bufs[0].ptr[0] == '@') {
|
||||
const char *scan = bufs[0].ptr;
|
||||
if (!(err = read_next_int(&scan, &range.old_start)) && *scan == ',')
|
||||
err = read_next_int(&scan, &range.old_lines);
|
||||
if (!err &&
|
||||
!(err = read_next_int(&scan, &range.new_start)) && *scan == ',')
|
||||
err = read_next_int(&scan, &range.new_lines);
|
||||
if (!err && range.old_start >= 0 && range.new_start >= 0)
|
||||
err = info->hunk_cb(
|
||||
info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size);
|
||||
}
|
||||
}
|
||||
else if ((len == 2 || len == 3) && info->line_cb) {
|
||||
int origin;
|
||||
|
||||
/* expect " "/"-"/"+", then data, then maybe newline */
|
||||
origin =
|
||||
(*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
|
||||
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
|
||||
GIT_DIFF_LINE_CONTEXT;
|
||||
|
||||
err = info->line_cb(
|
||||
info->cb_data, info->delta, origin, bufs[1].ptr, bufs[1].size);
|
||||
|
||||
/* deal with adding and removing newline at EOF */
|
||||
if (err == GIT_SUCCESS && len == 3) {
|
||||
if (origin == GIT_DIFF_LINE_ADDITION)
|
||||
origin = GIT_DIFF_LINE_ADD_EOFNL;
|
||||
else
|
||||
origin = GIT_DIFF_LINE_DEL_EOFNL;
|
||||
|
||||
err = info->line_cb(
|
||||
info->cb_data, info->delta, origin, bufs[2].ptr, bufs[2].size);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
|
||||
|
||||
static int set_file_is_binary_by_attr(git_repository *repo, git_diff_file *file)
|
||||
{
|
||||
const char *value;
|
||||
int error = git_attr_get(repo, file->path, "diff", &value);
|
||||
if (error != GIT_SUCCESS)
|
||||
return error;
|
||||
if (value == GIT_ATTR_FALSE)
|
||||
file->flags |= GIT_DIFF_FILE_BINARY;
|
||||
else if (value == GIT_ATTR_TRUE)
|
||||
file->flags |= GIT_DIFF_FILE_NOT_BINARY;
|
||||
/* otherwise leave file->flags alone */
|
||||
return error;
|
||||
}
|
||||
|
||||
static void set_delta_is_binary(git_diff_delta *delta)
|
||||
{
|
||||
if ((delta->old.flags & GIT_DIFF_FILE_BINARY) != 0 ||
|
||||
(delta->new.flags & GIT_DIFF_FILE_BINARY) != 0)
|
||||
delta->binary = 1;
|
||||
else if ((delta->old.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 ||
|
||||
(delta->new.flags & GIT_DIFF_FILE_NOT_BINARY) != 0)
|
||||
delta->binary = 0;
|
||||
/* otherwise leave delta->binary value untouched */
|
||||
}
|
||||
|
||||
static int file_is_binary_by_attr(
|
||||
git_diff_list *diff,
|
||||
git_diff_delta *delta)
|
||||
{
|
||||
int error, mirror_new;
|
||||
|
||||
delta->binary = -1;
|
||||
|
||||
/* make sure files are conceivably mmap-able */
|
||||
if ((git_off_t)((size_t)delta->old.size) != delta->old.size ||
|
||||
(git_off_t)((size_t)delta->new.size) != delta->new.size)
|
||||
{
|
||||
delta->old.flags |= GIT_DIFF_FILE_BINARY;
|
||||
delta->new.flags |= GIT_DIFF_FILE_BINARY;
|
||||
delta->binary = 1;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* check if user is forcing us to text diff these files */
|
||||
if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) {
|
||||
delta->old.flags |= GIT_DIFF_FILE_NOT_BINARY;
|
||||
delta->new.flags |= GIT_DIFF_FILE_NOT_BINARY;
|
||||
delta->binary = 0;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* check diff attribute +, -, or 0 */
|
||||
error = set_file_is_binary_by_attr(diff->repo, &delta->old);
|
||||
if (error != GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
mirror_new = (delta->new.path == delta->old.path ||
|
||||
strcmp(delta->new.path, delta->old.path) == 0);
|
||||
if (mirror_new)
|
||||
delta->new.flags &= (delta->old.flags & BINARY_DIFF_FLAGS);
|
||||
else
|
||||
error = set_file_is_binary_by_attr(diff->repo, &delta->new);
|
||||
|
||||
set_delta_is_binary(delta);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int file_is_binary_by_content(
|
||||
git_diff_list *diff,
|
||||
git_diff_delta *delta,
|
||||
git_map *old_data,
|
||||
git_map *new_data)
|
||||
{
|
||||
GIT_UNUSED_ARG(diff);
|
||||
|
||||
if ((delta->old.flags & BINARY_DIFF_FLAGS) == 0) {
|
||||
size_t search_len = min(old_data->len, 4000);
|
||||
if (strnlen(old_data->data, search_len) != search_len)
|
||||
delta->old.flags |= GIT_DIFF_FILE_BINARY;
|
||||
else
|
||||
delta->old.flags |= GIT_DIFF_FILE_NOT_BINARY;
|
||||
}
|
||||
|
||||
if ((delta->new.flags & BINARY_DIFF_FLAGS) == 0) {
|
||||
size_t search_len = min(new_data->len, 4000);
|
||||
if (strnlen(new_data->data, search_len) != search_len)
|
||||
delta->new.flags |= GIT_DIFF_FILE_BINARY;
|
||||
else
|
||||
delta->new.flags |= GIT_DIFF_FILE_NOT_BINARY;
|
||||
}
|
||||
|
||||
set_delta_is_binary(delta);
|
||||
|
||||
/* TODO: if value != NULL, implement diff drivers */
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void setup_xdiff_options(
|
||||
git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
|
||||
{
|
||||
memset(cfg, 0, sizeof(xdemitconf_t));
|
||||
memset(param, 0, sizeof(xpparam_t));
|
||||
|
||||
cfg->ctxlen =
|
||||
(!opts || !opts->context_lines) ? 3 : opts->context_lines;
|
||||
cfg->interhunkctxlen =
|
||||
(!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines;
|
||||
|
||||
if (!opts)
|
||||
return;
|
||||
|
||||
if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE)
|
||||
param->flags |= XDF_WHITESPACE_FLAGS;
|
||||
if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
|
||||
param->flags |= XDF_IGNORE_WHITESPACE_CHANGE;
|
||||
if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
|
||||
param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
|
||||
}
|
||||
|
||||
static int get_blob_content(
|
||||
git_repository *repo,
|
||||
const git_oid *oid,
|
||||
git_map *map,
|
||||
git_blob **blob)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (git_oid_iszero(oid))
|
||||
return GIT_SUCCESS;
|
||||
|
||||
if ((error = git_blob_lookup(blob, repo, oid)) == GIT_SUCCESS) {
|
||||
map->data = (void *)git_blob_rawcontent(*blob);
|
||||
map->len = git_blob_rawsize(*blob);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int get_workdir_content(
|
||||
git_repository *repo,
|
||||
git_diff_file *file,
|
||||
git_map *map)
|
||||
{
|
||||
git_buf full_path = GIT_BUF_INIT;
|
||||
int error = git_buf_joinpath(
|
||||
&full_path, git_repository_workdir(repo), file->path);
|
||||
if (error != GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if (S_ISLNK(file->mode)) {
|
||||
file->flags |= GIT_DIFF_FILE_FREE_DATA;
|
||||
file->flags |= GIT_DIFF_FILE_BINARY;
|
||||
|
||||
map->data = git__malloc((size_t)file->size + 1);
|
||||
if (map->data == NULL)
|
||||
error = GIT_ENOMEM;
|
||||
else {
|
||||
ssize_t read_len =
|
||||
p_readlink(full_path.ptr, map->data, (size_t)file->size + 1);
|
||||
if (read_len != (ssize_t)file->size)
|
||||
error = git__throw(
|
||||
GIT_EOSERR, "Failed to read symlink %s", file->path);
|
||||
else
|
||||
map->len = read_len;
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
error = git_futils_mmap_ro_file(map, full_path.ptr);
|
||||
file->flags |= GIT_DIFF_FILE_UNMAP_DATA;
|
||||
}
|
||||
git_buf_free(&full_path);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
|
||||
{
|
||||
if (blob != NULL)
|
||||
git_blob_free(blob);
|
||||
|
||||
if (file->flags & GIT_DIFF_FILE_FREE_DATA) {
|
||||
git__free(map->data);
|
||||
map->data = NULL;
|
||||
file->flags &= ~GIT_DIFF_FILE_FREE_DATA;
|
||||
}
|
||||
else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) {
|
||||
git_futils_mmap_free(map);
|
||||
map->data = NULL;
|
||||
file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
int git_diff_foreach(
|
||||
git_diff_list *diff,
|
||||
void *data,
|
||||
git_diff_file_fn file_cb,
|
||||
git_diff_hunk_fn hunk_cb,
|
||||
git_diff_line_fn line_cb)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
diff_output_info info;
|
||||
git_diff_delta *delta;
|
||||
xpparam_t xdiff_params;
|
||||
xdemitconf_t xdiff_config;
|
||||
xdemitcb_t xdiff_callback;
|
||||
|
||||
info.diff = diff;
|
||||
info.cb_data = data;
|
||||
info.hunk_cb = hunk_cb;
|
||||
info.line_cb = line_cb;
|
||||
|
||||
setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params);
|
||||
memset(&xdiff_callback, 0, sizeof(xdiff_callback));
|
||||
xdiff_callback.outf = diff_output_cb;
|
||||
xdiff_callback.priv = &info;
|
||||
|
||||
git_vector_foreach(&diff->deltas, info.index, delta) {
|
||||
git_blob *old_blob = NULL, *new_blob = NULL;
|
||||
git_map old_data, new_data;
|
||||
|
||||
if (delta->status == GIT_STATUS_UNMODIFIED)
|
||||
continue;
|
||||
|
||||
if (delta->status == GIT_STATUS_IGNORED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
|
||||
continue;
|
||||
|
||||
if (delta->status == GIT_STATUS_UNTRACKED &&
|
||||
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
|
||||
continue;
|
||||
|
||||
error = file_is_binary_by_attr(diff, delta);
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
old_data.data = "";
|
||||
old_data.len = 0;
|
||||
new_data.data = "";
|
||||
new_data.len = 0;
|
||||
|
||||
/* TODO: Partial blob reading to defer loading whole blob.
|
||||
* I.e. I want a blob with just the first 4kb loaded, then
|
||||
* later on I will read the rest of the blob if needed.
|
||||
*/
|
||||
|
||||
/* map files */
|
||||
if (delta->binary != 1 &&
|
||||
(hunk_cb || line_cb) &&
|
||||
(delta->status == GIT_STATUS_DELETED ||
|
||||
delta->status == GIT_STATUS_MODIFIED))
|
||||
{
|
||||
if (diff->old_src == GIT_ITERATOR_WORKDIR)
|
||||
error = get_workdir_content(diff->repo, &delta->old, &old_data);
|
||||
else
|
||||
error = get_blob_content(
|
||||
diff->repo, &delta->old.oid, &old_data, &old_blob);
|
||||
if (error != GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (delta->binary != 1 &&
|
||||
(hunk_cb || line_cb || git_oid_iszero(&delta->new.oid)) &&
|
||||
(delta->status == GIT_STATUS_ADDED ||
|
||||
delta->status == GIT_STATUS_MODIFIED))
|
||||
{
|
||||
if (diff->new_src == GIT_ITERATOR_WORKDIR)
|
||||
error = get_workdir_content(diff->repo, &delta->new, &new_data);
|
||||
else
|
||||
error = get_blob_content(
|
||||
diff->repo, &delta->new.oid, &new_data, &new_blob);
|
||||
if (error != GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
if ((delta->new.flags | GIT_DIFF_FILE_VALID_OID) == 0) {
|
||||
error = git_odb_hash(
|
||||
&delta->new.oid, new_data.data, new_data.len, GIT_OBJ_BLOB);
|
||||
if (error != GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
/* since we did not have the definitive oid, we may have
|
||||
* incorrect status and need to skip this item.
|
||||
*/
|
||||
if (git_oid_cmp(&delta->old.oid, &delta->new.oid) == 0) {
|
||||
delta->status = GIT_STATUS_UNMODIFIED;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have not already decided whether file is binary,
|
||||
* check the first 4K for nul bytes to decide...
|
||||
*/
|
||||
if (delta->binary == -1) {
|
||||
error = file_is_binary_by_content(
|
||||
diff, delta, &old_data, &new_data);
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* TODO: if ignore_whitespace is set, then we *must* do text
|
||||
* diffs to tell if a file has really been changed.
|
||||
*/
|
||||
|
||||
if (file_cb != NULL) {
|
||||
error = file_cb(data, delta, (float)info.index / diff->deltas.length);
|
||||
if (error != GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* don't do hunk and line diffs if file is binary */
|
||||
if (delta->binary == 1)
|
||||
goto cleanup;
|
||||
|
||||
/* nothing to do if we did not get data */
|
||||
if (!old_data.len && !new_data.len)
|
||||
goto cleanup;
|
||||
|
||||
assert(hunk_cb || line_cb);
|
||||
|
||||
info.delta = delta;
|
||||
|
||||
xdl_diff((mmfile_t *)&old_data, (mmfile_t *)&new_data,
|
||||
&xdiff_params, &xdiff_config, &xdiff_callback);
|
||||
|
||||
cleanup:
|
||||
release_content(&delta->old, &old_data, old_blob);
|
||||
release_content(&delta->new, &new_data, new_blob);
|
||||
|
||||
if (error != GIT_SUCCESS)
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
git_diff_list *diff;
|
||||
git_diff_output_fn print_cb;
|
||||
void *cb_data;
|
||||
git_buf *buf;
|
||||
} diff_print_info;
|
||||
|
||||
static char pick_suffix(int mode)
|
||||
{
|
||||
if (S_ISDIR(mode))
|
||||
return '/';
|
||||
else if (mode & 0100)
|
||||
/* in git, modes are very regular, so we must have 0100755 mode */
|
||||
return '*';
|
||||
else
|
||||
return ' ';
|
||||
}
|
||||
|
||||
static int print_compact(void *data, git_diff_delta *delta, float progress)
|
||||
{
|
||||
diff_print_info *pi = data;
|
||||
char code, old_suffix, new_suffix;
|
||||
|
||||
GIT_UNUSED_ARG(progress);
|
||||
|
||||
switch (delta->status) {
|
||||
case GIT_STATUS_ADDED: code = 'A'; break;
|
||||
case GIT_STATUS_DELETED: code = 'D'; break;
|
||||
case GIT_STATUS_MODIFIED: code = 'M'; break;
|
||||
case GIT_STATUS_RENAMED: code = 'R'; break;
|
||||
case GIT_STATUS_COPIED: code = 'C'; break;
|
||||
case GIT_STATUS_IGNORED: code = 'I'; break;
|
||||
case GIT_STATUS_UNTRACKED: code = '?'; break;
|
||||
default: code = 0;
|
||||
}
|
||||
|
||||
if (!code)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
old_suffix = pick_suffix(delta->old.mode);
|
||||
new_suffix = pick_suffix(delta->new.mode);
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
|
||||
if (delta->old.path != delta->new.path &&
|
||||
strcmp(delta->old.path,delta->new.path) != 0)
|
||||
git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
|
||||
delta->old.path, old_suffix, delta->new.path, new_suffix);
|
||||
else if (delta->old.mode != delta->new.mode &&
|
||||
delta->old.mode != 0 && delta->new.mode != 0)
|
||||
git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,
|
||||
delta->old.path, new_suffix, delta->old.mode, delta->new.mode);
|
||||
else if (old_suffix != ' ')
|
||||
git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old.path, old_suffix);
|
||||
else
|
||||
git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old.path);
|
||||
|
||||
if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
|
||||
return git_buf_lasterror(pi->buf);
|
||||
|
||||
return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr);
|
||||
}
|
||||
|
||||
int git_diff_print_compact(
|
||||
git_diff_list *diff,
|
||||
void *cb_data,
|
||||
git_diff_output_fn print_cb)
|
||||
{
|
||||
int error;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
diff_print_info pi;
|
||||
|
||||
pi.diff = diff;
|
||||
pi.print_cb = print_cb;
|
||||
pi.cb_data = cb_data;
|
||||
pi.buf = &buf;
|
||||
|
||||
error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
|
||||
{
|
||||
char start_oid[8], end_oid[8];
|
||||
|
||||
/* TODO: Determine a good actual OID range to print */
|
||||
git_oid_to_string(start_oid, sizeof(start_oid), &delta->old.oid);
|
||||
git_oid_to_string(end_oid, sizeof(end_oid), &delta->new.oid);
|
||||
|
||||
/* TODO: Match git diff more closely */
|
||||
if (delta->old.mode == delta->new.mode) {
|
||||
git_buf_printf(pi->buf, "index %s..%s %o\n",
|
||||
start_oid, end_oid, delta->old.mode);
|
||||
} else {
|
||||
if (delta->old.mode == 0) {
|
||||
git_buf_printf(pi->buf, "new file mode %o\n", delta->new.mode);
|
||||
} else if (delta->new.mode == 0) {
|
||||
git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old.mode);
|
||||
} else {
|
||||
git_buf_printf(pi->buf, "old mode %o\n", delta->old.mode);
|
||||
git_buf_printf(pi->buf, "new mode %o\n", delta->new.mode);
|
||||
}
|
||||
git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);
|
||||
}
|
||||
|
||||
return git_buf_lasterror(pi->buf);
|
||||
}
|
||||
|
||||
static int print_patch_file(void *data, git_diff_delta *delta, float progress)
|
||||
{
|
||||
int error;
|
||||
diff_print_info *pi = data;
|
||||
const char *oldpfx = pi->diff->opts.src_prefix;
|
||||
const char *oldpath = delta->old.path;
|
||||
const char *newpfx = pi->diff->opts.dst_prefix;
|
||||
const char *newpath = delta->new.path;
|
||||
|
||||
GIT_UNUSED_ARG(progress);
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old.path, newpfx, delta->new.path);
|
||||
if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if (git_oid_iszero(&delta->old.oid)) {
|
||||
oldpfx = "";
|
||||
oldpath = "/dev/null";
|
||||
}
|
||||
if (git_oid_iszero(&delta->new.oid)) {
|
||||
oldpfx = "";
|
||||
oldpath = "/dev/null";
|
||||
}
|
||||
|
||||
if (delta->binary != 1) {
|
||||
git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);
|
||||
git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);
|
||||
}
|
||||
|
||||
if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
|
||||
return git_buf_lasterror(pi->buf);
|
||||
|
||||
error = pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr);
|
||||
if (error != GIT_SUCCESS || delta->binary != 1)
|
||||
return error;
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
git_buf_printf(
|
||||
pi->buf, "Binary files %s%s and %s%s differ\n",
|
||||
oldpfx, oldpath, newpfx, newpath);
|
||||
if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
|
||||
return git_buf_lasterror(pi->buf);
|
||||
|
||||
return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_BINARY, pi->buf->ptr);
|
||||
}
|
||||
|
||||
static int print_patch_hunk(
|
||||
void *data,
|
||||
git_diff_delta *d,
|
||||
git_diff_range *r,
|
||||
const char *header,
|
||||
size_t header_len)
|
||||
{
|
||||
diff_print_info *pi = data;
|
||||
|
||||
GIT_UNUSED_ARG(d);
|
||||
GIT_UNUSED_ARG(r);
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
|
||||
if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) == GIT_SUCCESS)
|
||||
return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_HUNK_HDR, pi->buf->ptr);
|
||||
else
|
||||
return git_buf_lasterror(pi->buf);
|
||||
}
|
||||
|
||||
static int print_patch_line(
|
||||
void *data,
|
||||
git_diff_delta *delta,
|
||||
char line_origin, /* GIT_DIFF_LINE value from above */
|
||||
const char *content,
|
||||
size_t content_len)
|
||||
{
|
||||
diff_print_info *pi = data;
|
||||
|
||||
GIT_UNUSED_ARG(delta);
|
||||
|
||||
git_buf_clear(pi->buf);
|
||||
|
||||
if (line_origin == GIT_DIFF_LINE_ADDITION ||
|
||||
line_origin == GIT_DIFF_LINE_DELETION ||
|
||||
line_origin == GIT_DIFF_LINE_CONTEXT)
|
||||
git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);
|
||||
else if (content_len > 0)
|
||||
git_buf_printf(pi->buf, "%.*s", (int)content_len, content);
|
||||
|
||||
if (git_buf_lasterror(pi->buf) != GIT_SUCCESS)
|
||||
return git_buf_lasterror(pi->buf);
|
||||
|
||||
return pi->print_cb(pi->cb_data, line_origin, pi->buf->ptr);
|
||||
}
|
||||
|
||||
int git_diff_print_patch(
|
||||
git_diff_list *diff,
|
||||
void *cb_data,
|
||||
git_diff_output_fn print_cb)
|
||||
{
|
||||
int error;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
diff_print_info pi;
|
||||
|
||||
pi.diff = diff;
|
||||
pi.print_cb = print_cb;
|
||||
pi.cb_data = cb_data;
|
||||
pi.buf = &buf;
|
||||
|
||||
error = git_diff_foreach(
|
||||
diff, &pi, print_patch_file, print_patch_hunk, print_patch_line);
|
||||
|
||||
git_buf_free(&buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
int git_diff_blobs(
|
||||
git_repository *repo,
|
||||
git_blob *old_blob,
|
||||
git_blob *new_blob,
|
||||
git_diff_options *options,
|
||||
void *cb_data,
|
||||
git_diff_hunk_fn hunk_cb,
|
||||
git_diff_line_fn line_cb)
|
||||
{
|
||||
diff_output_info info;
|
||||
git_diff_delta delta;
|
||||
mmfile_t old, new;
|
||||
xpparam_t xdiff_params;
|
||||
xdemitconf_t xdiff_config;
|
||||
xdemitcb_t xdiff_callback;
|
||||
|
||||
assert(repo);
|
||||
|
||||
if (options && (options->flags & GIT_DIFF_REVERSE)) {
|
||||
git_blob *swap = old_blob;
|
||||
old_blob = new_blob;
|
||||
new_blob = swap;
|
||||
}
|
||||
|
||||
if (old_blob) {
|
||||
old.ptr = (char *)git_blob_rawcontent(old_blob);
|
||||
old.size = git_blob_rawsize(old_blob);
|
||||
} else {
|
||||
old.ptr = "";
|
||||
old.size = 0;
|
||||
}
|
||||
|
||||
if (new_blob) {
|
||||
new.ptr = (char *)git_blob_rawcontent(new_blob);
|
||||
new.size = git_blob_rawsize(new_blob);
|
||||
} else {
|
||||
new.ptr = "";
|
||||
new.size = 0;
|
||||
}
|
||||
|
||||
/* populate a "fake" delta record */
|
||||
delta.status = old.ptr ?
|
||||
(new.ptr ? GIT_STATUS_MODIFIED : GIT_STATUS_DELETED) :
|
||||
(new.ptr ? GIT_STATUS_ADDED : GIT_STATUS_UNTRACKED);
|
||||
delta.old.mode = 0100644; /* can't know the truth from a blob alone */
|
||||
delta.new.mode = 0100644;
|
||||
git_oid_cpy(&delta.old.oid, git_object_id((const git_object *)old_blob));
|
||||
git_oid_cpy(&delta.new.oid, git_object_id((const git_object *)new_blob));
|
||||
delta.old.path = NULL;
|
||||
delta.new.path = NULL;
|
||||
delta.similarity = 0;
|
||||
|
||||
info.diff = NULL;
|
||||
info.delta = δ
|
||||
info.cb_data = cb_data;
|
||||
info.hunk_cb = hunk_cb;
|
||||
info.line_cb = line_cb;
|
||||
|
||||
setup_xdiff_options(options, &xdiff_config, &xdiff_params);
|
||||
memset(&xdiff_callback, 0, sizeof(xdiff_callback));
|
||||
xdiff_callback.outf = diff_output_cb;
|
||||
xdiff_callback.priv = &info;
|
||||
|
||||
xdl_diff(&old, &new, &xdiff_params, &xdiff_config, &xdiff_callback);
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
@ -79,10 +79,6 @@ git_off_t git_futils_filesize(git_file fd)
|
||||
return sb.st_size;
|
||||
}
|
||||
|
||||
#define GIT_MODE_PERMS_MASK 0777
|
||||
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
|
||||
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
|
||||
|
||||
mode_t git_futils_canonical_mode(mode_t raw_mode)
|
||||
{
|
||||
if (S_ISREG(raw_mode))
|
||||
@ -181,6 +177,15 @@ int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
|
||||
return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
|
||||
}
|
||||
|
||||
int git_futils_mmap_ro_file(git_map *out, const char *path)
|
||||
{
|
||||
git_file fd = p_open(path, O_RDONLY /* | O_NOATIME */);
|
||||
size_t len = git_futils_filesize(fd);
|
||||
int result = git_futils_mmap_ro(out, fd, 0, len);
|
||||
p_close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void git_futils_mmap_free(git_map *out)
|
||||
{
|
||||
p_munmap(out);
|
||||
|
@ -81,6 +81,10 @@ extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t
|
||||
*/
|
||||
extern git_off_t git_futils_filesize(git_file fd);
|
||||
|
||||
#define GIT_MODE_PERMS_MASK 0777
|
||||
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
|
||||
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
|
||||
|
||||
/**
|
||||
* Convert a mode_t from the OS to a legal git mode_t value.
|
||||
*/
|
||||
@ -108,6 +112,19 @@ extern int git_futils_mmap_ro(
|
||||
git_off_t begin,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Read-only map an entire file.
|
||||
*
|
||||
* @param out buffer to populate with the mapping information.
|
||||
* @param path path to file to be opened.
|
||||
* @return
|
||||
* - GIT_SUCCESS on success;
|
||||
* - GIT_EOSERR on an unspecified OS related error.
|
||||
*/
|
||||
extern int git_futils_mmap_ro_file(
|
||||
git_map *out,
|
||||
const char *path);
|
||||
|
||||
/**
|
||||
* Release the memory associated with a previous memory mapping.
|
||||
* @param map the mapping description previously configured.
|
||||
|
@ -143,6 +143,16 @@ static void tree_iterator__free(git_iterator *self)
|
||||
git_buf_free(&ti->path);
|
||||
}
|
||||
|
||||
static int tree_iterator__reset(git_iterator *self)
|
||||
{
|
||||
tree_iterator *ti = (tree_iterator *)self;
|
||||
while (ti->stack && ti->stack->next)
|
||||
tree_iterator__pop_frame(ti);
|
||||
if (ti->stack)
|
||||
ti->stack->index = 0;
|
||||
return tree_iterator__expand_tree(ti);
|
||||
}
|
||||
|
||||
int git_iterator_for_tree(
|
||||
git_repository *repo, git_tree *tree, git_iterator **iter)
|
||||
{
|
||||
@ -155,6 +165,7 @@ int git_iterator_for_tree(
|
||||
ti->base.current = tree_iterator__current;
|
||||
ti->base.at_end = tree_iterator__at_end;
|
||||
ti->base.advance = tree_iterator__advance;
|
||||
ti->base.reset = tree_iterator__reset;
|
||||
ti->base.free = tree_iterator__free;
|
||||
ti->repo = repo;
|
||||
ti->stack = tree_iterator__alloc_frame(tree);
|
||||
@ -199,6 +210,13 @@ static int index_iterator__advance(
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int index_iterator__reset(git_iterator *self)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
ii->current = 0;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void index_iterator__free(git_iterator *self)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
@ -217,6 +235,7 @@ int git_iterator_for_index(git_repository *repo, git_iterator **iter)
|
||||
ii->base.current = index_iterator__current;
|
||||
ii->base.at_end = index_iterator__at_end;
|
||||
ii->base.advance = index_iterator__advance;
|
||||
ii->base.reset = index_iterator__reset;
|
||||
ii->base.free = index_iterator__free;
|
||||
ii->current = 0;
|
||||
|
||||
@ -251,7 +270,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
|
||||
workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
|
||||
if (wf == NULL)
|
||||
return wf;
|
||||
if (git_vector_init(&wf->entries, 0, git__strcmp_cb) != GIT_SUCCESS) {
|
||||
if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != GIT_SUCCESS) {
|
||||
git__free(wf);
|
||||
return NULL;
|
||||
}
|
||||
@ -261,7 +280,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
|
||||
static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
|
||||
{
|
||||
unsigned int i;
|
||||
char *path;
|
||||
git_path_with_stat *path;
|
||||
|
||||
git_vector_foreach(&wf->entries, i, path)
|
||||
git__free(path);
|
||||
@ -278,10 +297,7 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi)
|
||||
if (wf == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
/* allocate dir entries with extra byte (the "1" param) so we
|
||||
* can suffix directory names with a "/".
|
||||
*/
|
||||
error = git_path_dirload(wi->path.ptr, wi->root_len, 1, &wf->entries);
|
||||
error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries);
|
||||
if (error < GIT_SUCCESS || wf->entries.length == 0) {
|
||||
workdir_iterator__free_frame(wf);
|
||||
return GIT_ENOTFOUND;
|
||||
@ -319,7 +335,7 @@ static int workdir_iterator__advance(
|
||||
int error;
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
workdir_iterator_frame *wf;
|
||||
const char *next;
|
||||
git_path_with_stat *next;
|
||||
|
||||
if (entry != NULL)
|
||||
*entry = NULL;
|
||||
@ -330,7 +346,7 @@ static int workdir_iterator__advance(
|
||||
while ((wf = wi->stack) != NULL) {
|
||||
next = git_vector_get(&wf->entries, ++wf->index);
|
||||
if (next != NULL) {
|
||||
if (strcmp(next, DOT_GIT) == 0)
|
||||
if (strcmp(next->path, DOT_GIT "/") == 0)
|
||||
continue;
|
||||
/* else found a good entry */
|
||||
break;
|
||||
@ -355,6 +371,20 @@ static int workdir_iterator__advance(
|
||||
return error;
|
||||
}
|
||||
|
||||
static int workdir_iterator__reset(git_iterator *self)
|
||||
{
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
while (wi->stack != NULL && wi->stack->next != NULL) {
|
||||
workdir_iterator_frame *wf = wi->stack;
|
||||
wi->stack = wf->next;
|
||||
workdir_iterator__free_frame(wf);
|
||||
git_ignore__pop_dir(&wi->ignores);
|
||||
}
|
||||
if (wi->stack)
|
||||
wi->stack->index = 0;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void workdir_iterator__free(git_iterator *self)
|
||||
{
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
@ -372,39 +402,35 @@ static void workdir_iterator__free(git_iterator *self)
|
||||
static int workdir_iterator__update_entry(workdir_iterator *wi)
|
||||
{
|
||||
int error;
|
||||
struct stat st;
|
||||
char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index);
|
||||
git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index);
|
||||
|
||||
error = git_buf_joinpath(
|
||||
&wi->path, git_repository_workdir(wi->repo), relpath);
|
||||
git_buf_truncate(&wi->path, wi->root_len);
|
||||
error = git_buf_put(&wi->path, ps->path, ps->path_len);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
memset(&wi->entry, 0, sizeof(wi->entry));
|
||||
wi->entry.path = relpath;
|
||||
wi->entry.path = ps->path;
|
||||
|
||||
/* skip over .git directory */
|
||||
if (strcmp(relpath, DOT_GIT) == 0)
|
||||
if (strcmp(ps->path, DOT_GIT "/") == 0)
|
||||
return workdir_iterator__advance((git_iterator *)wi, NULL);
|
||||
|
||||
/* if there is an error processing the entry, treat as ignored */
|
||||
wi->is_ignored = 1;
|
||||
|
||||
if (p_lstat(wi->path.ptr, &st) < 0)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
/* TODO: remove shared code for struct stat conversion with index.c */
|
||||
wi->entry.ctime.seconds = (git_time_t)st.st_ctime;
|
||||
wi->entry.mtime.seconds = (git_time_t)st.st_mtime;
|
||||
wi->entry.dev = st.st_rdev;
|
||||
wi->entry.ino = st.st_ino;
|
||||
wi->entry.mode = git_futils_canonical_mode(st.st_mode);
|
||||
wi->entry.uid = st.st_uid;
|
||||
wi->entry.gid = st.st_gid;
|
||||
wi->entry.file_size = st.st_size;
|
||||
wi->entry.ctime.seconds = (git_time_t)ps->st.st_ctime;
|
||||
wi->entry.mtime.seconds = (git_time_t)ps->st.st_mtime;
|
||||
wi->entry.dev = ps->st.st_rdev;
|
||||
wi->entry.ino = ps->st.st_ino;
|
||||
wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
|
||||
wi->entry.uid = ps->st.st_uid;
|
||||
wi->entry.gid = ps->st.st_gid;
|
||||
wi->entry.file_size = ps->st.st_size;
|
||||
|
||||
/* if this is a file type we don't handle, treat as ignored */
|
||||
if (st.st_mode == 0)
|
||||
if (wi->entry.mode == 0)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
/* okay, we are far enough along to look up real ignore rule */
|
||||
@ -412,18 +438,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
|
||||
if (error != GIT_SUCCESS)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) {
|
||||
/* create submodule entry */
|
||||
wi->entry.mode = S_IFGITLINK;
|
||||
} else {
|
||||
/* create directory entry that can be advanced into as needed */
|
||||
size_t pathlen = strlen(wi->entry.path);
|
||||
wi->entry.path[pathlen] = '/';
|
||||
wi->entry.path[pathlen + 1] = '\0';
|
||||
wi->entry.mode = S_IFDIR;
|
||||
}
|
||||
}
|
||||
/* detect submodules */
|
||||
if (S_ISDIR(wi->entry.mode) &&
|
||||
git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS)
|
||||
wi->entry.mode = S_IFGITLINK;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
@ -439,10 +457,13 @@ int git_iterator_for_workdir(git_repository *repo, git_iterator **iter)
|
||||
wi->base.current = workdir_iterator__current;
|
||||
wi->base.at_end = workdir_iterator__at_end;
|
||||
wi->base.advance = workdir_iterator__advance;
|
||||
wi->base.reset = workdir_iterator__reset;
|
||||
wi->base.free = workdir_iterator__free;
|
||||
wi->repo = repo;
|
||||
|
||||
error = git_buf_sets(&wi->path, git_repository_workdir(repo));
|
||||
if (error == GIT_SUCCESS)
|
||||
error = git_path_to_dir(&wi->path);
|
||||
if (error == GIT_SUCCESS)
|
||||
error = git_ignore__for_path(repo, "", &wi->ignores);
|
||||
if (error != GIT_SUCCESS) {
|
||||
|
@ -23,6 +23,7 @@ struct git_iterator {
|
||||
int (*current)(git_iterator *, const git_index_entry **);
|
||||
int (*at_end)(git_iterator *);
|
||||
int (*advance)(git_iterator *, const git_index_entry **);
|
||||
int (*reset)(git_iterator *);
|
||||
void (*free)(git_iterator *);
|
||||
};
|
||||
|
||||
@ -60,6 +61,11 @@ GIT_INLINE(int) git_iterator_advance(
|
||||
return iter->advance(iter, entry);
|
||||
}
|
||||
|
||||
GIT_INLINE(int) git_iterator_reset(git_iterator *iter)
|
||||
{
|
||||
return iter->reset(iter);
|
||||
}
|
||||
|
||||
GIT_INLINE(void) git_iterator_free(git_iterator *iter)
|
||||
{
|
||||
iter->free(iter);
|
||||
|
10
src/oid.c
10
src/oid.c
@ -190,6 +190,16 @@ int git_oid_streq(const git_oid *a, const char *str)
|
||||
return git_oid_cmp(a, &id) == 0 ? GIT_SUCCESS : GIT_ERROR;
|
||||
}
|
||||
|
||||
int git_oid_iszero(const git_oid *oid_a)
|
||||
{
|
||||
const unsigned char *a = oid_a->id;
|
||||
unsigned int i;
|
||||
for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a)
|
||||
if (*a != 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef short node_index;
|
||||
|
||||
typedef union {
|
||||
|
43
src/path.c
43
src/path.c
@ -583,3 +583,46 @@ int git_path_dirload(
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
int git_path_with_stat_cmp(const void *a, const void *b)
|
||||
{
|
||||
const git_path_with_stat *psa = a, *psb = b;
|
||||
return git__strcmp_cb(psa->path, psb->path);
|
||||
}
|
||||
|
||||
int git_path_dirload_with_stat(
|
||||
const char *path,
|
||||
size_t prefix_len,
|
||||
git_vector *contents)
|
||||
{
|
||||
int error;
|
||||
unsigned int i;
|
||||
git_path_with_stat *ps;
|
||||
git_buf full = GIT_BUF_INIT;
|
||||
|
||||
if ((error = git_buf_set(&full, path, prefix_len)) != GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if ((error = git_path_dirload(path, prefix_len,
|
||||
sizeof(git_path_with_stat) + 1, contents)) != GIT_SUCCESS) {
|
||||
git_buf_free(&full);
|
||||
return error;
|
||||
}
|
||||
|
||||
git_vector_foreach(contents, i, ps) {
|
||||
size_t path_len = strlen((char *)ps);
|
||||
|
||||
memmove(ps->path, ps, path_len + 1);
|
||||
ps->path_len = path_len;
|
||||
|
||||
git_buf_joinpath(&full, full.ptr, ps->path);
|
||||
p_lstat(full.ptr, &ps->st);
|
||||
git_buf_truncate(&full, prefix_len);
|
||||
|
||||
if (S_ISDIR(ps->st.st_mode)) {
|
||||
ps->path[path_len] = '/';
|
||||
ps->path[path_len + 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
22
src/path.h
22
src/path.h
@ -246,4 +246,26 @@ extern int git_path_dirload(
|
||||
size_t alloc_extra,
|
||||
git_vector *contents);
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct stat st;
|
||||
size_t path_len;
|
||||
char path[GIT_FLEX_ARRAY];
|
||||
} git_path_with_stat;
|
||||
|
||||
extern int git_path_with_stat_cmp(const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Load all directory entries along with stat info into a vector.
|
||||
*
|
||||
* This is just like git_path_dirload except that each entry in the
|
||||
* vector is a git_path_with_stat structure that contains both the
|
||||
* path and the stat info, plus directories will have a / suffixed
|
||||
* to their path name.
|
||||
*/
|
||||
extern int git_path_dirload_with_stat(
|
||||
const char *path,
|
||||
size_t prefix_len,
|
||||
git_vector *contents);
|
||||
|
||||
#endif
|
||||
|
@ -66,4 +66,6 @@ extern int p_rename(const char *from, const char *to);
|
||||
# include "unix/posix.h"
|
||||
#endif
|
||||
|
||||
#define p_readdir_r(d,e,r) readdir_r(d,e,r)
|
||||
|
||||
#endif
|
||||
|
@ -131,7 +131,7 @@ static int status_entry_update_ignore(struct status_entry *e, git_ignores *ignor
|
||||
if ((error = git_ignore__lookup(ignores, path, &ignored)) == GIT_SUCCESS &&
|
||||
ignored)
|
||||
e->status_flags =
|
||||
(e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED;
|
||||
(e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_WT_IGNORED;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@ -21,6 +21,5 @@
|
||||
#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
|
||||
#define p_mkstemp(p) mkstemp(p)
|
||||
#define p_setenv(n,v,o) setenv(n,v,o)
|
||||
#define p_readdir_r(d,e,r) readdir_r(d,e,r)
|
||||
|
||||
#endif
|
||||
|
10
src/vector.c
10
src/vector.c
@ -220,4 +220,14 @@ void git_vector_clear(git_vector *v)
|
||||
v->sorted = 1;
|
||||
}
|
||||
|
||||
void git_vector_swap(git_vector *a, git_vector *b)
|
||||
{
|
||||
git_vector t;
|
||||
|
||||
if (!a || !b || a == b)
|
||||
return;
|
||||
|
||||
memcpy(&t, a, sizeof(t));
|
||||
memcpy(a, b, sizeof(t));
|
||||
memcpy(b, &t, sizeof(t));
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ typedef struct git_vector {
|
||||
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
|
||||
void git_vector_free(git_vector *v);
|
||||
void git_vector_clear(git_vector *v);
|
||||
void git_vector_swap(git_vector *a, git_vector *b);
|
||||
|
||||
int git_vector_search(git_vector *v, const void *entry);
|
||||
int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);
|
||||
@ -38,6 +39,11 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
|
||||
return (position < v->length) ? v->contents[position] : NULL;
|
||||
}
|
||||
|
||||
GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position)
|
||||
{
|
||||
return (position < v->length) ? v->contents[position] : NULL;
|
||||
}
|
||||
|
||||
GIT_INLINE(void *) git_vector_last(git_vector *v)
|
||||
{
|
||||
return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
|
||||
|
@ -58,8 +58,11 @@ git__DIR *git__opendir(const char *dir)
|
||||
return new;
|
||||
}
|
||||
|
||||
int git__readdir_r(
|
||||
git__DIR *d, struct git__dirent *entry, struct git__dirent **result)
|
||||
int git__readdir_ext(
|
||||
git__DIR *d,
|
||||
struct git__dirent *entry,
|
||||
struct git__dirent **result,
|
||||
int *is_dir)
|
||||
{
|
||||
if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
@ -80,13 +83,16 @@ int git__readdir_r(
|
||||
entry->d_name, GIT_PATH_MAX, NULL, NULL);
|
||||
|
||||
*result = entry;
|
||||
if (is_dir != NULL)
|
||||
*is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct git__dirent *git__readdir(git__DIR *d)
|
||||
{
|
||||
struct git__dirent *result;
|
||||
if (git__readdir_r(d, &d->entry, &result) < 0)
|
||||
if (git__readdir_ext(d, &d->entry, &result, NULL) < 0)
|
||||
return NULL;
|
||||
return result;
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ typedef struct {
|
||||
|
||||
extern git__DIR *git__opendir(const char *);
|
||||
extern struct git__dirent *git__readdir(git__DIR *);
|
||||
extern int git__readdir_r(git__DIR*, struct git__dirent*, struct git__dirent**);
|
||||
extern int git__readdir_ext(
|
||||
git__DIR *, struct git__dirent *, struct git__dirent **, int *);
|
||||
extern void git__rewinddir(git__DIR *);
|
||||
extern int git__closedir(git__DIR *);
|
||||
|
||||
@ -33,10 +34,9 @@ extern int git__closedir(git__DIR *);
|
||||
# define DIR git__DIR
|
||||
# define opendir git__opendir
|
||||
# define readdir git__readdir
|
||||
# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL)
|
||||
# define rewinddir git__rewinddir
|
||||
# define closedir git__closedir
|
||||
# endif
|
||||
|
||||
#define p_readdir_r(d,e,r) git__readdir_r(d,e,r)
|
||||
|
||||
#endif /* INCLUDE_dir_h__ */
|
||||
|
@ -69,12 +69,12 @@ extern "C" {
|
||||
|
||||
typedef struct s_mmfile {
|
||||
char *ptr;
|
||||
long size;
|
||||
size_t size;
|
||||
} mmfile_t;
|
||||
|
||||
typedef struct s_mmbuffer {
|
||||
char *ptr;
|
||||
long size;
|
||||
size_t size;
|
||||
} mmbuffer_t;
|
||||
|
||||
typedef struct s_xpparam {
|
||||
|
@ -38,10 +38,13 @@ GIT_INLINE(void) cl_assert_strequal_internal(
|
||||
if (!match) {
|
||||
char buf[4096];
|
||||
snprintf(buf, 4096, "'%s' != '%s'", a, b);
|
||||
clar__assert(0, file, line, buf, err, 1);
|
||||
clar__assert(0, file, line, err, buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#define cl_assert_intequal(a,b) \
|
||||
do { if ((a) != (b)) { char buf[128]; snprintf(buf,128,"%d != %d",(a),(b)); clar__assert(0,__FILE__,__LINE__,#a " != " #b,buf,1); } } while (0)
|
||||
|
||||
/*
|
||||
* Some utility macros for building long strings
|
||||
*/
|
||||
|
@ -29,12 +29,14 @@ int diff_file_fn(
|
||||
diff_expects *e = cb_data;
|
||||
(void)progress;
|
||||
e->files++;
|
||||
if (delta->old_attr == 0)
|
||||
e->file_adds++;
|
||||
else if (delta->new_attr == 0)
|
||||
e->file_dels++;
|
||||
else
|
||||
e->file_mods++;
|
||||
switch (delta->status) {
|
||||
case GIT_STATUS_ADDED: e->file_adds++; break;
|
||||
case GIT_STATUS_DELETED: e->file_dels++; break;
|
||||
case GIT_STATUS_MODIFIED: e->file_mods++; break;
|
||||
case GIT_STATUS_IGNORED: e->file_ignored++; break;
|
||||
case GIT_STATUS_UNTRACKED: e->file_untracked++; break;
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ typedef struct {
|
||||
int file_adds;
|
||||
int file_dels;
|
||||
int file_mods;
|
||||
int file_ignored;
|
||||
int file_untracked;
|
||||
|
||||
int hunks;
|
||||
int hunk_new_lines;
|
||||
|
@ -338,7 +338,7 @@ static void workdir_iterator_test(
|
||||
|
||||
void test_diff_iterator__workdir_0(void)
|
||||
{
|
||||
workdir_iterator_test("attr", 24, 2, NULL, "ign");
|
||||
workdir_iterator_test("attr", 25, 2, NULL, "ign");
|
||||
}
|
||||
|
||||
static const char *status_paths[] = {
|
||||
@ -351,10 +351,10 @@ static const char *status_paths[] = {
|
||||
"staged_delete_modified_file",
|
||||
"staged_new_file",
|
||||
"staged_new_file_modified_file",
|
||||
"subdir.txt",
|
||||
"subdir/current_file",
|
||||
"subdir/modified_file",
|
||||
"subdir/new_file",
|
||||
"subdir.txt",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -100,7 +100,7 @@ void test_diff_tree__options(void)
|
||||
|
||||
git_diff_options opts = {0};
|
||||
git_diff_list *diff = NULL;
|
||||
diff_expects exp;
|
||||
diff_expects actual;
|
||||
int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 };
|
||||
git_diff_options test_options[] = {
|
||||
/* a vs b tests */
|
||||
@ -123,25 +123,26 @@ void test_diff_tree__options(void)
|
||||
*/
|
||||
diff_expects test_expects[] = {
|
||||
/* a vs b tests */
|
||||
{ 5, 3, 0, 2, 4, 0, 0, 51, 2, 46, 3 },
|
||||
{ 5, 3, 0, 2, 4, 0, 0, 53, 4, 46, 3 },
|
||||
{ 5, 0, 3, 2, 4, 0, 0, 52, 3, 3, 46 },
|
||||
{ 5, 3, 0, 2, 5, 0, 0, 54, 3, 48, 3 },
|
||||
{ 5, 3, 0, 2, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
|
||||
{ 5, 3, 0, 2, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
|
||||
{ 5, 0, 3, 2, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
|
||||
{ 5, 3, 0, 2, 0, 0, 5, 0, 0, 54, 3, 48, 3 },
|
||||
/* c vs d tests */
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 22, 9, 10, 3 },
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 19, 12, 7, 0 },
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 20, 11, 8, 1 },
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 20, 11, 8, 1 },
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 18, 11, 0, 7 },
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 0, 0, 20, 11, 8, 1 },
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 0, 0, 20, 11, 8, 1 },
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 0, 0, 18, 11, 0, 7 },
|
||||
{ 0 },
|
||||
};
|
||||
diff_expects *expected;
|
||||
int i;
|
||||
|
||||
cl_assert(a);
|
||||
cl_assert(b);
|
||||
|
||||
for (i = 0; test_expects[i].files > 0; i++) {
|
||||
memset(&exp, 0, sizeof(exp)); /* clear accumulator */
|
||||
memset(&actual, 0, sizeof(actual)); /* clear accumulator */
|
||||
opts = test_options[i];
|
||||
|
||||
if (test_ab_or_cd[i] == 0)
|
||||
@ -150,17 +151,18 @@ void test_diff_tree__options(void)
|
||||
cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff));
|
||||
|
||||
cl_git_pass(git_diff_foreach(
|
||||
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
|
||||
diff, &actual, diff_file_fn, diff_hunk_fn, diff_line_fn));
|
||||
|
||||
cl_assert(exp.files == test_expects[i].files);
|
||||
cl_assert(exp.file_adds == test_expects[i].file_adds);
|
||||
cl_assert(exp.file_dels == test_expects[i].file_dels);
|
||||
cl_assert(exp.file_mods == test_expects[i].file_mods);
|
||||
cl_assert(exp.hunks == test_expects[i].hunks);
|
||||
cl_assert(exp.lines == test_expects[i].lines);
|
||||
cl_assert(exp.line_ctxt == test_expects[i].line_ctxt);
|
||||
cl_assert(exp.line_adds == test_expects[i].line_adds);
|
||||
cl_assert(exp.line_dels == test_expects[i].line_dels);
|
||||
expected = &test_expects[i];
|
||||
cl_assert_intequal(actual.files, expected->files);
|
||||
cl_assert_intequal(actual.file_adds, expected->file_adds);
|
||||
cl_assert_intequal(actual.file_dels, expected->file_dels);
|
||||
cl_assert_intequal(actual.file_mods, expected->file_mods);
|
||||
cl_assert_intequal(actual.hunks, expected->hunks);
|
||||
cl_assert_intequal(actual.lines, expected->lines);
|
||||
cl_assert_intequal(actual.line_ctxt, expected->line_ctxt);
|
||||
cl_assert_intequal(actual.line_adds, expected->line_adds);
|
||||
cl_assert_intequal(actual.line_dels, expected->line_dels);
|
||||
|
||||
git_diff_list_free(diff);
|
||||
diff = NULL;
|
||||
|
230
tests-clar/diff/workdir.c
Normal file
230
tests-clar/diff/workdir.c
Normal file
@ -0,0 +1,230 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "diff_helpers.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
|
||||
void test_diff_workdir__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_workdir__cleanup(void)
|
||||
{
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
cl_fixture_cleanup("status");
|
||||
}
|
||||
|
||||
void test_diff_workdir__to_index(void)
|
||||
{
|
||||
git_diff_options opts = {0};
|
||||
git_diff_list *diff = NULL;
|
||||
diff_expects exp;
|
||||
|
||||
opts.context_lines = 3;
|
||||
opts.interhunk_lines = 1;
|
||||
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||
|
||||
memset(&exp, 0, sizeof(exp));
|
||||
|
||||
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &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
|
||||
* - git diff
|
||||
* - mv .git .gitted
|
||||
*/
|
||||
cl_assert_intequal(12, exp.files);
|
||||
cl_assert_intequal(0, exp.file_adds);
|
||||
cl_assert_intequal(4, exp.file_dels);
|
||||
cl_assert_intequal(4, exp.file_mods);
|
||||
cl_assert_intequal(1, exp.file_ignored);
|
||||
cl_assert_intequal(3, exp.file_untracked);
|
||||
|
||||
cl_assert_intequal(8, exp.hunks);
|
||||
|
||||
cl_assert_intequal(14, exp.lines);
|
||||
cl_assert_intequal(5, exp.line_ctxt);
|
||||
cl_assert_intequal(4, exp.line_adds);
|
||||
cl_assert_intequal(5, exp.line_dels);
|
||||
|
||||
git_diff_list_free(diff);
|
||||
}
|
||||
|
||||
void test_diff_workdir__to_tree(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;
|
||||
git_diff_list *diff2 = NULL;
|
||||
diff_expects exp;
|
||||
|
||||
opts.context_lines = 3;
|
||||
opts.interhunk_lines = 1;
|
||||
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||
|
||||
memset(&exp, 0, sizeof(exp));
|
||||
|
||||
/* You can't really generate the equivalent of git_diff_workdir_to_tree()
|
||||
* using C git. It really wants to interpose the index into the diff.
|
||||
*
|
||||
* To validate the following results with command line git, I ran the
|
||||
* following:
|
||||
* - git ls-tree 26a125
|
||||
* - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
|
||||
* The results are documented at the bottom of this file in the
|
||||
* long comment entitled "PREPARATION OF TEST DATA".
|
||||
*/
|
||||
cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff));
|
||||
|
||||
cl_git_pass(git_diff_foreach(
|
||||
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
|
||||
|
||||
cl_assert(exp.files == 13);
|
||||
cl_assert(exp.file_adds == 0);
|
||||
cl_assert(exp.file_dels == 4);
|
||||
cl_assert(exp.file_mods == 4);
|
||||
cl_assert(exp.file_ignored == 1);
|
||||
cl_assert(exp.file_untracked == 4);
|
||||
|
||||
/* Since there is no git diff equivalent, let's just assume that the
|
||||
* text diffs produced by git_diff_foreach are accurate here. We will
|
||||
* do more apples-to-apples test comparison below.
|
||||
*/
|
||||
|
||||
git_diff_list_free(diff);
|
||||
diff = NULL;
|
||||
memset(&exp, 0, sizeof(exp));
|
||||
|
||||
/* This is a compatible emulation of "git diff <sha>" which looks like
|
||||
* a workdir to tree diff (even though it is not really). This is what
|
||||
* you would get from "git diff --name-status 26a125ee1bf"
|
||||
*/
|
||||
cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff));
|
||||
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2));
|
||||
cl_git_pass(git_diff_merge(diff, diff2));
|
||||
git_diff_list_free(diff2);
|
||||
|
||||
cl_git_pass(git_diff_foreach(
|
||||
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
|
||||
|
||||
cl_assert(exp.files == 14);
|
||||
cl_assert(exp.file_adds == 2);
|
||||
cl_assert(exp.file_dels == 5);
|
||||
cl_assert(exp.file_mods == 4);
|
||||
cl_assert(exp.file_ignored == 1);
|
||||
cl_assert(exp.file_untracked == 2);
|
||||
|
||||
cl_assert(exp.hunks == 11);
|
||||
|
||||
cl_assert(exp.lines == 17);
|
||||
cl_assert(exp.line_ctxt == 4);
|
||||
cl_assert(exp.line_adds == 8);
|
||||
cl_assert(exp.line_dels == 5);
|
||||
|
||||
git_diff_list_free(diff);
|
||||
diff = NULL;
|
||||
memset(&exp, 0, sizeof(exp));
|
||||
|
||||
/* Again, emulating "git diff <sha>" for testing purposes using
|
||||
* "git diff --name-status 0017bd4ab1ec3" instead.
|
||||
*/
|
||||
cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff));
|
||||
cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2));
|
||||
cl_git_pass(git_diff_merge(diff, diff2));
|
||||
git_diff_list_free(diff2);
|
||||
|
||||
cl_git_pass(git_diff_foreach(
|
||||
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
|
||||
|
||||
cl_assert(exp.files == 15);
|
||||
cl_assert(exp.file_adds == 5);
|
||||
cl_assert(exp.file_dels == 4);
|
||||
cl_assert(exp.file_mods == 3);
|
||||
cl_assert(exp.file_ignored == 1);
|
||||
cl_assert(exp.file_untracked == 2);
|
||||
|
||||
cl_assert(exp.hunks == 12);
|
||||
|
||||
cl_assert(exp.lines == 19);
|
||||
cl_assert(exp.line_ctxt == 3);
|
||||
cl_assert(exp.line_adds == 12);
|
||||
cl_assert(exp.line_dels == 4);
|
||||
|
||||
git_tree_free(a);
|
||||
git_tree_free(b);
|
||||
}
|
||||
|
||||
/* PREPARATION OF TEST DATA
|
||||
*
|
||||
* Since there is no command line equivalent of git_diff_workdir_to_tree,
|
||||
* it was a bit of a pain to confirm that I was getting the expected
|
||||
* results in the first part of this tests. Here is what I ended up
|
||||
* doing to set my expectation for the file counts and results:
|
||||
*
|
||||
* Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows:
|
||||
*
|
||||
* A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
|
||||
* B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted
|
||||
* C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file
|
||||
* D 32504b727382542f9f089e24fddac5e78533e96c staged_changes
|
||||
* E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted
|
||||
* F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file
|
||||
* G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted
|
||||
* H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
|
||||
* I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
|
||||
* J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file
|
||||
* K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file
|
||||
* L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
|
||||
*
|
||||
* --------
|
||||
*
|
||||
* find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
|
||||
*
|
||||
* A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
|
||||
* M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file
|
||||
* C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file
|
||||
* N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file
|
||||
* D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes
|
||||
* F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file
|
||||
* H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
|
||||
* O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file
|
||||
* P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file
|
||||
* I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
|
||||
* K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file
|
||||
* Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file
|
||||
* L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
|
||||
*
|
||||
* --------
|
||||
*
|
||||
* A - current_file (UNMODIFIED) -> not in results
|
||||
* B D file_deleted
|
||||
* M I ignored_file (IGNORED)
|
||||
* C M modified_file
|
||||
* N U new_file (UNTRACKED)
|
||||
* D M staged_changes
|
||||
* E D staged_changes_file_deleted
|
||||
* F M staged_changes_modified_file
|
||||
* G D staged_delete_file_deleted
|
||||
* H - staged_delete_modified_file (UNMODIFIED) -> not in results
|
||||
* O U staged_new_file
|
||||
* P U staged_new_file_modified_file
|
||||
* I - subdir/current_file (UNMODIFIED) -> not in results
|
||||
* J D subdir/deleted_file
|
||||
* K M subdir/modified_file
|
||||
* Q U subdir/new_file
|
||||
* L - subdir.txt (UNMODIFIED) -> not in results
|
||||
*
|
||||
* Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR
|
||||
*/
|
@ -29,7 +29,7 @@ static const char *entry_paths0[] = {
|
||||
|
||||
static const unsigned int entry_statuses0[] = {
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_IGNORED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_INDEX_MODIFIED,
|
||||
|
@ -143,7 +143,7 @@ void test_status_worktree__ignores(void)
|
||||
|
||||
for (i = 0; i < (int)entry_count0; i++) {
|
||||
cl_git_pass(git_status_should_ignore(_repository, entry_paths0[i], &ignored));
|
||||
cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
|
||||
cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_WT_IGNORED));
|
||||
}
|
||||
|
||||
cl_git_pass(git_status_should_ignore(_repository, "nonexistent_file", &ignored));
|
||||
|
@ -83,7 +83,7 @@ static const char *entry_paths0[] = {
|
||||
|
||||
static const unsigned int entry_statuses0[] = {
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_IGNORED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_INDEX_MODIFIED,
|
||||
@ -205,7 +205,7 @@ static const char *entry_paths2[] = {
|
||||
static const unsigned int entry_statuses2[] = {
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_IGNORED,
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
|
||||
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
|
||||
@ -291,7 +291,7 @@ static const unsigned int entry_statuses3[] = {
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_WT_DELETED,
|
||||
GIT_STATUS_IGNORED,
|
||||
GIT_STATUS_WT_IGNORED,
|
||||
GIT_STATUS_WT_MODIFIED,
|
||||
GIT_STATUS_WT_NEW,
|
||||
GIT_STATUS_INDEX_MODIFIED,
|
||||
|
Loading…
Reference in New Issue
Block a user