Commit 30caf0cf by Vicent Martí

Merge pull request #1595 from arrbee/even-more-rename-fixes

Even more rename detection fixes
parents 87a56fe0 49f70f2c
...@@ -84,6 +84,10 @@ static int check_uint16_param(const char *arg, const char *pattern, uint16_t *va ...@@ -84,6 +84,10 @@ static int check_uint16_param(const char *arg, const char *pattern, uint16_t *va
char *endptr = NULL; char *endptr = NULL;
if (strncmp(arg, pattern, len)) if (strncmp(arg, pattern, len))
return 0; return 0;
if (arg[len] == '\0' && pattern[len - 1] != '=')
return 1;
if (arg[len] == '=')
len++;
strval = strtoul(arg + len, &endptr, 0); strval = strtoul(arg + len, &endptr, 0);
if (endptr == arg) if (endptr == arg)
return 0; return 0;
...@@ -110,13 +114,20 @@ static void usage(const char *message, const char *arg) ...@@ -110,13 +114,20 @@ static void usage(const char *message, const char *arg)
exit(1); exit(1);
} }
enum {
FORMAT_PATCH = 0,
FORMAT_COMPACT = 1,
FORMAT_RAW = 2
};
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
git_repository *repo = NULL; git_repository *repo = NULL;
git_tree *t1 = NULL, *t2 = NULL; git_tree *t1 = NULL, *t2 = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
git_diff_list *diff; git_diff_list *diff;
int i, color = -1, compact = 0, cached = 0; int i, color = -1, format = FORMAT_PATCH, cached = 0;
char *a, *treeish1 = NULL, *treeish2 = NULL; char *a, *treeish1 = NULL, *treeish2 = NULL;
const char *dir = "."; const char *dir = ".";
...@@ -137,11 +148,13 @@ int main(int argc, char *argv[]) ...@@ -137,11 +148,13 @@ int main(int argc, char *argv[])
} }
else if (!strcmp(a, "-p") || !strcmp(a, "-u") || else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
!strcmp(a, "--patch")) !strcmp(a, "--patch"))
compact = 0; format = FORMAT_PATCH;
else if (!strcmp(a, "--cached")) else if (!strcmp(a, "--cached"))
cached = 1; cached = 1;
else if (!strcmp(a, "--name-status")) else if (!strcmp(a, "--name-status"))
compact = 1; format = FORMAT_COMPACT;
else if (!strcmp(a, "--raw"))
format = FORMAT_RAW;
else if (!strcmp(a, "--color")) else if (!strcmp(a, "--color"))
color = 0; color = 0;
else if (!strcmp(a, "--no-color")) else if (!strcmp(a, "--no-color"))
...@@ -160,6 +173,20 @@ int main(int argc, char *argv[]) ...@@ -160,6 +173,20 @@ int main(int argc, char *argv[])
opts.flags |= GIT_DIFF_INCLUDE_IGNORED; opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
else if (!strcmp(a, "--untracked")) else if (!strcmp(a, "--untracked"))
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
else if (check_uint16_param(a, "-M", &findopts.rename_threshold) ||
check_uint16_param(a, "--find-renames",
&findopts.rename_threshold))
findopts.flags |= GIT_DIFF_FIND_RENAMES;
else if (check_uint16_param(a, "-C", &findopts.copy_threshold) ||
check_uint16_param(a, "--find-copies",
&findopts.copy_threshold))
findopts.flags |= GIT_DIFF_FIND_COPIES;
else if (!strcmp(a, "--find-copies-harder"))
findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) {
/* TODO: parse thresholds */
findopts.flags |= GIT_DIFF_FIND_REWRITES;
}
else if (!check_uint16_param(a, "-U", &opts.context_lines) && else if (!check_uint16_param(a, "-U", &opts.context_lines) &&
!check_uint16_param(a, "--unified=", &opts.context_lines) && !check_uint16_param(a, "--unified=", &opts.context_lines) &&
!check_uint16_param(a, "--inter-hunk-context=", !check_uint16_param(a, "--inter-hunk-context=",
...@@ -204,13 +231,24 @@ int main(int argc, char *argv[]) ...@@ -204,13 +231,24 @@ int main(int argc, char *argv[])
else else
check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff"); check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff");
if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0)
check(git_diff_find_similar(diff, &findopts),
"finding renames and copies ");
if (color >= 0) if (color >= 0)
fputs(colors[0], stdout); fputs(colors[0], stdout);
if (compact) switch (format) {
check(git_diff_print_compact(diff, printer, &color), "Displaying diff"); case FORMAT_PATCH:
else
check(git_diff_print_patch(diff, printer, &color), "Displaying diff"); check(git_diff_print_patch(diff, printer, &color), "Displaying diff");
break;
case FORMAT_COMPACT:
check(git_diff_print_compact(diff, printer, &color), "Displaying diff");
break;
case FORMAT_RAW:
check(git_diff_print_raw(diff, printer, &color), "Displaying diff");
break;
}
if (color >= 0) if (color >= 0)
fputs(colors[0], stdout); fputs(colors[0], stdout);
......
...@@ -243,6 +243,19 @@ typedef struct { ...@@ -243,6 +243,19 @@ typedef struct {
* `NOT_BINARY` flag set to avoid examining file contents if you do not pass * `NOT_BINARY` flag set to avoid examining file contents if you do not pass
* in hunk and/or line callbacks to the diff foreach iteration function. It * in hunk and/or line callbacks to the diff foreach iteration function. It
* will just use the git attributes for those files. * will just use the git attributes for those files.
*
* The similarity score is zero unless you call `git_diff_find_similar()`
* which does a similarity analysis of files in the diff. Use that
* function to do rename and copy detection, and to split heavily modified
* files in add/delete pairs. After that call, deltas with a status of
* GIT_DELTA_RENAMED or GIT_DELTA_COPIED will have a similarity score
* between 0 and 100 indicating how similar the old and new sides are.
*
* If you ask `git_diff_find_similar` to find heavily modified files to
* break, but to not *actually* break the records, then GIT_DELTA_MODIFIED
* records may have a non-zero similarity score if the self-similarity is
* below the split threshold. To display this value like core Git, invert
* the score (a la `printf("M%03d", 100 - delta->similarity)`).
*/ */
typedef struct { typedef struct {
git_diff_file old_file; git_diff_file old_file;
...@@ -408,18 +421,28 @@ typedef enum { ...@@ -408,18 +421,28 @@ typedef enum {
/** consider unmodified as copy sources? (`--find-copies-harder`) */ /** consider unmodified as copy sources? (`--find-copies-harder`) */
GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3), GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3),
/** split large rewrites into delete/add pairs (`--break-rewrites=/M`) */ /** mark large rewrites for split (`--break-rewrites=/M`) */
GIT_DIFF_FIND_AND_BREAK_REWRITES = (1 << 4), GIT_DIFF_FIND_REWRITES = (1 << 4),
/** actually split large rewrites into delete/add pairs */
GIT_DIFF_BREAK_REWRITES = (1 << 5),
/** mark rewrites for split and break into delete/add pairs */
GIT_DIFF_FIND_AND_BREAK_REWRITES =
(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES),
/** find renames/copies for untracked items in working directory */
GIT_DIFF_FIND_FOR_UNTRACKED = (1 << 6),
/** turn on all finding features */ /** turn on all finding features */
GIT_DIFF_FIND_ALL = (0x1f), GIT_DIFF_FIND_ALL = (0x0ff),
/** measure similarity ignoring leading whitespace (default) */ /** measure similarity ignoring leading whitespace (default) */
GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0, GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,
/** measure similarity ignoring all whitespace */ /** measure similarity ignoring all whitespace */
GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 6), GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 12),
/** measure similarity including all data */ /** measure similarity including all data */
GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 7), GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13),
/** measure similarity only by comparing SHAs (fast and cheap) */
GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14),
} git_diff_find_t; } git_diff_find_t;
/** /**
...@@ -446,7 +469,10 @@ typedef struct { ...@@ -446,7 +469,10 @@ typedef struct {
* - `copy_threshold` is the same as the -C option with a value * - `copy_threshold` is the same as the -C option with a value
* - `rename_from_rewrite_threshold` matches the top of the -B option * - `rename_from_rewrite_threshold` matches the top of the -B option
* - `break_rewrite_threshold` matches the bottom of the -B option * - `break_rewrite_threshold` matches the bottom of the -B option
* - `target_limit` matches the -l option * - `rename_limit` is the maximum number of matches to consider for
* a particular file. This is a little different from the `-l` option
* to regular Git because we will still process up to this many matches
* before abandoning the search.
* *
* The `metric` option allows you to plug in a custom similarity metric. * The `metric` option allows you to plug in a custom similarity metric.
* Set it to NULL for the default internal metric which is based on sampling * Set it to NULL for the default internal metric which is based on sampling
...@@ -458,21 +484,21 @@ typedef struct { ...@@ -458,21 +484,21 @@ typedef struct {
unsigned int version; unsigned int version;
/** Combination of git_diff_find_t values (default FIND_RENAMES) */ /** Combination of git_diff_find_t values (default FIND_RENAMES) */
unsigned int flags; uint32_t flags;
/** Similarity to consider a file renamed (default 50) */ /** Similarity to consider a file renamed (default 50) */
unsigned int rename_threshold; uint16_t rename_threshold;
/** Similarity of modified to be eligible rename source (default 50) */ /** Similarity of modified to be eligible rename source (default 50) */
unsigned int rename_from_rewrite_threshold; uint16_t rename_from_rewrite_threshold;
/** Similarity to consider a file a copy (default 50) */ /** Similarity to consider a file a copy (default 50) */
unsigned int copy_threshold; uint16_t copy_threshold;
/** Similarity to split modify into delete/add pair (default 60) */ /** Similarity to split modify into delete/add pair (default 60) */
unsigned int break_rewrite_threshold; uint16_t break_rewrite_threshold;
/** Maximum similarity sources to examine (a la diff's `-l` option or /** Maximum similarity sources to examine for a file (somewhat like
* the `diff.renameLimit` config) (default 200) * git-diff's `-l` option or `diff.renameLimit` config) (default 200)
*/ */
unsigned int target_limit; size_t rename_limit;
/** Pluggable similarity metric; pass NULL to use internal metric */ /** Pluggable similarity metric; pass NULL to use internal metric */
git_diff_similarity_metric *metric; git_diff_similarity_metric *metric;
...@@ -689,6 +715,22 @@ GIT_EXTERN(int) git_diff_print_compact( ...@@ -689,6 +715,22 @@ GIT_EXTERN(int) git_diff_print_compact(
void *payload); void *payload);
/** /**
* Iterate over a diff generating text output like "git diff --raw".
*
* 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 print_cb Callback to make per line of diff text.
* @param payload Reference pointer that will be passed to your callback.
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_diff_print_raw(
git_diff_list *diff,
git_diff_data_cb print_cb,
void *payload);
/**
* Look up the single character abbreviation for a delta status code. * Look up the single character abbreviation for a delta status code.
* *
* When you call `git_diff_print_compact` it prints single letter codes into * When you call `git_diff_print_compact` it prints single letter codes into
......
...@@ -90,6 +90,17 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw); ...@@ -90,6 +90,17 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw);
GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
/** /**
* Format a git_oid into a partial hex string.
*
* @param out output hex string; you say how many bytes to write.
* If the number of bytes is > GIT_OID_HEXSZ, extra bytes
* will be zeroed; if not, a '\0' terminator is NOT added.
* @param n number of characters to write into out string
* @param oid oid structure to format.
*/
GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id);
/**
* Format a git_oid into a loose-object path string. * Format a git_oid into a loose-object path string.
* *
* The resulting string is "aa/...", where "aa" is the first two * The resulting string is "aa/...", where "aa" is the first two
...@@ -117,10 +128,12 @@ GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *id); ...@@ -117,10 +128,12 @@ GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *id);
* Format a git_oid into a buffer as a hex format c-string. * Format a git_oid into a buffer as a hex format c-string.
* *
* If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting * If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting
* oid c-string will be truncated to n-1 characters. If there are * oid c-string will be truncated to n-1 characters (but will still be
* any input parameter errors (out == NULL, n == 0, oid == NULL), * NUL-byte terminated).
* then a pointer to an empty string is returned, so that the return *
* value can always be printed. * If there are any input parameter errors (out == NULL, n == 0, oid ==
* NULL), then a pointer to an empty string is returned, so that the
* return value can always be printed.
* *
* @param out the buffer into which the oid string is output. * @param out the buffer into which the oid string is output.
* @param n the size of the out buffer. * @param n the size of the out buffer.
......
...@@ -676,33 +676,26 @@ static int buffer_to_file( ...@@ -676,33 +676,26 @@ static int buffer_to_file(
int file_open_flags, int file_open_flags,
mode_t file_mode) mode_t file_mode)
{ {
int fd, error; int error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
return error; return error;
if ((fd = p_open(path, file_open_flags, file_mode)) < 0) { if ((error = git_futils_writebuffer(
giterr_set(GITERR_OS, "Could not open '%s' for writing", path); buffer, path, file_open_flags, file_mode)) < 0)
return fd; return error;
}
if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
giterr_set(GITERR_OS, "Could not write to '%s'", path);
(void)p_close(fd);
} else {
if ((error = p_close(fd)) < 0)
giterr_set(GITERR_OS, "Error while closing '%s'", path);
if ((error = p_stat(path, st)) < 0) if (st != NULL && (error = p_stat(path, st)) < 0) {
giterr_set(GITERR_OS, "Error while statting '%s'", path); giterr_set(GITERR_OS, "Error while statting '%s'", path);
return error;
} }
if (!error && if ((file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) {
(file_mode & 0100) != 0 &&
(error = p_chmod(path, file_mode)) < 0)
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
return error;
}
return error; return 0;
} }
static int blob_content_to_file( static int blob_content_to_file(
......
...@@ -231,10 +231,23 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix) ...@@ -231,10 +231,23 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
return git_pool_strndup(pool, prefix, len + 1); return git_pool_strndup(pool, prefix, len + 1);
} }
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
const char *str = delta->old_file.path;
if (!str ||
delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_COPIED)
str = delta->new_file.path;
return str;
}
int git_diff_delta__cmp(const void *a, const void *b) int git_diff_delta__cmp(const void *a, const void *b)
{ {
const git_diff_delta *da = a, *db = b; const git_diff_delta *da = a, *db = b;
int val = strcmp(da->old_file.path, db->old_file.path); int val = strcmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status); return val ? val : ((int)da->status - (int)db->status);
} }
......
...@@ -34,10 +34,18 @@ enum { ...@@ -34,10 +34,18 @@ enum {
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
GIT_DIFF_FLAG__TO_DELETE = (1 << 11), /* delete entry during rename det. */
GIT_DIFF_FLAG__TO_SPLIT = (1 << 12), /* split entry during rename det. */ GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
}; };
#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
#define GIT_DIFF__VERBOSE (1 << 30)
struct git_diff_list { struct git_diff_list {
git_refcount rc; git_refcount rc;
git_repository *repo; git_repository *repo;
......
...@@ -236,9 +236,8 @@ static int get_blob_content( ...@@ -236,9 +236,8 @@ static int get_blob_content(
char oidstr[GIT_OID_HEXSZ+1]; char oidstr[GIT_OID_HEXSZ+1];
git_buf content = GIT_BUF_INIT; git_buf content = GIT_BUF_INIT;
git_oid_fmt(oidstr, &file->oid); git_oid_tostr(oidstr, sizeof(oidstr), &file->oid);
oidstr[GIT_OID_HEXSZ] = 0; git_buf_printf(&content, "Subproject commit %s\n", oidstr);
git_buf_printf(&content, "Subproject commit %s\n", oidstr );
map->data = git_buf_detach(&content); map->data = git_buf_detach(&content);
map->len = strlen(map->data); map->len = strlen(map->data);
...@@ -318,14 +317,13 @@ static int get_workdir_sm_content( ...@@ -318,14 +317,13 @@ static int get_workdir_sm_content(
} }
} }
git_oid_fmt(oidstr, &file->oid); git_oid_tostr(oidstr, sizeof(oidstr), &file->oid);
oidstr[GIT_OID_HEXSZ] = '\0';
if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
sm_status_text = "-dirty"; sm_status_text = "-dirty";
git_buf_printf(&content, "Subproject commit %s%s\n", git_buf_printf(
oidstr, sm_status_text); &content, "Subproject commit %s%s\n", oidstr, sm_status_text);
map->data = git_buf_detach(&content); map->data = git_buf_detach(&content);
map->len = strlen(map->data); map->len = strlen(map->data);
...@@ -1021,8 +1019,33 @@ typedef struct { ...@@ -1021,8 +1019,33 @@ typedef struct {
git_diff_data_cb print_cb; git_diff_data_cb print_cb;
void *payload; void *payload;
git_buf *buf; git_buf *buf;
int oid_strlen;
} diff_print_info; } diff_print_info;
static int diff_print_info_init(
diff_print_info *pi,
git_buf *out, git_diff_list *diff, git_diff_data_cb cb, void *payload)
{
assert(diff && diff->repo);
pi->diff = diff;
pi->print_cb = cb;
pi->payload = payload;
pi->buf = out;
if (git_repository__cvar(&pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
return -1;
pi->oid_strlen += 1; /* for NUL byte */
if (pi->oid_strlen < 2)
pi->oid_strlen = 2;
else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
pi->oid_strlen = GIT_OID_HEXSZ + 1;
return 0;
}
static char pick_suffix(int mode) static char pick_suffix(int mode)
{ {
if (S_ISDIR(mode)) if (S_ISDIR(mode))
...@@ -1106,34 +1129,79 @@ int git_diff_print_compact( ...@@ -1106,34 +1129,79 @@ int git_diff_print_compact(
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
diff_print_info pi; diff_print_info pi;
pi.diff = diff; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
pi.print_cb = print_cb; error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
pi.payload = payload;
pi.buf = &buf;
error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);
git_buf_free(&buf); git_buf_free(&buf);
return error; return error;
} }
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) static int print_raw(
const git_diff_delta *delta, float progress, void *data)
{ {
int abbrevlen; diff_print_info *pi = data;
char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
if (git_repository__cvar(&abbrevlen, pi->diff->repo, GIT_CVAR_ABBREV) < 0) GIT_UNUSED(progress);
if (code == ' ')
return 0;
git_buf_clear(pi->buf);
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
git_buf_printf(
pi->buf, ":%06o %06o %s... %s... %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
if (delta->similarity > 0)
git_buf_printf(pi->buf, "%03u", delta->similarity);
if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED)
git_buf_printf(
pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
else
git_buf_printf(
pi->buf, "\t%s\n", delta->old_file.path ?
delta->old_file.path : delta->new_file.path);
if (git_buf_oom(pi->buf))
return -1; return -1;
abbrevlen += 1; /* for NUL byte */ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,
if (abbrevlen < 2) git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))
abbrevlen = 2; return callback_error();
else if (abbrevlen > (int)sizeof(start_oid))
abbrevlen = (int)sizeof(start_oid); return 0;
}
git_oid_tostr(start_oid, abbrevlen, &delta->old_file.oid); int git_diff_print_raw(
git_oid_tostr(end_oid, abbrevlen, &delta->new_file.oid); git_diff_list *diff,
git_diff_data_cb print_cb,
void *payload)
{
int error;
git_buf buf = GIT_BUF_INIT;
diff_print_info pi;
if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi);
git_buf_free(&buf);
return error;
}
static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)
{
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
/* TODO: Match git diff more closely */ /* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) { if (delta->old_file.mode == delta->new_file.mode) {
...@@ -1289,13 +1357,9 @@ int git_diff_print_patch( ...@@ -1289,13 +1357,9 @@ int git_diff_print_patch(
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
diff_print_info pi; diff_print_info pi;
pi.diff = diff; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload)))
pi.print_cb = print_cb; error = git_diff_foreach(
pi.payload = payload; diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
pi.buf = &buf;
error = git_diff_foreach(
diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);
git_buf_free(&buf); git_buf_free(&buf);
...@@ -1736,12 +1800,9 @@ int git_diff_patch_print( ...@@ -1736,12 +1800,9 @@ int git_diff_patch_print(
assert(patch && print_cb); assert(patch && print_cb);
pi.diff = patch->diff; if (!(error = diff_print_info_init(
pi.print_cb = print_cb; &pi, &temp, patch->diff, print_cb, payload)))
pi.payload = payload; error = print_patch_file(patch->delta, 0, &pi);
pi.buf = &temp;
error = print_patch_file(patch->delta, 0, &pi);
for (h = 0; h < patch->hunks_size && !error; ++h) { for (h = 0; h < patch->hunks_size && !error; ++h) {
diff_patch_hunk *hunk = &patch->hunks[h]; diff_patch_hunk *hunk = &patch->hunks[h];
......
...@@ -202,6 +202,32 @@ int git_futils_readbuffer(git_buf *buf, const char *path) ...@@ -202,6 +202,32 @@ int git_futils_readbuffer(git_buf *buf, const char *path)
return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
} }
int git_futils_writebuffer(
const git_buf *buf, const char *path, int flags, mode_t mode)
{
int fd, error = 0;
if (flags <= 0)
flags = O_CREAT | O_TRUNC | O_WRONLY;
if (!mode)
mode = GIT_FILEMODE_BLOB;
if ((fd = p_open(path, flags, mode)) < 0) {
giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
return fd;
}
if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
giterr_set(GITERR_OS, "Could not write to '%s'", path);
(void)p_close(fd);
}
if ((error = p_close(fd)) < 0)
giterr_set(GITERR_OS, "Error while closing '%s'", path);
return error;
}
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{ {
if (git_futils_mkpath2file(to, dirmode) < 0) if (git_futils_mkpath2file(to, dirmode) < 0)
......
...@@ -22,6 +22,9 @@ extern int git_futils_readbuffer_updated( ...@@ -22,6 +22,9 @@ extern int git_futils_readbuffer_updated(
git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated); git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
extern int git_futils_writebuffer(
const git_buf *buf, const char *path, int open_flags, mode_t mode);
/** /**
* File utils * File utils
* *
...@@ -223,6 +226,7 @@ extern git_off_t git_futils_filesize(git_file fd); ...@@ -223,6 +226,7 @@ extern git_off_t git_futils_filesize(git_file fd);
#define GIT_MODE_PERMS_MASK 0777 #define GIT_MODE_PERMS_MASK 0777
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) #define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) #define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
/** /**
* Convert a mode_t from the OS to a legal git mode_t value. * Convert a mode_t from the OS to a legal git mode_t value.
......
...@@ -68,12 +68,31 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val) ...@@ -68,12 +68,31 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
return str; return str;
} }
void git_oid_fmt(char *str, const git_oid *oid) void git_oid_nfmt(char *str, size_t n, const git_oid *oid)
{ {
size_t i; size_t i, max_i;
if (!oid) {
memset(str, 0, n);
return;
}
if (n > GIT_OID_HEXSZ) {
memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
n = GIT_OID_HEXSZ;
}
max_i = n / 2;
for (i = 0; i < sizeof(oid->id); i++) for (i = 0; i < max_i; i++)
str = fmt_one(str, oid->id[i]); str = fmt_one(str, oid->id[i]);
if (n & 1)
*str++ = to_hex[oid->id[i] >> 4];
}
void git_oid_fmt(char *str, const git_oid *oid)
{
git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
} }
void git_oid_pathfmt(char *str, const git_oid *oid) void git_oid_pathfmt(char *str, const git_oid *oid)
...@@ -91,31 +110,20 @@ char *git_oid_allocfmt(const git_oid *oid) ...@@ -91,31 +110,20 @@ char *git_oid_allocfmt(const git_oid *oid)
char *str = git__malloc(GIT_OID_HEXSZ + 1); char *str = git__malloc(GIT_OID_HEXSZ + 1);
if (!str) if (!str)
return NULL; return NULL;
git_oid_fmt(str, oid); git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
str[GIT_OID_HEXSZ] = '\0';
return str; return str;
} }
char *git_oid_tostr(char *out, size_t n, const git_oid *oid) char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
{ {
char str[GIT_OID_HEXSZ];
if (!out || n == 0) if (!out || n == 0)
return ""; return "";
n--; /* allow room for terminating NUL */ if (n > GIT_OID_HEXSZ + 1)
n = GIT_OID_HEXSZ + 1;
if (oid == NULL)
n = 0;
if (n > 0) {
git_oid_fmt(str, oid);
if (n > GIT_OID_HEXSZ)
n = GIT_OID_HEXSZ;
memcpy(out, str, n);
}
out[n] = '\0'; git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
out[n - 1] = '\0';
return out; return out;
} }
......
...@@ -109,6 +109,13 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) ...@@ -109,6 +109,13 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
return p == (git_off_t)r; return p == (git_off_t)r;
} }
/** @return true if p fits into the range of a uint32_t */
GIT_INLINE(int) git__is_uint32(size_t p)
{
uint32_t r = (uint32_t)p;
return p == (size_t)r;
}
/* 32-bit cross-platform rotl */ /* 32-bit cross-platform rotl */
#ifdef _MSC_VER /* use built-in method in MSVC */ #ifdef _MSC_VER /* use built-in method in MSVC */
# define git__rotl(v, s) (uint32_t)_rotl(v, s) # define git__rotl(v, s) (uint32_t)_rotl(v, s)
......
...@@ -213,3 +213,8 @@ void diff_print(FILE *fp, git_diff_list *diff) ...@@ -213,3 +213,8 @@ void diff_print(FILE *fp, git_diff_list *diff)
{ {
cl_git_pass(git_diff_print_patch(diff, diff_print_cb, fp ? fp : stderr)); cl_git_pass(git_diff_print_patch(diff, diff_print_cb, fp ? fp : stderr));
} }
void diff_print_raw(FILE *fp, git_diff_list *diff)
{
cl_git_pass(git_diff_print_raw(diff, diff_print_cb, fp ? fp : stderr));
}
...@@ -65,4 +65,4 @@ extern int diff_foreach_via_iterator( ...@@ -65,4 +65,4 @@ extern int diff_foreach_via_iterator(
void *data); void *data);
extern void diff_print(FILE *fp, git_diff_list *diff); extern void diff_print(FILE *fp, git_diff_list *diff);
extern void diff_print_raw(FILE *fp, git_diff_list *diff);
...@@ -73,3 +73,40 @@ void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void) ...@@ -73,3 +73,40 @@ void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void)
cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y'); cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y');
cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z');
} }
static void check_partial_oid(
char *buffer, size_t count, const git_oid *oid, const char *expected)
{
git_oid_nfmt(buffer, count, oid);
buffer[count] = '\0';
cl_assert_equal_s(expected, buffer);
}
void test_object_raw_convert__convert_oid_partially(void)
{
const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
git_oid in;
char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */
cl_git_pass(git_oid_fromstr(&in, exp));
git_oid_nfmt(big, sizeof(big), &in);
cl_assert_equal_s(exp, big);
git_oid_nfmt(big, GIT_OID_HEXSZ + 1, &in);
cl_assert_equal_s(exp, big);
check_partial_oid(big, 1, &in, "1");
check_partial_oid(big, 2, &in, "16");
check_partial_oid(big, 3, &in, "16a");
check_partial_oid(big, 4, &in, "16a0");
check_partial_oid(big, 5, &in, "16a01");
check_partial_oid(big, GIT_OID_HEXSZ, &in, exp);
check_partial_oid(
big, GIT_OID_HEXSZ - 1, &in, "16a0123456789abcdef4b775213c23a8bd74f5e");
check_partial_oid(
big, GIT_OID_HEXSZ - 2, &in, "16a0123456789abcdef4b775213c23a8bd74f5");
check_partial_oid(
big, GIT_OID_HEXSZ - 3, &in, "16a0123456789abcdef4b775213c23a8bd74f");
}
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