Commit b55fd885 by Vicent Martí

Merge pull request #941 from arrbee/diff-separate-iterators

Create a diff patch object as a replacement for iterators
parents 5942bd18 bae957b9
...@@ -122,7 +122,7 @@ typedef enum { ...@@ -122,7 +122,7 @@ typedef enum {
*/ */
typedef struct { typedef struct {
git_oid oid; git_oid oid;
char *path; const char *path;
git_off_t size; git_off_t size;
unsigned int flags; unsigned int flags;
uint16_t mode; uint16_t mode;
...@@ -154,7 +154,7 @@ typedef struct { ...@@ -154,7 +154,7 @@ typedef struct {
*/ */
typedef int (*git_diff_file_fn)( typedef int (*git_diff_file_fn)(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
float progress); float progress);
/** /**
...@@ -172,8 +172,8 @@ typedef struct { ...@@ -172,8 +172,8 @@ typedef struct {
*/ */
typedef int (*git_diff_hunk_fn)( typedef int (*git_diff_hunk_fn)(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
const char *header, const char *header,
size_t header_len); size_t header_len);
...@@ -213,16 +213,20 @@ enum { ...@@ -213,16 +213,20 @@ enum {
*/ */
typedef int (*git_diff_data_fn)( typedef int (*git_diff_data_fn)(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
char line_origin, /**< GIT_DIFF_LINE_... value from above */ char line_origin, /**< GIT_DIFF_LINE_... value from above */
const char *content, const char *content,
size_t content_len); size_t content_len);
/** /**
* The diff iterator object is used to scan a diff list. * The diff patch is used to store all the text diffs for a delta.
*
* You can easily loop over the content of patches and get information about
* them.
*/ */
typedef struct git_diff_iterator git_diff_iterator; typedef struct git_diff_patch git_diff_patch;
/** @name Diff List Generator Functions /** @name Diff List Generator Functions
* *
...@@ -349,7 +353,7 @@ GIT_EXTERN(int) git_diff_merge( ...@@ -349,7 +353,7 @@ GIT_EXTERN(int) git_diff_merge(
/**@{*/ /**@{*/
/** /**
* Iterate over a diff list issuing callbacks. * Loop over all deltas in a diff list issuing callbacks.
* *
* This will iterate through all of the files described in a diff. You * This will iterate through all of the files described in a diff. You
* should provide a file callback to learn about each file. * should provide a file callback to learn about each file.
...@@ -381,188 +385,191 @@ GIT_EXTERN(int) git_diff_foreach( ...@@ -381,188 +385,191 @@ GIT_EXTERN(int) git_diff_foreach(
git_diff_data_fn line_cb); git_diff_data_fn line_cb);
/** /**
* Create a diff iterator object that can be used to traverse a diff. * Iterate over a diff generating text output like "git diff --name-status".
* *
* This iterator can be used instead of `git_diff_foreach` in situations * Returning a non-zero value from the callbacks will terminate the
* where callback functions are awkward to use. Because of the way that * iteration and cause this return `GIT_EUSER`.
* diffs are calculated internally, using an iterator will use somewhat
* more memory than `git_diff_foreach` would.
* *
* @param iterator Output parameter of newly created iterator. * @param diff A git_diff_list generated by one of the above functions.
* @param diff Diff over which you wish to iterate. * @param cb_data Reference pointer that will be passed to your callback.
* @return 0 on success, < 0 on error * @param print_cb Callback to make per line of diff text.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/ */
GIT_EXTERN(int) git_diff_iterator_new( GIT_EXTERN(int) git_diff_print_compact(
git_diff_iterator **iterator, git_diff_list *diff,
git_diff_list *diff); void *cb_data,
git_diff_data_fn print_cb);
/** /**
* Release the iterator object. * Look up the single character abbreviation for a delta status code.
* *
* Call this when you are done using the iterator. * When you call `git_diff_print_compact` it prints single letter codes into
* the output such as 'A' for added, 'D' for deleted, 'M' for modified, etc.
* It is sometimes convenient to convert a git_delta_t value into these
* letters for your own purposes. This function does just that. By the
* way, unmodified will return a space (i.e. ' ').
* *
* @param iterator The diff iterator to be freed. * @param delta_t The git_delta_t value to look up
* @return The single character label for that code
*/ */
GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iterator); GIT_EXTERN(char) git_diff_status_char(git_delta_t status);
/** /**
* Return progress value for traversing the diff. * Iterate over a diff generating text output like "git diff".
*
* This returns a value between 0.0 and 1.0 that represents the progress
* through the diff iterator. The value is monotonically increasing and
* will advance gradually as you progress through the iteration.
* *
* @param iterator The diff iterator * This is a super easy way to generate a patch from a diff.
* @return Value between 0.0 and 1.0
*/
GIT_EXTERN(float) git_diff_iterator_progress(git_diff_iterator *iterator);
/**
* Return the number of hunks in the current file
* *
* This will return the number of diff hunks in the current file. If the * Returning a non-zero value from the callbacks will terminate the
* diff has not been performed yet, this may result in loading the file and * iteration and cause this return `GIT_EUSER`.
* performing the diff.
* *
* @param iterator The iterator object * @param diff A git_diff_list generated by one of the above functions.
* @return The number of hunks in the current file or <0 on loading failure * @param cb_data Reference pointer that will be passed to your callbacks.
* @param print_cb Callback function to output lines of the diff. This
* same function will be called for file headers, hunk
* headers, and diff lines. Fortunately, you can probably
* use various GIT_DIFF_LINE constants to determine what
* text you are given.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/ */
GIT_EXTERN(int) git_diff_iterator_num_hunks_in_file(git_diff_iterator *iterator); GIT_EXTERN(int) git_diff_print_patch(
git_diff_list *diff,
void *cb_data,
git_diff_data_fn print_cb);
/** /**
* Return the number of lines in the hunk currently being examined. * Query how many diff records are there in a diff list.
*
* This will return the number of lines in the current hunk. If the diff
* has not been performed yet, this may result in loading the file and
* performing the diff.
* *
* @param iterator The iterator object * @param diff A git_diff_list generated by one of the above functions
* @return The number of lines in the current hunk (context, added, and * @return Count of number of deltas in the list
* removed all added together) or <0 on loading failure
*/ */
GIT_EXTERN(int) git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iterator); GIT_EXTERN(size_t) git_diff_num_deltas(git_diff_list *diff);
/** /**
* Return the delta information for the next file in the diff. * Query how many diff deltas are there in a diff list filtered by type.
* *
* This will return a pointer to the next git_diff_delta` to be processed or * This works just like `git_diff_entrycount()` with an extra parameter
* NULL if the iterator is at the end of the diff, then advance. This * that is a `git_delta_t` and returns just the count of how many deltas
* returns the value `GIT_ITEROVER` after processing the last file. * match that particular type.
* *
* @param delta Output parameter for the next delta object * @param diff A git_diff_list generated by one of the above functions
* @param iterator The iterator object * @param type A git_delta_t value to filter the count
* @return 0 on success, GIT_ITEROVER when done, other value < 0 on error * @return Count of number of deltas matching delta_t type
*/ */
GIT_EXTERN(int) git_diff_iterator_next_file( GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
git_diff_delta **delta, git_diff_list *diff,
git_diff_iterator *iterator); git_delta_t type);
/** /**
* Return the hunk information for the next hunk in the current file. * Return the diff delta and patch for an entry in the diff list.
*
* The `git_diff_patch` is a newly created object contains the text diffs
* for the delta. You have to call `git_diff_patch_free()` when you are
* done with it. You can use the patch object to loop over all the hunks
* and lines in the diff of the one delta.
* *
* It is recommended that you not call this if the file is a binary * For an unchanged file or a binary file, no `git_diff_patch` will be
* file, but it is allowed to do so. * created, the output will be set to NULL, and the `binary` flag will be
* set true in the `git_diff_delta` structure.
* *
* The `header` text output will contain the standard hunk header that * The `git_diff_delta` pointer points to internal data and you do not have
* would appear in diff output. The header string will be NUL terminated. * to release it when you are done with it. It will go away when the
* `git_diff_list` and `git_diff_patch` go away.
* *
* WARNING! Call this function for the first time on a file is when the * It is okay to pass NULL for either of the output parameters; if you pass
* actual text diff will be computed (it cannot be computed incrementally) * NULL for the `git_diff_patch`, then the text diff will not be calculated.
* so the first call for a new file is expensive (at least in relative
* terms - in reality, it is still pretty darn fast).
* *
* @param range Output pointer to range of lines covered by the hunk; * @param patch Output parameter for the delta patch object
* This range object is owned by the library and should not be freed. * @param delta Output parameter for the delta object
* @param header Output pointer to the text of the hunk header * @param diff Diff list object
* This string is owned by the library and should not be freed. * @param idx Index into diff list
* @param header_len Output pointer to store the length of the header text * @return 0 on success, other value < 0 on error
* @param iterator The iterator object
* @return 0 on success, GIT_ITEROVER when done with current file, other
* value < 0 on error
*/ */
GIT_EXTERN(int) git_diff_iterator_next_hunk( GIT_EXTERN(int) git_diff_get_patch(
git_diff_range **range, git_diff_patch **patch,
const char **header, const git_diff_delta **delta,
size_t *header_len, git_diff_list *diff,
git_diff_iterator *iterator); size_t idx);
/** /**
* Return the next line of the current hunk of diffs. * Free a git_diff_patch object.
*
* The `line_origin` output will tell you what type of line this is
* (e.g. was it added or removed or is it just context for the diff).
*
* The `content` will be a pointer to the file data that goes in the
* line. IT WILL NOT BE NUL TERMINATED. You have to use the `content_len`
* value and only process that many bytes of data from the content string.
*
* @param line_origin Output pointer to store a GIT_DIFF_LINE value for this
* next chunk of data. The value is a single character, not a buffer.
* @param content Output pointer to store the content of the diff; this
* string is owned by the library and should not be freed.
* @param content_len Output pointer to store the length of the content.
* @param iterator The iterator object
* @return 0 on success, GIT_ITEROVER when done with current line, other
* value < 0 on error
*/ */
GIT_EXTERN(int) git_diff_iterator_next_line( GIT_EXTERN(void) git_diff_patch_free(
char *line_origin, /**< GIT_DIFF_LINE_... value from above */ git_diff_patch *patch);
const char **content,
size_t *content_len,
git_diff_iterator *iterator);
/** /**
* Iterate over a diff generating text output like "git diff --name-status". * Get the delta associated with a patch
*
* Returning a non-zero value from the callbacks will terminate the
* iteration and cause this return `GIT_EUSER`.
*
* @param diff A git_diff_list generated by one of the above functions.
* @param cb_data Reference pointer that will be passed to your callback.
* @param print_cb Callback to make per line of diff text.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/ */
GIT_EXTERN(int) git_diff_print_compact( GIT_EXTERN(const git_diff_delta *) git_diff_patch_delta(
git_diff_list *diff, git_diff_patch *patch);
void *cb_data,
git_diff_data_fn print_cb);
/** /**
* Iterate over a diff generating text output like "git diff". * Get the number of hunks in a patch
*/
GIT_EXTERN(size_t) git_diff_patch_num_hunks(
git_diff_patch *patch);
/**
* Get the information about a hunk in a patch
* *
* This is a super easy way to generate a patch from a diff. * Given a patch and a hunk index into the patch, this returns detailed
* information about that hunk. Any of the output pointers can be passed
* as NULL if you don't care about that particular piece of information.
* *
* Returning a non-zero value from the callbacks will terminate the * @param range Output pointer to git_diff_range of hunk
* iteration and cause this return `GIT_EUSER`. * @param header Output pointer to header string for hunk. Unlike the
* content pointer for each line, this will be NUL-terminated
* @param header_len Output value of characters in header string
* @param lines_in_hunk Output count of total lines in this hunk
* @param patch Input pointer to patch object
* @param hunk_idx Input index of hunk to get information about
* @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error
*/
GIT_EXTERN(int) git_diff_patch_get_hunk(
const git_diff_range **range,
const char **header,
size_t *header_len,
size_t *lines_in_hunk,
git_diff_patch *patch,
size_t hunk_idx);
/**
* Get the number of lines in a hunk.
* *
* @param diff A git_diff_list generated by one of the above functions. * @param patch The git_diff_patch object
* @param cb_data Reference pointer that will be passed to your callbacks. * @param hunk_idx Index of the hunk
* @param print_cb Callback function to output lines of the diff. This * @return Number of lines in hunk or -1 if invalid hunk index
* same function will be called for file headers, hunk
* headers, and diff lines. Fortunately, you can probably
* use various GIT_DIFF_LINE constants to determine what
* text you are given.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/ */
GIT_EXTERN(int) git_diff_print_patch( GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk(
git_diff_list *diff, git_diff_patch *patch,
void *cb_data, size_t hunk_idx);
git_diff_data_fn print_cb);
/** /**
* Query how many diff records are there in a diff list. * Get data about a line in a hunk of a patch.
* *
* You can optionally pass in a `git_delta_t` value if you want a count * Given a patch, a hunk index, and a line index in the hunk, this
* of just entries that match that delta type, or pass -1 for all delta * will return a lot of details about that line. If you pass a hunk
* records. * index larger than the number of hunks or a line index larger than
* the number of lines in the hunk, this will return -1.
* *
* @param diff A git_diff_list generated by one of the above functions * @param line_origin A GIT_DIFF_LINE constant from above
* @param delta_t A git_delta_t value to filter the count, or -1 for all records * @param content Pointer to content of diff line, not NUL-terminated
* @return Count of number of deltas matching delta_t type * @param content_len Number of characters in content
* @param old_lineno Line number in old file or -1 if line is added
* @param new_lineno Line number in new file or -1 if line is deleted
* @param patch The patch to look in
* @param hunk_idx The index of the hunk
* @param line_of_index The index of the line in the hunk
* @return 0 on success, <0 on failure
*/ */
GIT_EXTERN(int) git_diff_entrycount( GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
git_diff_list *diff, char *line_origin,
int delta_t); const char **content,
size_t *content_len,
int *old_lineno,
int *new_lineno,
git_diff_patch *patch,
size_t hunk_idx,
size_t line_of_hunk);
/**@}*/ /**@}*/
...@@ -588,7 +595,7 @@ GIT_EXTERN(int) git_diff_entrycount( ...@@ -588,7 +595,7 @@ GIT_EXTERN(int) git_diff_entrycount(
GIT_EXTERN(int) git_diff_blobs( GIT_EXTERN(int) git_diff_blobs(
git_blob *old_blob, git_blob *old_blob,
git_blob *new_blob, git_blob *new_blob,
git_diff_options *options, const git_diff_options *options,
void *cb_data, void *cb_data,
git_diff_file_fn file_cb, git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
......
...@@ -126,7 +126,7 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli ...@@ -126,7 +126,7 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
static int checkout_blob( static int checkout_blob(
git_repository *repo, git_repository *repo,
git_oid *blob_oid, const git_oid *blob_oid,
const char *path, const char *path,
mode_t filemode, mode_t filemode,
bool can_symlink, bool can_symlink,
...@@ -150,7 +150,7 @@ static int checkout_blob( ...@@ -150,7 +150,7 @@ static int checkout_blob(
static int checkout_diff_fn( static int checkout_diff_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
float progress) float progress)
{ {
struct checkout_diff_data *data; struct checkout_diff_data *data;
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "common.h" #include "common.h"
#include "git2/diff.h"
#include "git2/oid.h"
#include "diff.h" #include "diff.h"
#include "fileops.h" #include "fileops.h"
#include "config.h" #include "config.h"
...@@ -268,9 +266,17 @@ static int diff_delta__from_two( ...@@ -268,9 +266,17 @@ static int diff_delta__from_two(
delta->old_file.mode = old_mode; delta->old_file.mode = old_mode;
delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); git_oid_cpy(&delta->new_file.oid, &new_entry->oid);
delta->new_file.size = new_entry->file_size; delta->new_file.size = new_entry->file_size;
delta->new_file.mode = new_mode; delta->new_file.mode = new_mode;
if (new_oid) {
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0)
git_oid_cpy(&delta->old_file.oid, new_oid);
else
git_oid_cpy(&delta->new_file.oid, new_oid);
}
if (new_oid || !git_oid_iszero(&new_entry->oid)) if (new_oid || !git_oid_iszero(&new_entry->oid))
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
...@@ -425,6 +431,11 @@ void git_diff_list_free(git_diff_list *diff) ...@@ -425,6 +431,11 @@ void git_diff_list_free(git_diff_list *diff)
GIT_REFCOUNT_DEC(diff, diff_list_free); GIT_REFCOUNT_DEC(diff, diff_list_free);
} }
void git_diff_list_addref(git_diff_list *diff)
{
GIT_REFCOUNT_INC(diff);
}
static int oid_for_workdir_item( static int oid_for_workdir_item(
git_repository *repo, git_repository *repo,
const git_index_entry *item, const git_index_entry *item,
...@@ -519,17 +530,17 @@ static int maybe_modified( ...@@ -519,17 +530,17 @@ static int maybe_modified(
omode == nmode) omode == nmode)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* if modes match and we have an unknown OID and a workdir iterator, /* if we have an unknown OID and a workdir iterator, then check some
* then check deeper for matching * circumstances that can accelerate things or need special handling
*/ */
else if (omode == nmode && else if (git_oid_iszero(&nitem->oid) &&
git_oid_iszero(&nitem->oid) &&
new_iter->type == GIT_ITERATOR_WORKDIR) new_iter->type == GIT_ITERATOR_WORKDIR)
{ {
/* TODO: add check against index file st_mtime to avoid racy-git */ /* TODO: add check against index file st_mtime to avoid racy-git */
/* if they files look exactly alike, then we'll assume the same */ /* if the stat data looks exactly alike, then assume the same */
if (oitem->file_size == nitem->file_size && if (omode == nmode &&
oitem->file_size == nitem->file_size &&
(!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
(oitem->ctime.seconds == nitem->ctime.seconds)) && (oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds && oitem->mtime.seconds == nitem->mtime.seconds &&
...@@ -554,16 +565,15 @@ static int maybe_modified( ...@@ -554,16 +565,15 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
} }
} }
}
/* TODO: check git attributes so we will not have to read the file /* if we got here and decided that the files are modified, but we
* in if it is marked binary. * haven't calculated the OID of the new item, then calculate it now
*/ */
if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1; return -1;
else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
omode == nmode)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* store calculated oid so we don't have to recalc later */ /* store calculated oid so we don't have to recalc later */
...@@ -797,6 +807,28 @@ on_error: ...@@ -797,6 +807,28 @@ on_error:
return -1; return -1;
} }
bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta)
{
uint32_t flags = opts ? opts->flags : 0;
if (delta->status == GIT_DELTA_UNMODIFIED &&
(flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true;
if (delta->status == GIT_DELTA_IGNORED &&
(flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true;
if (delta->status == GIT_DELTA_UNTRACKED &&
(flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
return false;
}
int git_diff_merge( int git_diff_merge(
git_diff_list *onto, git_diff_list *onto,
const git_diff_list *from) const git_diff_list *from)
...@@ -833,6 +865,14 @@ int git_diff_merge( ...@@ -833,6 +865,14 @@ int git_diff_merge(
j++; j++;
} }
/* 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)) {
git__free(delta);
continue;
}
if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
break; break;
} }
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
#ifndef INCLUDE_diff_h__ #ifndef INCLUDE_diff_h__
#define INCLUDE_diff_h__ #define INCLUDE_diff_h__
#include "git2/diff.h"
#include "git2/oid.h"
#include <stdio.h> #include <stdio.h>
#include "vector.h" #include "vector.h"
#include "buffer.h" #include "buffer.h"
...@@ -25,14 +28,17 @@ enum { ...@@ -25,14 +28,17 @@ enum {
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
}; };
#define MAX_DIFF_FILESIZE 0x20000000 typedef struct {
git_refcount rc;
git_diff_delta delta;
} git_diff_delta_refcounted;
struct git_diff_list { struct git_diff_list {
git_refcount rc; git_refcount rc;
git_repository *repo; git_repository *repo;
git_diff_options opts; git_diff_options opts;
git_vector pathspec; git_vector pathspec;
git_vector deltas; /* vector of git_diff_file_delta */ git_vector deltas; /* vector of git_diff_delta_refcounted */
git_pool pool; git_pool pool;
git_iterator_type_t old_src; git_iterator_type_t old_src;
git_iterator_type_t new_src; git_iterator_type_t new_src;
...@@ -42,27 +48,10 @@ struct git_diff_list { ...@@ -42,27 +48,10 @@ struct git_diff_list {
extern void git_diff__cleanup_modes( extern void git_diff__cleanup_modes(
uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
/** extern void git_diff_list_addref(git_diff_list *diff);
* Return the maximum possible number of files in the diff.
* extern bool git_diff_delta__should_skip(
* NOTE: This number has to be treated as an upper bound on the number of const git_diff_options *opts, const git_diff_delta *delta);
* files that have changed if the diff is with the working directory.
*
* Why?! For efficiency, we defer loading the file contents as long as
* possible, so if a file has been "touched" in the working directory and
* then reverted to the original content, it may get stored in the diff list
* as MODIFIED along with a flag that the status should be reconfirmed when
* it is actually loaded into memory. When that load happens, it could get
* flipped to UNMODIFIED. If unmodified files are being skipped, then the
* iterator will skip that file and this number may be too high.
*
* This behavior is true of `git_diff_foreach` as well, but the only
* implication there is that the `progress` value would not advance evenly.
*
* @param iterator The iterator object
* @return The maximum number of files to be iterated over
*/
int git_diff_iterator__max_files(git_diff_iterator *iterator);
#endif #endif
...@@ -5,58 +5,13 @@ ...@@ -5,58 +5,13 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "common.h" #include "common.h"
#include "git2/diff.h"
#include "git2/attr.h" #include "git2/attr.h"
#include "git2/blob.h"
#include "git2/oid.h" #include "git2/oid.h"
#include "xdiff/xdiff.h" #include "diff_output.h"
#include <ctype.h> #include <ctype.h>
#include "diff.h"
#include "map.h"
#include "fileops.h" #include "fileops.h"
#include "filter.h" #include "filter.h"
/*
* A diff_delta_context represents all of the information that goes into
* processing the diff of an observed file change. In the case of the
* git_diff_foreach() call it is an emphemeral structure that is filled
* in to execute each diff. In the case of a git_diff_iterator, it holds
* most of the information for the diff in progress.
*
* As each delta is processed, it goes through 3 phases: prep, load, exec.
*
* - In the prep phase, we just set the delta and quickly check the file
* attributes to see if it should be treated as binary.
* - In the load phase, we actually load the file content into memory.
* At this point, if we had deferred calculating OIDs, we might have to
* correct the delta to be UNMODIFIED.
* - In the exec phase, we actually run the diff and execute the callbacks.
* For foreach, this is just a pass-through to the user's callbacks. For
* iterators, we record the hunks and data spans into memory.
*/
typedef struct {
git_repository *repo;
git_diff_options *opts;
xdemitconf_t xdiff_config;
xpparam_t xdiff_params;
git_diff_delta *delta;
uint32_t prepped : 1;
uint32_t loaded : 1;
uint32_t diffable : 1;
uint32_t diffed : 1;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
git_blob *old_blob;
git_blob *new_blob;
git_map old_data;
git_map new_data;
void *cb_data;
git_diff_hunk_fn per_hunk;
git_diff_data_fn per_line;
int cb_error;
git_diff_range range;
} diff_delta_context;
static int read_next_int(const char **str, int *value) static int read_next_int(const char **str, int *value)
{ {
const char *scan = *str; const char *scan = *str;
...@@ -96,55 +51,8 @@ static int parse_hunk_header(git_diff_range *range, const char *header) ...@@ -96,55 +51,8 @@ static int parse_hunk_header(git_diff_range *range, const char *header)
return 0; return 0;
} }
static int format_hunk_header(char *header, size_t len, git_diff_range *range) #define KNOWN_BINARY_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
{ #define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
if (range->old_lines != 1) {
if (range->new_lines != 1)
return p_snprintf(
header, len, "@@ -%d,%d +%d,%d @@",
range->old_start, range->old_lines,
range->new_start, range->new_lines);
else
return p_snprintf(
header, len, "@@ -%d,%d +%d @@",
range->old_start, range->old_lines, range->new_start);
} else {
if (range->new_lines != 1)
return p_snprintf(
header, len, "@@ -%d +%d,%d @@",
range->old_start, range->new_start, range->new_lines);
else
return p_snprintf(
header, len, "@@ -%d +%d @@",
range->old_start, range->new_start);
}
}
static bool diff_delta_is_ambiguous(git_diff_delta *delta)
{
return (git_oid_iszero(&delta->new_file.oid) &&
(delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0 &&
delta->status == GIT_DELTA_MODIFIED);
}
static bool diff_delta_should_skip(git_diff_options *opts, git_diff_delta *delta)
{
if (delta->status == GIT_DELTA_UNMODIFIED &&
(opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true;
if (delta->status == GIT_DELTA_IGNORED &&
(opts->flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true;
if (delta->status == GIT_DELTA_UNTRACKED &&
(opts->flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
return false;
}
#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
static int update_file_is_binary_by_attr( static int update_file_is_binary_by_attr(
git_repository *repo, git_diff_file *file) git_repository *repo, git_diff_file *file)
...@@ -173,8 +81,6 @@ static void update_delta_is_binary(git_diff_delta *delta) ...@@ -173,8 +81,6 @@ static void update_delta_is_binary(git_diff_delta *delta)
(delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)
delta->binary = 1; delta->binary = 1;
#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 && else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
(delta->new_file.flags & NOT_BINARY_FLAGS) != 0) (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
delta->binary = 0; delta->binary = 0;
...@@ -182,10 +88,11 @@ static void update_delta_is_binary(git_diff_delta *delta) ...@@ -182,10 +88,11 @@ static void update_delta_is_binary(git_diff_delta *delta)
/* otherwise leave delta->binary value untouched */ /* otherwise leave delta->binary value untouched */
} }
static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) static int diff_delta_is_binary_by_attr(
diff_context *ctxt, git_diff_patch *patch)
{ {
int error = 0, mirror_new; int error = 0, mirror_new;
git_diff_delta *delta = ctxt->delta; git_diff_delta *delta = patch->delta;
delta->binary = -1; delta->binary = -1;
...@@ -200,7 +107,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) ...@@ -200,7 +107,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
} }
/* check if user is forcing us to text diff these files */ /* check if user is forcing us to text diff these files */
if (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) { if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {
delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->binary = 0; delta->binary = 0;
...@@ -214,7 +121,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) ...@@ -214,7 +121,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
mirror_new = (delta->new_file.path == delta->old_file.path || mirror_new = (delta->new_file.path == delta->old_file.path ||
strcmp(delta->new_file.path, delta->old_file.path) == 0); strcmp(delta->new_file.path, delta->old_file.path) == 0);
if (mirror_new) if (mirror_new)
delta->new_file.flags |= (delta->old_file.flags & BINARY_DIFF_FLAGS); delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
else else
error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file); error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);
...@@ -224,11 +131,13 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) ...@@ -224,11 +131,13 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
} }
static int diff_delta_is_binary_by_content( static int diff_delta_is_binary_by_content(
diff_delta_context *ctxt, git_diff_file *file, git_map *map) diff_context *ctxt, git_diff_delta *delta, git_diff_file *file, git_map *map)
{ {
git_buf search; git_buf search;
if ((file->flags & BINARY_DIFF_FLAGS) == 0) { GIT_UNUSED(ctxt);
if ((file->flags & KNOWN_BINARY_FLAGS) == 0) {
search.ptr = map->data; search.ptr = map->data;
search.size = min(map->len, 4000); search.size = min(map->len, 4000);
...@@ -238,17 +147,17 @@ static int diff_delta_is_binary_by_content( ...@@ -238,17 +147,17 @@ static int diff_delta_is_binary_by_content(
file->flags |= GIT_DIFF_FILE_NOT_BINARY; file->flags |= GIT_DIFF_FILE_NOT_BINARY;
} }
update_delta_is_binary(ctxt->delta); update_delta_is_binary(delta);
return 0; return 0;
} }
static int diff_delta_is_binary_by_size( static int diff_delta_is_binary_by_size(
diff_delta_context *ctxt, git_diff_file *file) diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)
{ {
git_off_t threshold = MAX_DIFF_FILESIZE; git_off_t threshold = MAX_DIFF_FILESIZE;
if ((file->flags & BINARY_DIFF_FLAGS) != 0) if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
return 0; return 0;
if (ctxt && ctxt->opts) { if (ctxt && ctxt->opts) {
...@@ -262,13 +171,13 @@ static int diff_delta_is_binary_by_size( ...@@ -262,13 +171,13 @@ static int diff_delta_is_binary_by_size(
if (file->size > threshold) if (file->size > threshold)
file->flags |= GIT_DIFF_FILE_BINARY; file->flags |= GIT_DIFF_FILE_BINARY;
update_delta_is_binary(ctxt->delta); update_delta_is_binary(delta);
return 0; return 0;
} }
static void setup_xdiff_options( static void setup_xdiff_options(
git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param) const git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)
{ {
memset(cfg, 0, sizeof(xdemitconf_t)); memset(cfg, 0, sizeof(xdemitconf_t));
memset(param, 0, sizeof(xpparam_t)); memset(param, 0, sizeof(xpparam_t));
...@@ -289,8 +198,10 @@ static void setup_xdiff_options( ...@@ -289,8 +198,10 @@ static void setup_xdiff_options(
param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
} }
static int get_blob_content( static int get_blob_content(
diff_delta_context *ctxt, diff_context *ctxt,
git_diff_delta *delta,
git_diff_file *file, git_diff_file *file,
git_map *map, git_map *map,
git_blob **blob) git_blob **blob)
...@@ -318,9 +229,9 @@ static int get_blob_content( ...@@ -318,9 +229,9 @@ static int get_blob_content(
} }
/* if blob is too large to diff, mark as binary */ /* if blob is too large to diff, mark as binary */
if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0) if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)
return error; return error;
if (ctxt->delta->binary == 1) if (delta->binary == 1)
return 0; return 0;
if (odb_obj != NULL) { if (odb_obj != NULL) {
...@@ -336,11 +247,12 @@ static int get_blob_content( ...@@ -336,11 +247,12 @@ static int get_blob_content(
map->data = (void *)git_blob_rawcontent(*blob); map->data = (void *)git_blob_rawcontent(*blob);
map->len = git_blob_rawsize(*blob); map->len = git_blob_rawsize(*blob);
return diff_delta_is_binary_by_content(ctxt, file, map); return diff_delta_is_binary_by_content(ctxt, delta, file, map);
} }
static int get_workdir_content( static int get_workdir_content(
diff_delta_context *ctxt, diff_context *ctxt,
git_diff_delta *delta,
git_diff_file *file, git_diff_file *file,
git_map *map) git_map *map)
{ {
...@@ -386,8 +298,8 @@ static int get_workdir_content( ...@@ -386,8 +298,8 @@ static int get_workdir_content(
if (!file->size) if (!file->size)
file->size = git_futils_filesize(fd); file->size = git_futils_filesize(fd);
if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0 || if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
ctxt->delta->binary == 1) delta->binary == 1)
goto close_and_cleanup; goto close_and_cleanup;
error = git_filters_load( error = git_filters_load(
...@@ -428,7 +340,7 @@ close_and_cleanup: ...@@ -428,7 +340,7 @@ close_and_cleanup:
} }
if (!error) if (!error)
error = diff_delta_is_binary_by_content(ctxt, file, map); error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
cleanup: cleanup:
git_buf_free(&path); git_buf_free(&path);
...@@ -454,78 +366,101 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob) ...@@ -454,78 +366,101 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
} }
} }
static void diff_delta_init_context(
diff_delta_context *ctxt, static void diff_context_init(
diff_context *ctxt,
git_diff_list *diff,
git_repository *repo, git_repository *repo,
git_diff_options *opts, const git_diff_options *opts,
git_iterator_type_t old_src, void *data,
git_iterator_type_t new_src) git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb,
git_diff_data_fn data_cb)
{ {
memset(ctxt, 0, sizeof(diff_delta_context)); memset(ctxt, 0, sizeof(diff_context));
ctxt->repo = repo; ctxt->repo = repo;
ctxt->diff = diff;
ctxt->opts = opts; ctxt->opts = opts;
ctxt->old_src = old_src; ctxt->file_cb = file_cb;
ctxt->new_src = new_src; ctxt->hunk_cb = hunk_cb;
ctxt->data_cb = data_cb;
ctxt->cb_data = data;
ctxt->cb_error = 0;
setup_xdiff_options(opts, &ctxt->xdiff_config, &ctxt->xdiff_params); setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
} }
static void diff_delta_init_context_from_diff_list( static int diff_delta_file_callback(
diff_delta_context *ctxt, diff_context *ctxt, git_diff_delta *delta, size_t idx)
git_diff_list *diff)
{ {
diff_delta_init_context( float progress;
ctxt, diff->repo, &diff->opts, diff->old_src, diff->new_src);
if (!ctxt->file_cb)
return 0;
progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;
if (ctxt->file_cb(ctxt->cb_data, delta, progress) != 0)
ctxt->cb_error = GIT_EUSER;
return ctxt->cb_error;
} }
static void diff_delta_unload(diff_delta_context *ctxt) static void diff_patch_init(
diff_context *ctxt, git_diff_patch *patch)
{ {
ctxt->diffed = 0; memset(patch, 0, sizeof(git_diff_patch));
if (ctxt->loaded) { patch->diff = ctxt->diff;
release_content(&ctxt->delta->old_file, &ctxt->old_data, ctxt->old_blob); patch->ctxt = ctxt;
release_content(&ctxt->delta->new_file, &ctxt->new_data, ctxt->new_blob);
ctxt->loaded = 0;
}
ctxt->delta = NULL; if (patch->diff) {
ctxt->prepped = 0; patch->old_src = patch->diff->old_src;
patch->new_src = patch->diff->new_src;
} else {
patch->old_src = patch->new_src = GIT_ITERATOR_TREE;
}
} }
static int diff_delta_prep(diff_delta_context *ctxt) static git_diff_patch *diff_patch_alloc(
diff_context *ctxt, git_diff_delta *delta)
{ {
int error; git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));
if (!patch)
return NULL;
if (ctxt->prepped || !ctxt->delta) diff_patch_init(ctxt, patch);
return 0;
error = diff_delta_is_binary_by_attr(ctxt); git_diff_list_addref(patch->diff);
ctxt->prepped = !error; GIT_REFCOUNT_INC(patch);
return error; patch->delta = delta;
patch->flags = GIT_DIFF_PATCH_ALLOCATED;
return patch;
} }
static int diff_delta_load(diff_delta_context *ctxt) static int diff_patch_load(
diff_context *ctxt, git_diff_patch *patch)
{ {
int error = 0; int error = 0;
git_diff_delta *delta = ctxt->delta; git_diff_delta *delta = patch->delta;
bool check_if_unmodified = false; bool check_if_unmodified = false;
if (ctxt->loaded || !ctxt->delta) if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
return 0; return 0;
if (!ctxt->prepped && (error = diff_delta_prep(ctxt)) < 0) error = diff_delta_is_binary_by_attr(ctxt, patch);
goto cleanup;
ctxt->old_data.data = ""; patch->old_data.data = "";
ctxt->old_data.len = 0; patch->old_data.len = 0;
ctxt->old_blob = NULL; patch->old_blob = NULL;
ctxt->new_data.data = ""; patch->new_data.data = "";
ctxt->new_data.len = 0; patch->new_data.len = 0;
ctxt->new_blob = NULL; patch->new_blob = NULL;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
...@@ -557,36 +492,38 @@ static int diff_delta_load(diff_delta_context *ctxt) ...@@ -557,36 +492,38 @@ static int diff_delta_load(diff_delta_context *ctxt)
*/ */
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
ctxt->old_src == GIT_ITERATOR_WORKDIR) { patch->old_src == GIT_ITERATOR_WORKDIR) {
if ((error = get_workdir_content( if ((error = get_workdir_content(
ctxt, &delta->old_file, &ctxt->old_data)) < 0) ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
ctxt->new_src == GIT_ITERATOR_WORKDIR) { patch->new_src == GIT_ITERATOR_WORKDIR) {
if ((error = get_workdir_content( if ((error = get_workdir_content(
ctxt, &delta->new_file, &ctxt->new_data)) < 0) ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
ctxt->old_src != GIT_ITERATOR_WORKDIR) { patch->old_src != GIT_ITERATOR_WORKDIR) {
if ((error = get_blob_content( if ((error = get_blob_content(
ctxt, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) ctxt, delta, &delta->old_file,
&patch->old_data, &patch->old_blob)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
ctxt->new_src != GIT_ITERATOR_WORKDIR) { patch->new_src != GIT_ITERATOR_WORKDIR) {
if ((error = get_blob_content( if ((error = get_blob_content(
ctxt, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) ctxt, delta, &delta->new_file,
&patch->new_data, &patch->new_blob)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
...@@ -606,33 +543,34 @@ static int diff_delta_load(diff_delta_context *ctxt) ...@@ -606,33 +543,34 @@ static int diff_delta_load(diff_delta_context *ctxt)
} }
cleanup: cleanup:
/* last change to update binary flag */
if (delta->binary == -1) if (delta->binary == -1)
update_delta_is_binary(delta); update_delta_is_binary(delta);
ctxt->loaded = !error; if (!error) {
patch->flags |= GIT_DIFF_PATCH_LOADED;
/* flag if we would want to diff the contents of these files */ if (delta->binary != 1 &&
if (ctxt->loaded)
ctxt->diffable =
(delta->binary != 1 &&
delta->status != GIT_DELTA_UNMODIFIED && delta->status != GIT_DELTA_UNMODIFIED &&
(ctxt->old_data.len || ctxt->new_data.len) && (patch->old_data.len || patch->new_data.len) &&
git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)); !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
}
return error; return error;
} }
static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)
{ {
diff_delta_context *ctxt = priv; git_diff_patch *patch = priv;
diff_context *ctxt = patch->ctxt;
if (len == 1) { if (len == 1) {
if ((ctxt->cb_error = parse_hunk_header(&ctxt->range, bufs[0].ptr)) < 0) ctxt->cb_error = parse_hunk_header(&ctxt->cb_range, bufs[0].ptr);
if (ctxt->cb_error < 0)
return ctxt->cb_error; return ctxt->cb_error;
if (ctxt->per_hunk != NULL && if (ctxt->hunk_cb != NULL &&
ctxt->per_hunk(ctxt->cb_data, ctxt->delta, &ctxt->range, ctxt->hunk_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[0].ptr, bufs[0].size)) bufs[0].ptr, bufs[0].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
...@@ -644,9 +582,9 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -644,9 +582,9 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
GIT_DIFF_LINE_CONTEXT; GIT_DIFF_LINE_CONTEXT;
if (ctxt->per_line != NULL && if (ctxt->data_cb != NULL &&
ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[1].ptr, bufs[1].size)) origin, bufs[1].ptr, bufs[1].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
...@@ -661,105 +599,276 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -661,105 +599,276 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
GIT_DIFF_LINE_CONTEXT; GIT_DIFF_LINE_CONTEXT;
if (ctxt->per_line != NULL && if (ctxt->data_cb != NULL &&
ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[2].ptr, bufs[2].size)) origin, bufs[2].ptr, bufs[2].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
return ctxt->cb_error; return ctxt->cb_error;
} }
static int diff_delta_exec( static int diff_patch_generate(
diff_delta_context *ctxt, diff_context *ctxt, git_diff_patch *patch)
void *cb_data,
git_diff_hunk_fn per_hunk,
git_diff_data_fn per_line)
{ {
int error = 0; int error = 0;
xdemitcb_t xdiff_callback; xdemitcb_t xdiff_callback;
mmfile_t old_xdiff_data, new_xdiff_data; mmfile_t old_xdiff_data, new_xdiff_data;
if (ctxt->diffed || !ctxt->delta) if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
return 0; return 0;
if (!ctxt->loaded && (error = diff_delta_load(ctxt)) < 0) if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
goto cleanup; if ((error = diff_patch_load(ctxt, patch)) < 0)
return error;
if (!ctxt->diffable) if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
return 0; return 0;
ctxt->cb_data = cb_data; if (ctxt)
ctxt->per_hunk = per_hunk; patch->ctxt = ctxt;
ctxt->per_line = per_line;
ctxt->cb_error = 0;
memset(&xdiff_callback, 0, sizeof(xdiff_callback)); memset(&xdiff_callback, 0, sizeof(xdiff_callback));
xdiff_callback.outf = diff_delta_cb; xdiff_callback.outf = diff_patch_cb;
xdiff_callback.priv = ctxt; xdiff_callback.priv = patch;
old_xdiff_data.ptr = ctxt->old_data.data; old_xdiff_data.ptr = patch->old_data.data;
old_xdiff_data.size = ctxt->old_data.len; old_xdiff_data.size = patch->old_data.len;
new_xdiff_data.ptr = ctxt->new_data.data; new_xdiff_data.ptr = patch->new_data.data;
new_xdiff_data.size = ctxt->new_data.len; new_xdiff_data.size = patch->new_data.len;
xdl_diff(&old_xdiff_data, &new_xdiff_data, xdl_diff(&old_xdiff_data, &new_xdiff_data,
&ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback); &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);
error = ctxt->cb_error; error = ctxt->cb_error;
cleanup: if (!error)
ctxt->diffed = !error; patch->flags |= GIT_DIFF_PATCH_DIFFED;
return error; return error;
} }
static void diff_patch_unload(git_diff_patch *patch)
{
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {
patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);
patch->hunks_size = 0;
patch->lines_size = 0;
}
if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) {
patch->flags = (patch->flags & ~GIT_DIFF_PATCH_LOADED);
release_content(
&patch->delta->old_file, &patch->old_data, patch->old_blob);
release_content(
&patch->delta->new_file, &patch->new_data, patch->new_blob);
}
}
static void diff_patch_free(git_diff_patch *patch)
{
diff_patch_unload(patch);
git__free(patch->lines);
patch->lines = NULL;
patch->lines_asize = 0;
git__free(patch->hunks);
patch->hunks = NULL;
patch->hunks_asize = 0;
if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))
return;
patch->flags = 0;
git_diff_list_free(patch->diff); /* decrements refcount */
git__free(patch);
}
#define MAX_HUNK_STEP 128
#define MIN_HUNK_STEP 8
#define MAX_LINE_STEP 256
#define MIN_LINE_STEP 8
static int diff_patch_hunk_cb(
void *cb_data,
const git_diff_delta *delta,
const git_diff_range *range,
const char *header,
size_t header_len)
{
git_diff_patch *patch = cb_data;
diff_patch_hunk *hunk;
GIT_UNUSED(delta);
if (patch->hunks_size >= patch->hunks_asize) {
size_t new_size;
diff_patch_hunk *new_hunks;
if (patch->hunks_asize > MAX_HUNK_STEP)
new_size = patch->hunks_asize + MAX_HUNK_STEP;
else
new_size = patch->hunks_asize * 2;
if (new_size < MIN_HUNK_STEP)
new_size = MIN_HUNK_STEP;
new_hunks = git__realloc(
patch->hunks, new_size * sizeof(diff_patch_hunk));
if (!new_hunks)
return -1;
patch->hunks = new_hunks;
patch->hunks_asize = new_size;
}
hunk = &patch->hunks[patch->hunks_size++];
memcpy(&hunk->range, range, sizeof(hunk->range));
assert(header_len + 1 < sizeof(hunk->header));
memcpy(&hunk->header, header, header_len);
hunk->header[header_len] = '\0';
hunk->header_len = header_len;
hunk->line_start = patch->lines_size;
hunk->line_count = 0;
return 0;
}
static int diff_patch_line_cb(
void *cb_data,
const git_diff_delta *delta,
const git_diff_range *range,
char line_origin,
const char *content,
size_t content_len)
{
git_diff_patch *patch = cb_data;
diff_patch_hunk *hunk;
diff_patch_line *last, *line;
GIT_UNUSED(delta);
GIT_UNUSED(range);
assert(patch->hunks_size > 0);
assert(patch->hunks != NULL);
hunk = &patch->hunks[patch->hunks_size - 1];
if (patch->lines_size >= patch->lines_asize) {
size_t new_size;
diff_patch_line *new_lines;
if (patch->lines_asize > MAX_LINE_STEP)
new_size = patch->lines_asize + MAX_LINE_STEP;
else
new_size = patch->lines_asize * 2;
if (new_size < MIN_LINE_STEP)
new_size = MIN_LINE_STEP;
new_lines = git__realloc(
patch->lines, new_size * sizeof(diff_patch_line));
if (!new_lines)
return -1;
patch->lines = new_lines;
patch->lines_asize = new_size;
}
last = (patch->lines_size > 0) ?
&patch->lines[patch->lines_size - 1] : NULL;
line = &patch->lines[patch->lines_size++];
line->ptr = content;
line->len = content_len;
line->origin = line_origin;
/* do some bookkeeping so we can provide old/new line numbers */
for (line->lines = 0; content_len > 0; --content_len) {
if (*content++ == '\n')
++line->lines;
}
if (!last) {
line->oldno = hunk->range.old_start;
line->newno = hunk->range.new_start;
} else {
switch (last->origin) {
case GIT_DIFF_LINE_ADDITION:
line->oldno = last->oldno;
line->newno = last->newno + last->lines;
break;
case GIT_DIFF_LINE_DELETION:
line->oldno = last->oldno + last->lines;
line->newno = last->newno;
break;
default:
line->oldno = last->oldno + last->lines;
line->newno = last->newno + last->lines;
break;
}
}
hunk->line_count++;
return 0;
}
int git_diff_foreach( int git_diff_foreach(
git_diff_list *diff, git_diff_list *diff,
void *data, void *cb_data,
git_diff_file_fn file_cb, git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn data_cb)
{ {
int error = 0; int error = 0;
diff_delta_context ctxt; diff_context ctxt;
size_t idx; size_t idx;
git_diff_patch patch;
diff_delta_init_context_from_diff_list(&ctxt, diff); diff_context_init(
&ctxt, diff, diff->repo, &diff->opts,
cb_data, file_cb, hunk_cb, data_cb);
git_vector_foreach(&diff->deltas, idx, ctxt.delta) { diff_patch_init(&ctxt, &patch);
if (diff_delta_is_ambiguous(ctxt.delta))
if ((error = diff_delta_load(&ctxt)) < 0)
goto cleanup;
if (diff_delta_should_skip(ctxt.opts, ctxt.delta)) git_vector_foreach(&diff->deltas, idx, patch.delta) {
/* check flags against patch status */
if (git_diff_delta__should_skip(ctxt.opts, patch.delta))
continue; continue;
if ((error = diff_delta_load(&ctxt)) < 0) if (!(error = diff_patch_load(&ctxt, &patch))) {
goto cleanup;
if (file_cb != NULL && /* invoke file callback */
file_cb(data, ctxt.delta, (float)idx / diff->deltas.length) != 0) error = diff_delta_file_callback(&ctxt, patch.delta, idx);
{
error = GIT_EUSER;
goto cleanup;
}
error = diff_delta_exec(&ctxt, data, hunk_cb, line_cb); /* generate diffs and invoke hunk and line callbacks */
if (!error)
error = diff_patch_generate(&ctxt, &patch);
cleanup: diff_patch_unload(&patch);
diff_delta_unload(&ctxt); }
if (error < 0) if (error < 0)
break; break;
} }
if (error == GIT_EUSER) if (error == GIT_EUSER)
giterr_clear(); giterr_clear(); /* don't let error message leak */
return error; return error;
} }
typedef struct { typedef struct {
git_diff_list *diff; git_diff_list *diff;
git_diff_data_fn print_cb; git_diff_data_fn print_cb;
...@@ -778,14 +887,11 @@ static char pick_suffix(int mode) ...@@ -778,14 +887,11 @@ static char pick_suffix(int mode)
return ' '; return ' ';
} }
static int print_compact(void *data, git_diff_delta *delta, float progress) char git_diff_status_char(git_delta_t status)
{ {
diff_print_info *pi = data; char code;
char code, old_suffix, new_suffix;
GIT_UNUSED(progress);
switch (delta->status) { switch (status) {
case GIT_DELTA_ADDED: code = 'A'; break; case GIT_DELTA_ADDED: code = 'A'; break;
case GIT_DELTA_DELETED: code = 'D'; break; case GIT_DELTA_DELETED: code = 'D'; break;
case GIT_DELTA_MODIFIED: code = 'M'; break; case GIT_DELTA_MODIFIED: code = 'M'; break;
...@@ -793,10 +899,21 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) ...@@ -793,10 +899,21 @@ static int print_compact(void *data, git_diff_delta *delta, float progress)
case GIT_DELTA_COPIED: code = 'C'; break; case GIT_DELTA_COPIED: code = 'C'; break;
case GIT_DELTA_IGNORED: code = 'I'; break; case GIT_DELTA_IGNORED: code = 'I'; break;
case GIT_DELTA_UNTRACKED: code = '?'; break; case GIT_DELTA_UNTRACKED: code = '?'; break;
default: code = 0; default: code = ' '; break;
} }
if (!code) return code;
}
static int print_compact(
void *data, const git_diff_delta *delta, float progress)
{
diff_print_info *pi = data;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
GIT_UNUSED(progress);
if (code == ' ')
return 0; return 0;
old_suffix = pick_suffix(delta->old_file.mode); old_suffix = pick_suffix(delta->old_file.mode);
...@@ -851,8 +968,7 @@ int git_diff_print_compact( ...@@ -851,8 +968,7 @@ int git_diff_print_compact(
return error; return error;
} }
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
{ {
char start_oid[8], end_oid[8]; char start_oid[8], end_oid[8];
...@@ -882,7 +998,8 @@ static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) ...@@ -882,7 +998,8 @@ static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
return 0; return 0;
} }
static int print_patch_file(void *data, git_diff_delta *delta, float progress) static int print_patch_file(
void *data, const git_diff_delta *delta, float progress)
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
const char *oldpfx = pi->diff->opts.old_prefix; const char *oldpfx = pi->diff->opts.old_prefix;
...@@ -949,8 +1066,8 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) ...@@ -949,8 +1066,8 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress)
static int print_patch_hunk( static int print_patch_hunk(
void *data, void *data,
git_diff_delta *d, const git_diff_delta *d,
git_diff_range *r, const git_diff_range *r,
const char *header, const char *header,
size_t header_len) size_t header_len)
{ {
...@@ -972,8 +1089,8 @@ static int print_patch_hunk( ...@@ -972,8 +1089,8 @@ static int print_patch_hunk(
static int print_patch_line( static int print_patch_line(
void *data, void *data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
char line_origin, /* GIT_DIFF_LINE value from above */ char line_origin, /* GIT_DIFF_LINE value from above */
const char *content, const char *content,
size_t content_len) size_t content_len)
...@@ -1024,30 +1141,6 @@ int git_diff_print_patch( ...@@ -1024,30 +1141,6 @@ int git_diff_print_patch(
return error; return error;
} }
int git_diff_entrycount(git_diff_list *diff, int delta_t)
{
int count = 0;
unsigned int i;
git_diff_delta *delta;
assert(diff);
git_vector_foreach(&diff->deltas, i, delta) {
if (diff_delta_should_skip(&diff->opts, delta))
continue;
if (delta_t < 0 || delta->status == (git_delta_t)delta_t)
count++;
}
/* It is possible that this has overcounted the number of diffs because
* there may be entries that are marked as MODIFIED due to differences
* in stat() output that will turn out to be the same once we calculate
* the actual SHA of the data on disk.
*/
return count;
}
static void set_data_from_blob( static void set_data_from_blob(
git_blob *blob, git_map *map, git_diff_file *file) git_blob *blob, git_map *map, git_diff_file *file)
...@@ -1056,6 +1149,7 @@ static void set_data_from_blob( ...@@ -1056,6 +1149,7 @@ static void set_data_from_blob(
map->data = (char *)git_blob_rawcontent(blob); map->data = (char *)git_blob_rawcontent(blob);
file->size = map->len = git_blob_rawsize(blob); file->size = map->len = git_blob_rawsize(blob);
git_oid_cpy(&file->oid, git_object_id((const git_object *)blob)); git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
file->mode = 0644;
} else { } else {
map->data = ""; map->data = "";
file->size = map->len = 0; file->size = map->len = 0;
...@@ -1066,433 +1160,254 @@ static void set_data_from_blob( ...@@ -1066,433 +1160,254 @@ static void set_data_from_blob(
int git_diff_blobs( int git_diff_blobs(
git_blob *old_blob, git_blob *old_blob,
git_blob *new_blob, git_blob *new_blob,
git_diff_options *options, const git_diff_options *options,
void *cb_data, void *cb_data,
git_diff_file_fn file_cb, git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn data_cb)
{ {
int error; int error;
diff_delta_context ctxt;
git_diff_delta delta;
git_blob *new, *old;
git_repository *repo; git_repository *repo;
diff_context ctxt;
new = new_blob; git_diff_delta delta;
old = old_blob; git_diff_patch patch;
if (options && (options->flags & GIT_DIFF_REVERSE)) { if (options && (options->flags & GIT_DIFF_REVERSE)) {
git_blob *swap = old; git_blob *swap = old_blob;
old = new; old_blob = new_blob;
new = swap; new_blob = swap;
} }
if (new) if (new_blob)
repo = git_object_owner((git_object *)new); repo = git_object_owner((git_object *)new_blob);
else if (old) else if (old_blob)
repo = git_object_owner((git_object *)old); repo = git_object_owner((git_object *)old_blob);
else else
repo = NULL; repo = NULL;
diff_delta_init_context( diff_context_init(
&ctxt, repo, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); &ctxt, NULL, repo, options,
cb_data, file_cb, hunk_cb, data_cb);
diff_patch_init(&ctxt, &patch);
/* populate a "fake" delta record */ /* create a fake delta record and simulate diff_patch_load */
memset(&delta, 0, sizeof(delta)); memset(&delta, 0, sizeof(delta));
delta.binary = -1;
set_data_from_blob(old, &ctxt.old_data, &delta.old_file); set_data_from_blob(old_blob, &patch.old_data, &delta.old_file);
set_data_from_blob(new, &ctxt.new_data, &delta.new_file); set_data_from_blob(new_blob, &patch.new_data, &delta.new_file);
delta.status = new ? delta.status = new_blob ?
(old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (old_blob ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
(old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); (old_blob ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0)
delta.status = GIT_DELTA_UNMODIFIED; delta.status = GIT_DELTA_UNMODIFIED;
ctxt.delta = &delta; patch.delta = &delta;
if ((error = diff_delta_prep(&ctxt)) < 0)
goto cleanup;
if (delta.binary == -1) {
if ((error = diff_delta_is_binary_by_content( if ((error = diff_delta_is_binary_by_content(
&ctxt, &delta.old_file, &ctxt.old_data)) < 0 || &ctxt, &delta, &delta.old_file, &patch.old_data)) < 0 ||
(error = diff_delta_is_binary_by_content( (error = diff_delta_is_binary_by_content(
&ctxt, &delta.new_file, &ctxt.new_data)) < 0) &ctxt, &delta, &delta.new_file, &patch.new_data)) < 0)
goto cleanup; goto cleanup;
}
ctxt.loaded = 1; patch.flags |= GIT_DIFF_PATCH_LOADED;
ctxt.diffable = (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED); if (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED)
patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
/* do diffs */ /* do diffs */
if (file_cb != NULL && file_cb(cb_data, &delta, 1)) { if (!(error = diff_delta_file_callback(&ctxt, patch.delta, 1)))
error = GIT_EUSER; error = diff_patch_generate(&ctxt, &patch);
goto cleanup;
}
error = diff_delta_exec(&ctxt, cb_data, hunk_cb, line_cb);
cleanup: cleanup:
diff_patch_unload(&patch);
if (error == GIT_EUSER) if (error == GIT_EUSER)
giterr_clear(); giterr_clear();
diff_delta_unload(&ctxt);
return error; return error;
} }
typedef struct diffiter_line diffiter_line;
struct diffiter_line {
diffiter_line *next;
char origin;
const char *ptr;
size_t len;
};
typedef struct diffiter_hunk diffiter_hunk; size_t git_diff_num_deltas(git_diff_list *diff)
struct diffiter_hunk {
diffiter_hunk *next;
git_diff_range range;
diffiter_line *line_head;
size_t line_count;
};
struct git_diff_iterator {
git_diff_list *diff;
diff_delta_context ctxt;
size_t file_index;
size_t next_index;
git_pool hunks;
size_t hunk_count;
diffiter_hunk *hunk_head;
diffiter_hunk *hunk_curr;
char hunk_header[128];
git_pool lines;
diffiter_line *line_curr;
};
typedef struct {
git_diff_iterator *iter;
diffiter_hunk *last_hunk;
diffiter_line *last_line;
} diffiter_cb_info;
static int diffiter_hunk_cb(
void *cb_data,
git_diff_delta *delta,
git_diff_range *range,
const char *header,
size_t header_len)
{ {
diffiter_cb_info *info = cb_data; assert(diff);
git_diff_iterator *iter = info->iter; return (size_t)diff->deltas.length;
diffiter_hunk *hunk;
GIT_UNUSED(delta);
GIT_UNUSED(header);
GIT_UNUSED(header_len);
if ((hunk = git_pool_mallocz(&iter->hunks, 1)) == NULL) {
iter->ctxt.cb_error = -1;
return -1;
}
if (info->last_hunk)
info->last_hunk->next = hunk;
info->last_hunk = hunk;
info->last_line = NULL;
memcpy(&hunk->range, range, sizeof(hunk->range));
iter->hunk_count++;
/* adding first hunk to list */
if (iter->hunk_head == NULL) {
iter->hunk_head = hunk;
iter->hunk_curr = NULL;
}
return 0;
} }
static int diffiter_line_cb( size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
void *cb_data,
git_diff_delta *delta,
git_diff_range *range,
char line_origin,
const char *content,
size_t content_len)
{ {
diffiter_cb_info *info = cb_data; size_t i, count = 0;
git_diff_iterator *iter = info->iter; git_diff_delta *delta;
diffiter_line *line;
GIT_UNUSED(delta); assert(diff);
GIT_UNUSED(range);
if ((line = git_pool_mallocz(&iter->lines, 1)) == NULL) { git_vector_foreach(&diff->deltas, i, delta) {
iter->ctxt.cb_error = -1; count += (delta->status == type);
return -1;
} }
if (info->last_line) return count;
info->last_line->next = line;
info->last_line = line;
line->origin = line_origin;
line->ptr = content;
line->len = content_len;
info->last_hunk->line_count++;
if (info->last_hunk->line_head == NULL)
info->last_hunk->line_head = line;
return 0;
} }
static int diffiter_do_diff_file(git_diff_iterator *iter) int git_diff_get_patch(
git_diff_patch **patch_ptr,
const git_diff_delta **delta_ptr,
git_diff_list *diff,
size_t idx)
{ {
int error; int error;
diffiter_cb_info info; diff_context ctxt;
git_diff_delta *delta;
if (iter->ctxt.diffed || !iter->ctxt.delta) git_diff_patch *patch;
return 0;
memset(&info, 0, sizeof(info));
info.iter = iter;
error = diff_delta_exec(
&iter->ctxt, &info, diffiter_hunk_cb, diffiter_line_cb);
if (error == GIT_EUSER)
error = iter->ctxt.cb_error;
return error;
}
static void diffiter_do_unload_file(git_diff_iterator *iter) if (patch_ptr)
{ *patch_ptr = NULL;
if (iter->ctxt.loaded) {
diff_delta_unload(&iter->ctxt);
git_pool_clear(&iter->lines); delta = git_vector_get(&diff->deltas, idx);
git_pool_clear(&iter->hunks); if (!delta) {
if (delta_ptr)
*delta_ptr = NULL;
giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
return GIT_ENOTFOUND;
} }
iter->ctxt.delta = NULL; if (delta_ptr)
iter->hunk_curr = iter->hunk_head = NULL; *delta_ptr = delta;
iter->hunk_count = 0;
iter->line_curr = NULL;
}
int git_diff_iterator_new(
git_diff_iterator **iterator_ptr,
git_diff_list *diff)
{
git_diff_iterator *iter;
assert(diff && iterator_ptr);
*iterator_ptr = NULL; if (!patch_ptr && delta->binary != -1)
return 0;
iter = git__malloc(sizeof(git_diff_iterator));
GITERR_CHECK_ALLOC(iter);
memset(iter, 0, sizeof(*iter));
iter->diff = diff;
GIT_REFCOUNT_INC(iter->diff);
diff_delta_init_context_from_diff_list(&iter->ctxt, diff); diff_context_init(
&ctxt, diff, diff->repo, &diff->opts,
NULL, NULL, diff_patch_hunk_cb, diff_patch_line_cb);
if (git_pool_init(&iter->hunks, sizeof(diffiter_hunk), 0) < 0 || if (git_diff_delta__should_skip(ctxt.opts, delta))
git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0) return 0;
goto fail;
*iterator_ptr = iter; patch = diff_patch_alloc(&ctxt, delta);
if (!patch)
return -1;
return 0; if (!(error = diff_patch_load(&ctxt, patch))) {
ctxt.cb_data = patch;
fail: error = diff_patch_generate(&ctxt, patch);
git_diff_iterator_free(iter);
return -1; if (error == GIT_EUSER)
} error = ctxt.cb_error;
}
void git_diff_iterator_free(git_diff_iterator *iter) if (error)
{ git_diff_patch_free(patch);
diffiter_do_unload_file(iter); else if (patch_ptr)
git_diff_list_free(iter->diff); /* decrement ref count */ *patch_ptr = patch;
git__free(iter);
}
float git_diff_iterator_progress(git_diff_iterator *iter) return error;
{
return (float)iter->next_index / (float)iter->diff->deltas.length;
} }
int git_diff_iterator__max_files(git_diff_iterator *iter) void git_diff_patch_free(git_diff_patch *patch)
{ {
return (int)iter->diff->deltas.length; if (patch)
GIT_REFCOUNT_DEC(patch, diff_patch_free);
} }
int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
{ {
int error = diffiter_do_diff_file(iter); assert(patch);
return (error != 0) ? error : (int)iter->hunk_count; return patch->delta;
} }
int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) size_t git_diff_patch_num_hunks(git_diff_patch *patch)
{ {
int error = diffiter_do_diff_file(iter); assert(patch);
if (error) return patch->hunks_size;
return error;
if (iter->hunk_curr)
return (int)iter->hunk_curr->line_count;
if (iter->hunk_head)
return (int)iter->hunk_head->line_count;
return 0;
} }
int git_diff_iterator_next_file( int git_diff_patch_get_hunk(
git_diff_delta **delta_ptr, const git_diff_range **range,
git_diff_iterator *iter)
{
int error = 0;
assert(iter);
iter->file_index = iter->next_index;
diffiter_do_unload_file(iter);
while (!error) {
iter->ctxt.delta = git_vector_get(&iter->diff->deltas, iter->file_index);
if (!iter->ctxt.delta) {
error = GIT_ITEROVER;
break;
}
if (diff_delta_is_ambiguous(iter->ctxt.delta) &&
(error = diff_delta_load(&iter->ctxt)) < 0)
break;
if (!diff_delta_should_skip(iter->ctxt.opts, iter->ctxt.delta))
break;
iter->file_index++;
}
if (!error) {
iter->next_index = iter->file_index + 1;
error = diff_delta_prep(&iter->ctxt);
}
if (iter->ctxt.delta == NULL) {
iter->hunk_curr = iter->hunk_head = NULL;
iter->line_curr = NULL;
}
if (delta_ptr != NULL)
*delta_ptr = !error ? iter->ctxt.delta : NULL;
return error;
}
int git_diff_iterator_next_hunk(
git_diff_range **range_ptr,
const char **header, const char **header,
size_t *header_len, size_t *header_len,
git_diff_iterator *iter) size_t *lines_in_hunk,
git_diff_patch *patch,
size_t hunk_idx)
{ {
int error = diffiter_do_diff_file(iter); diff_patch_hunk *hunk;
git_diff_range *range;
if (error) assert(patch);
return error;
if (iter->hunk_curr == NULL) { if (hunk_idx >= patch->hunks_size) {
if (iter->hunk_head == NULL) if (range) *range = NULL;
goto no_more_hunks; if (header) *header = NULL;
iter->hunk_curr = iter->hunk_head; if (header_len) *header_len = 0;
} else { if (lines_in_hunk) *lines_in_hunk = 0;
if (iter->hunk_curr->next == NULL) return GIT_ENOTFOUND;
goto no_more_hunks;
iter->hunk_curr = iter->hunk_curr->next;
} }
range = &iter->hunk_curr->range; hunk = &patch->hunks[hunk_idx];
if (range_ptr)
*range_ptr = range;
if (header) {
int out = format_hunk_header(
iter->hunk_header, sizeof(iter->hunk_header), range);
/* TODO: append function name to header */ if (range) *range = &hunk->range;
if (header) *header = hunk->header;
if (header_len) *header_len = hunk->header_len;
if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
*(iter->hunk_header + out++) = '\n'; return 0;
}
*header = iter->hunk_header; int git_diff_patch_num_lines_in_hunk(
git_diff_patch *patch,
size_t hunk_idx)
{
assert(patch);
if (header_len) if (hunk_idx >= patch->hunks_size)
*header_len = (size_t)out; return GIT_ENOTFOUND;
} else
return patch->hunks[hunk_idx].line_count;
}
iter->line_curr = iter->hunk_curr->line_head; int git_diff_patch_get_line_in_hunk(
char *line_origin,
const char **content,
size_t *content_len,
int *old_lineno,
int *new_lineno,
git_diff_patch *patch,
size_t hunk_idx,
size_t line_of_hunk)
{
diff_patch_hunk *hunk;
diff_patch_line *line;
return error; assert(patch);
no_more_hunks: if (hunk_idx >= patch->hunks_size)
if (range_ptr) *range_ptr = NULL; goto notfound;
if (header) *header = NULL; hunk = &patch->hunks[hunk_idx];
if (header_len) *header_len = 0;
iter->line_curr = NULL;
return GIT_ITEROVER; if (line_of_hunk >= hunk->line_count)
} goto notfound;
int git_diff_iterator_next_line( line = &patch->lines[hunk->line_start + line_of_hunk];
char *line_origin, /**< GIT_DIFF_LINE_... value from above */
const char **content_ptr,
size_t *content_len,
git_diff_iterator *iter)
{
int error = diffiter_do_diff_file(iter);
if (error) if (line_origin) *line_origin = line->origin;
return error; if (content) *content = line->ptr;
if (content_len) *content_len = line->len;
if (old_lineno) *old_lineno = line->oldno;
if (new_lineno) *new_lineno = line->newno;
/* if the user has not called next_hunk yet, call it implicitly (OK?) */ return 0;
if (iter->hunk_curr == NULL) {
error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter);
if (error)
return error;
}
if (iter->line_curr == NULL) { notfound:
if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
if (content_ptr) *content_ptr = NULL; if (content) *content = NULL;
if (content_len) *content_len = 0; if (content_len) *content_len = 0;
return GIT_ITEROVER; if (old_lineno) *old_lineno = -1;
} if (new_lineno) *new_lineno = -1;
if (line_origin)
*line_origin = iter->line_curr->origin;
if (content_ptr)
*content_ptr = iter->line_curr->ptr;
if (content_len)
*content_len = iter->line_curr->len;
iter->line_curr = iter->line_curr->next;
return error; return GIT_ENOTFOUND;
} }
/*
* Copyright (C) 2009-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.
*/
#ifndef INCLUDE_diff_output_h__
#define INCLUDE_diff_output_h__
#include "git2/blob.h"
#include "diff.h"
#include "map.h"
#include "xdiff/xdiff.h"
#define MAX_DIFF_FILESIZE 0x20000000
enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_PREPPED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2),
GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
GIT_DIFF_PATCH_DIFFED = (1 << 4),
};
/* context for performing diffs */
typedef struct {
git_repository *repo;
git_diff_list *diff;
const git_diff_options *opts;
git_diff_file_fn file_cb;
git_diff_hunk_fn hunk_cb;
git_diff_data_fn data_cb;
void *cb_data;
int cb_error;
git_diff_range cb_range;
xdemitconf_t xdiff_config;
xpparam_t xdiff_params;
} diff_context;
/* cached information about a single span in a diff */
typedef struct diff_patch_line diff_patch_line;
struct diff_patch_line {
const char *ptr;
size_t len;
int lines, oldno, newno;
char origin;
};
/* cached information about a hunk in a diff */
typedef struct diff_patch_hunk diff_patch_hunk;
struct diff_patch_hunk {
git_diff_range range;
char header[128];
size_t header_len;
size_t line_start;
size_t line_count;
};
struct git_diff_patch {
git_refcount rc;
git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */
git_diff_delta *delta;
diff_context *ctxt; /* only valid while generating patch */
git_iterator_type_t old_src;
git_iterator_type_t new_src;
git_blob *old_blob;
git_blob *new_blob;
git_map old_data;
git_map new_data;
uint32_t flags;
diff_patch_hunk *hunks;
size_t hunks_asize, hunks_size;
diff_patch_line *lines;
size_t lines_asize, lines_size;
};
/* context for performing diff on a single delta */
typedef struct {
git_diff_patch *patch;
uint32_t prepped : 1;
uint32_t loaded : 1;
uint32_t diffable : 1;
uint32_t diffed : 1;
} diff_delta_context;
#endif
...@@ -1457,7 +1457,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) ...@@ -1457,7 +1457,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff); error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
if (!error) { if (!error) {
if (git_diff_entrycount(diff, -1) > 0) if (git_diff_num_deltas(diff) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
git_diff_list_free(diff); git_diff_list_free(diff);
...@@ -1474,12 +1474,13 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) ...@@ -1474,12 +1474,13 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
error = git_diff_workdir_to_index(sm_repo, &opt, &diff); error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
if (!error) { if (!error) {
int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED); int untracked =
git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
if (untracked > 0) if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
if (git_diff_entrycount(diff, -1) - untracked > 0) if ((git_diff_num_deltas(diff) - untracked) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff); git_diff_list_free(diff);
......
...@@ -23,7 +23,7 @@ git_tree *resolve_commit_oid_to_tree( ...@@ -23,7 +23,7 @@ git_tree *resolve_commit_oid_to_tree(
int diff_file_fn( int diff_file_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
float progress) float progress)
{ {
diff_expects *e = cb_data; diff_expects *e = cb_data;
...@@ -48,8 +48,8 @@ int diff_file_fn( ...@@ -48,8 +48,8 @@ int diff_file_fn(
int diff_hunk_fn( int diff_hunk_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
const char *header, const char *header,
size_t header_len) size_t header_len)
{ {
...@@ -67,8 +67,8 @@ int diff_hunk_fn( ...@@ -67,8 +67,8 @@ int diff_hunk_fn(
int diff_line_fn( int diff_line_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
char line_origin, char line_origin,
const char *content, const char *content,
size_t content_len) size_t content_len)
...@@ -112,64 +112,71 @@ int diff_foreach_via_iterator( ...@@ -112,64 +112,71 @@ int diff_foreach_via_iterator(
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn line_cb)
{ {
int error; size_t d, num_d = git_diff_num_deltas(diff);
git_diff_iterator *iter;
git_diff_delta *delta;
if ((error = git_diff_iterator_new(&iter, diff)) < 0) for (d = 0; d < num_d; ++d) {
return error; git_diff_patch *patch;
const git_diff_delta *delta;
size_t h, num_h;
while (!(error = git_diff_iterator_next_file(&delta, iter))) { cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
git_diff_range *range; cl_assert(delta);
const char *hdr;
size_t hdr_len;
float progress = git_diff_iterator_progress(iter);
/* call file_cb for this file */ /* call file_cb for this file */
if (file_cb != NULL && file_cb(data, delta, progress) != 0) if (file_cb != NULL && file_cb(data, delta, (float)d / num_d) != 0) {
git_diff_patch_free(patch);
goto abort; goto abort;
}
if (!hunk_cb && !line_cb) /* if there are no changes, then the patch will be NULL */
if (!patch) {
cl_assert(delta->status == GIT_DELTA_UNMODIFIED || delta->binary == 1);
continue; continue;
}
while (!(error = git_diff_iterator_next_hunk( if (!hunk_cb && !line_cb) {
&range, &hdr, &hdr_len, iter))) { git_diff_patch_free(patch);
char origin; continue;
const char *line; }
size_t line_len;
if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0) num_h = git_diff_patch_num_hunks(patch);
goto abort;
if (!line_cb) for (h = 0; h < num_h; h++) {
continue; const git_diff_range *range;
const char *hdr;
size_t hdr_len, l, num_l;
while (!(error = git_diff_iterator_next_line( cl_git_pass(git_diff_patch_get_hunk(
&origin, &line, &line_len, iter))) { &range, &hdr, &hdr_len, &num_l, patch, h));
if (line_cb(data, delta, range, origin, line, line_len) != 0) if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0) {
git_diff_patch_free(patch);
goto abort; goto abort;
} }
if (error && error != GIT_ITEROVER) for (l = 0; l < num_l; ++l) {
goto done; char origin;
} const char *line;
size_t line_len;
int old_lineno, new_lineno;
if (error && error != GIT_ITEROVER) cl_git_pass(git_diff_patch_get_line_in_hunk(
goto done; &origin, &line, &line_len, &old_lineno, &new_lineno,
} patch, h, l));
done: if (line_cb(data, delta, range, origin, line, line_len) != 0) {
git_diff_iterator_free(iter); git_diff_patch_free(patch);
goto abort;
}
}
}
if (error == GIT_ITEROVER) git_diff_patch_free(patch);
error = 0; }
return error; return 0;
abort: abort:
git_diff_iterator_free(iter);
giterr_clear(); giterr_clear();
return GIT_EUSER; return GIT_EUSER;
} }
...@@ -27,20 +27,20 @@ typedef struct { ...@@ -27,20 +27,20 @@ typedef struct {
extern int diff_file_fn( extern int diff_file_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
float progress); float progress);
extern int diff_hunk_fn( extern int diff_hunk_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
const char *header, const char *header,
size_t header_len); size_t header_len);
extern int diff_line_fn( extern int diff_line_fn(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
char line_origin, char line_origin,
const char *content, const char *content,
size_t content_len); size_t content_len);
......
...@@ -14,11 +14,16 @@ void test_diff_diffiter__create(void) ...@@ -14,11 +14,16 @@ void test_diff_diffiter__create(void)
{ {
git_repository *repo = cl_git_sandbox_init("attr"); git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff; git_diff_list *diff;
git_diff_iterator *iter; size_t d, num_d;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff));
git_diff_iterator_free(iter); num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta;
cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
}
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -26,24 +31,22 @@ void test_diff_diffiter__iterate_files(void) ...@@ -26,24 +31,22 @@ void test_diff_diffiter__iterate_files(void)
{ {
git_repository *repo = cl_git_sandbox_init("attr"); git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff; git_diff_list *diff;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int count = 0;
int error, count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff));
while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(0, error); cl_assert_equal_i(6, num_d);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta;
cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
cl_assert(delta != NULL); cl_assert(delta != NULL);
count++; count++;
} }
cl_assert_equal_i(GIT_ITEROVER, error);
cl_assert(delta == NULL);
cl_assert_equal_i(6, count); cl_assert_equal_i(6, count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -51,24 +54,22 @@ void test_diff_diffiter__iterate_files_2(void) ...@@ -51,24 +54,22 @@ void test_diff_diffiter__iterate_files_2(void)
{ {
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_list *diff; git_diff_list *diff;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int count = 0;
int error, count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff));
while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(0, error); cl_assert_equal_i(8, num_d);
for (d = 0; d < num_d; ++d) {
const git_diff_delta *delta;
cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
cl_assert(delta != NULL); cl_assert(delta != NULL);
count++; count++;
} }
cl_assert_equal_i(GIT_ITEROVER, error);
cl_assert(delta == NULL);
cl_assert_equal_i(8, count); cl_assert_equal_i(8, count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -77,12 +78,8 @@ void test_diff_diffiter__iterate_files_and_hunks(void) ...@@ -77,12 +78,8 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int file_count = 0, hunk_count = 0;
git_diff_range *range;
const char *header;
size_t header_len;
int error, file_count = 0, hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
...@@ -90,28 +87,42 @@ void test_diff_diffiter__iterate_files_and_hunks(void) ...@@ -90,28 +87,42 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff)); num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_diff_patch *patch;
const git_diff_delta *delta;
size_t h, num_h;
cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
cl_assert_equal_i(0, error);
cl_assert(delta); cl_assert(delta);
cl_assert(patch);
file_count++; file_count++;
while ((error = git_diff_iterator_next_hunk( num_h = git_diff_patch_num_hunks(patch);
&range, &header, &header_len, iter)) != GIT_ITEROVER) {
cl_assert_equal_i(0, error); for (h = 0; h < num_h; h++) {
const git_diff_range *range;
const char *header;
size_t header_len, num_l;
cl_git_pass(git_diff_patch_get_hunk(
&range, &header, &header_len, &num_l, patch, h));
cl_assert(range); cl_assert(range);
cl_assert(header);
hunk_count++; hunk_count++;
} }
git_diff_patch_free(patch);
} }
cl_assert_equal_i(GIT_ITEROVER, error);
cl_assert(delta == NULL);
cl_assert_equal_i(13, file_count); cl_assert_equal_i(13, file_count);
cl_assert_equal_i(8, hunk_count); cl_assert_equal_i(8, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -120,45 +131,42 @@ void test_diff_diffiter__max_size_threshold(void) ...@@ -120,45 +131,42 @@ void test_diff_diffiter__max_size_threshold(void)
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter; int file_count = 0, binary_count = 0, hunk_count = 0;
git_diff_delta *delta; size_t d, num_d;
int error, file_count = 0, binary_count = 0, hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff)); num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_diff_patch *patch;
const git_diff_delta *delta;
while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
cl_assert_equal_i(0, error);
cl_assert(delta); cl_assert(delta);
cl_assert(patch);
file_count++; file_count++;
hunk_count += git_diff_patch_num_hunks(patch);
hunk_count += git_diff_iterator_num_hunks_in_file(iter);
assert(delta->binary == 0 || delta->binary == 1); assert(delta->binary == 0 || delta->binary == 1);
binary_count += delta->binary; binary_count += delta->binary;
}
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert(delta == NULL); }
cl_assert_equal_i(13, file_count); cl_assert_equal_i(13, file_count);
cl_assert_equal_i(0, binary_count); cl_assert_equal_i(0, binary_count);
cl_assert_equal_i(8, hunk_count); cl_assert_equal_i(8, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
/* try again with low file size threshold */ /* try again with low file size threshold */
file_count = 0; file_count = binary_count = hunk_count = 0;
binary_count = 0;
hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
...@@ -166,36 +174,171 @@ void test_diff_diffiter__max_size_threshold(void) ...@@ -166,36 +174,171 @@ void test_diff_diffiter__max_size_threshold(void)
opts.max_size = 50; /* treat anything over 50 bytes as binary! */ opts.max_size = 50; /* treat anything over 50 bytes as binary! */
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff)); num_d = git_diff_num_deltas(diff);
while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { for (d = 0; d < num_d; ++d) {
cl_assert_equal_i(0, error); git_diff_patch *patch;
cl_assert(delta); const git_diff_delta *delta;
file_count++; cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
hunk_count += git_diff_iterator_num_hunks_in_file(iter); file_count++;
hunk_count += git_diff_patch_num_hunks(patch);
assert(delta->binary == 0 || delta->binary == 1); assert(delta->binary == 0 || delta->binary == 1);
binary_count += delta->binary; binary_count += delta->binary;
}
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert(delta == NULL); }
cl_assert_equal_i(13, file_count); cl_assert_equal_i(13, file_count);
/* Three files are over the 50 byte threshold: /* Three files are over the 50 byte threshold:
* - staged_changes_file_deleted * - staged_changes_file_deleted
* - staged_changes_modified_file * - staged_changes_modified_file
* - staged_new_file_modified_file * - staged_new_file_modified_file
*/ */
cl_assert_equal_i(3, binary_count); cl_assert_equal_i(3, binary_count);
cl_assert_equal_i(5, hunk_count); cl_assert_equal_i(5, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
}
void test_diff_diffiter__iterate_all(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0};
git_diff_list *diff = NULL;
diff_expects exp = {0};
size_t d, num_d;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
git_diff_patch *patch;
const git_diff_delta *delta;
size_t h, num_h;
cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
cl_assert(patch && delta);
exp.files++;
num_h = git_diff_patch_num_hunks(patch);
for (h = 0; h < num_h; h++) {
const git_diff_range *range;
const char *header;
size_t header_len, l, num_l;
cl_git_pass(git_diff_patch_get_hunk(
&range, &header, &header_len, &num_l, patch, h));
cl_assert(range && header);
exp.hunks++;
for (l = 0; l < num_l; ++l) {
char origin;
const char *content;
size_t content_len;
cl_git_pass(git_diff_patch_get_line_in_hunk(
&origin, &content, &content_len, NULL, NULL, patch, h, l));
cl_assert(content);
exp.lines++;
}
}
git_diff_patch_free(patch);
}
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
cl_assert_equal_i(14, exp.lines);
git_diff_list_free(diff);
}
static void iterate_over_patch(git_diff_patch *patch, diff_expects *exp)
{
size_t h, num_h = git_diff_patch_num_hunks(patch), num_l;
exp->files++;
exp->hunks += num_h;
/* let's iterate in reverse, just because we can! */
for (h = 1, num_l = 0; h <= num_h; ++h)
num_l += git_diff_patch_num_lines_in_hunk(patch, num_h - h);
exp->lines += num_l;
}
#define PATCH_CACHE 5
void test_diff_diffiter__iterate_randomly_while_saving_state(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0};
git_diff_list *diff = NULL;
diff_expects exp = {0};
git_diff_patch *patches[PATCH_CACHE];
size_t p, d, num_d;
memset(patches, 0, sizeof(patches));
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
num_d = git_diff_num_deltas(diff);
/* To make sure that references counts work for diff and patch objects,
* this generates patches and randomly caches them. Only when the patch
* is removed from the cache are hunks and lines counted. At the end,
* there are still patches in the cache, so free the diff and try to
* process remaining patches after the diff is freed.
*/
srand(121212);
p = rand() % PATCH_CACHE;
for (d = 0; d < num_d; ++d) {
/* take old patch */
git_diff_patch *patch = patches[p];
patches[p] = NULL;
/* cache new patch */
cl_git_pass(git_diff_get_patch(&patches[p], NULL, diff, d));
cl_assert(patches[p] != NULL);
/* process old patch if non-NULL */
if (patch != NULL) {
iterate_over_patch(patch, &exp);
git_diff_patch_free(patch);
}
p = rand() % PATCH_CACHE;
}
/* free diff list now - refcounts should keep things safe */
git_diff_list_free(diff);
/* process remaining unprocessed patches */
for (p = 0; p < PATCH_CACHE; p++) {
git_diff_patch *patch = patches[p];
if (patch != NULL) {
iterate_over_patch(patch, &exp);
git_diff_patch_free(patch);
}
}
/* hopefully it all still added up right */
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
cl_assert_equal_i(14, exp.lines);
} }
...@@ -93,7 +93,7 @@ void test_diff_index__0(void) ...@@ -93,7 +93,7 @@ void test_diff_index__0(void)
static int diff_stop_after_2_files( static int diff_stop_after_2_files(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
float progress) float progress)
{ {
diff_expects *e = cb_data; diff_expects *e = cb_data;
......
...@@ -23,8 +23,8 @@ void test_diff_patch__cleanup(void) ...@@ -23,8 +23,8 @@ void test_diff_patch__cleanup(void)
static int check_removal_cb( static int check_removal_cb(
void *cb_data, void *cb_data,
git_diff_delta *delta, const git_diff_delta *delta,
git_diff_range *range, const git_diff_range *range,
char line_origin, char line_origin,
const char *formatted_output, const char *formatted_output,
size_t output_len) size_t output_len)
......
...@@ -264,10 +264,12 @@ void test_diff_tree__larger_hunks(void) ...@@ -264,10 +264,12 @@ void test_diff_tree__larger_hunks(void)
git_tree *a, *b; git_tree *a, *b;
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter = NULL; size_t d, num_d, h, num_h, l, num_l, header_len, line_len;
git_diff_delta *delta; const git_diff_delta *delta;
diff_expects exp; git_diff_patch *patch;
int error, num_files = 0; const git_diff_range *range;
const char *header, *line;
char origin;
g_repo = cl_git_sandbox_init("diff"); g_repo = cl_git_sandbox_init("diff");
...@@ -277,61 +279,38 @@ void test_diff_tree__larger_hunks(void) ...@@ -277,61 +279,38 @@ void test_diff_tree__larger_hunks(void)
opts.context_lines = 1; opts.context_lines = 1;
opts.interhunk_lines = 0; opts.interhunk_lines = 0;
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff));
/* this should be exact */
cl_assert(git_diff_iterator_progress(iter) == 0.0f);
/* You wouldn't actually structure an iterator loop this way, but num_d = git_diff_num_deltas(diff);
* I have here for testing purposes of the return value for (d = 0; d < num_d; ++d) {
*/ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
while (!(error = git_diff_iterator_next_file(&delta, iter))) { cl_assert(patch && delta);
git_diff_range *range;
const char *header;
size_t header_len;
int actual_hunks = 0, num_hunks;
float expected_progress;
num_files++; num_h = git_diff_patch_num_hunks(patch);
for (h = 0; h < num_h; h++) {
cl_git_pass(git_diff_patch_get_hunk(
&range, &header, &header_len, &num_l, patch, h));
expected_progress = (float)num_files / 2.0f; for (l = 0; l < num_l; ++l) {
cl_assert(expected_progress == git_diff_iterator_progress(iter)); cl_git_pass(git_diff_patch_get_line_in_hunk(
&origin, &line, &line_len, NULL, NULL, patch, h, l));
num_hunks = git_diff_iterator_num_hunks_in_file(iter); cl_assert(line);
}
while (!(error = git_diff_iterator_next_hunk(
&range, &header, &header_len, iter)))
{
int actual_lines = 0;
int num_lines = git_diff_iterator_num_lines_in_hunk(iter);
char origin;
const char *line;
size_t line_len;
while (!(error = git_diff_iterator_next_line( cl_git_fail(git_diff_patch_get_line_in_hunk(
&origin, &line, &line_len, iter))) &origin, &line, &line_len, NULL, NULL, patch, h, num_l));
{
actual_lines++;
} }
cl_assert_equal_i(GIT_ITEROVER, error); cl_git_fail(git_diff_patch_get_hunk(
cl_assert_equal_i(actual_lines, num_lines); &range, &header, &header_len, &num_l, patch, num_h));
actual_hunks++; git_diff_patch_free(patch);
} }
cl_assert_equal_i(GIT_ITEROVER, error); cl_git_fail(git_diff_get_patch(&patch, &delta, diff, num_d));
cl_assert_equal_i(actual_hunks, num_hunks);
}
cl_assert_equal_i(GIT_ITEROVER, error); cl_assert_equal_i(2, num_d);
cl_assert_equal_i(2, num_files);
cl_assert(git_diff_iterator_progress(iter) == 1.0f);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
diff = NULL; diff = NULL;
......
...@@ -678,7 +678,7 @@ void test_diff_workdir__larger_hunks(void) ...@@ -678,7 +678,7 @@ void test_diff_workdir__larger_hunks(void)
const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
git_tree *a, *b; git_tree *a, *b;
git_diff_options opts = {0}; git_diff_options opts = {0};
int i, error; size_t i, d, num_d, h, num_h, l, num_l, header_len, line_len;
g_repo = cl_git_sandbox_init("diff"); g_repo = cl_git_sandbox_init("diff");
...@@ -690,9 +690,10 @@ void test_diff_workdir__larger_hunks(void) ...@@ -690,9 +690,10 @@ void test_diff_workdir__larger_hunks(void)
for (i = 0; i <= 2; ++i) { for (i = 0; i <= 2; ++i) {
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter = NULL; git_diff_patch *patch;
git_diff_delta *delta; const git_diff_range *range;
int num_files = 0; const char *header, *line;
char origin;
/* okay, this is a bit silly, but oh well */ /* okay, this is a bit silly, but oh well */
switch (i) { switch (i) {
...@@ -707,54 +708,36 @@ void test_diff_workdir__larger_hunks(void) ...@@ -707,54 +708,36 @@ void test_diff_workdir__larger_hunks(void)
break; break;
} }
cl_git_pass(git_diff_iterator_new(&iter, diff)); num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(2, (int)num_d);
cl_assert(git_diff_iterator_progress(iter) == 0.0f);
while (!(error = git_diff_iterator_next_file(&delta, iter))) {
git_diff_range *range;
const char *header;
size_t header_len;
int actual_hunks = 0, num_hunks;
float expected_progress;
num_files++; for (d = 0; d < num_d; ++d) {
cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d));
cl_assert(patch);
expected_progress = (float)num_files / 2.0f; num_h = git_diff_patch_num_hunks(patch);
cl_assert(expected_progress == git_diff_iterator_progress(iter)); for (h = 0; h < num_h; h++) {
cl_git_pass(git_diff_patch_get_hunk(
&range, &header, &header_len, &num_l, patch, h));
num_hunks = git_diff_iterator_num_hunks_in_file(iter); for (l = 0; l < num_l; ++l) {
cl_git_pass(git_diff_patch_get_line_in_hunk(
while (!(error = git_diff_iterator_next_hunk( &origin, &line, &line_len, NULL, NULL, patch, h, l));
&range, &header, &header_len, iter))) cl_assert(line);
{
int actual_lines = 0;
int num_lines = git_diff_iterator_num_lines_in_hunk(iter);
char origin;
const char *line;
size_t line_len;
while (!(error = git_diff_iterator_next_line(
&origin, &line, &line_len, iter)))
{
actual_lines++;
} }
cl_assert_equal_i(GIT_ITEROVER, error); /* confirm fail after the last item */
cl_assert_equal_i(actual_lines, num_lines); cl_git_fail(git_diff_patch_get_line_in_hunk(
&origin, &line, &line_len, NULL, NULL, patch, h, num_l));
actual_hunks++;
} }
cl_assert_equal_i(GIT_ITEROVER, error); /* confirm fail after the last item */
cl_assert_equal_i(actual_hunks, num_hunks); cl_git_fail(git_diff_patch_get_hunk(
} &range, &header, &header_len, &num_l, patch, num_h));
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert_equal_i(2, num_files); }
cl_assert(git_diff_iterator_progress(iter) == 1.0f);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
......
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