/* * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "git2/errors.h" #include "common.h" #include "diff.h" #include "diff_driver.h" #include "diff_xdiff.h" #include "patch_generate.h" static int git_xdiff_scan_int(const char **str, int *value) { const char *scan = *str; int v = 0, digits = 0; /* find next digit */ for (scan = *str; *scan && !git__isdigit(*scan); scan++); /* parse next number */ for (; git__isdigit(*scan); scan++, digits++) v = (v * 10) + (*scan - '0'); *str = scan; *value = v; return (digits > 0) ? 0 : -1; } static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) { /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ if (*header != '@') goto fail; if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) goto fail; if (*header == ',') { if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) goto fail; } else hunk->old_lines = 1; if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) goto fail; if (*header == ',') { if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) goto fail; } else hunk->new_lines = 1; if (hunk->old_start < 0 || hunk->new_start < 0) goto fail; return 0; fail: giterr_set(GITERR_INVALID, "malformed hunk header from xdiff"); return -1; } typedef struct { git_xdiff_output *xo; git_patch_generated *patch; git_diff_hunk hunk; int old_lineno, new_lineno; mmfile_t xd_old_data, xd_new_data; } git_xdiff_info; static int diff_update_lines( git_xdiff_info *info, git_diff_line *line, const char *content, size_t content_len) { const char *scan = content, *scan_end = content + content_len; for (line->num_lines = 0; scan < scan_end; ++scan) if (*scan == '\n') ++line->num_lines; line->content = content; line->content_len = content_len; /* expect " "/"-"/"+", then data */ switch (line->origin) { case GIT_DIFF_LINE_ADDITION: case GIT_DIFF_LINE_DEL_EOFNL: line->old_lineno = -1; line->new_lineno = info->new_lineno; info->new_lineno += (int)line->num_lines; break; case GIT_DIFF_LINE_DELETION: case GIT_DIFF_LINE_ADD_EOFNL: line->old_lineno = info->old_lineno; line->new_lineno = -1; info->old_lineno += (int)line->num_lines; break; case GIT_DIFF_LINE_CONTEXT: case GIT_DIFF_LINE_CONTEXT_EOFNL: line->old_lineno = info->old_lineno; line->new_lineno = info->new_lineno; info->old_lineno += (int)line->num_lines; info->new_lineno += (int)line->num_lines; break; default: giterr_set(GITERR_INVALID, "unknown diff line origin %02x", (unsigned int)line->origin); return -1; } return 0; } static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) { git_xdiff_info *info = priv; git_patch_generated *patch = info->patch; const git_diff_delta *delta = patch->base.delta; git_patch_generated_output *output = &info->xo->output; git_diff_line line; if (len == 1) { output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); if (output->error < 0) return output->error; info->hunk.header_len = bufs[0].size; if (info->hunk.header_len >= sizeof(info->hunk.header)) info->hunk.header_len = sizeof(info->hunk.header) - 1; memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); info->hunk.header[info->hunk.header_len] = '\0'; if (output->hunk_cb != NULL && (output->error = output->hunk_cb( delta, &info->hunk, output->payload))) return output->error; info->old_lineno = info->hunk.old_start; info->new_lineno = info->hunk.new_start; } if (len == 2 || len == 3) { /* expect " "/"-"/"+", then data */ line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : GIT_DIFF_LINE_CONTEXT; if (line.origin == GIT_DIFF_LINE_ADDITION) line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; else if (line.origin == GIT_DIFF_LINE_DELETION) line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; else line.content_offset = -1; output->error = diff_update_lines( info, &line, bufs[1].ptr, bufs[1].size); if (!output->error && output->data_cb != NULL) output->error = output->data_cb( delta, &info->hunk, &line, output->payload); } if (len == 3 && !output->error) { /* If we have a '+' and a third buf, then we have added a line * without a newline and the old code had one, so DEL_EOFNL. * If we have a '-' and a third buf, then we have removed a line * with out a newline but added a blank line, so ADD_EOFNL. */ line.origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : GIT_DIFF_LINE_CONTEXT_EOFNL; line.content_offset = -1; output->error = diff_update_lines( info, &line, bufs[2].ptr, bufs[2].size); if (!output->error && output->data_cb != NULL) output->error = output->data_cb( delta, &info->hunk, &line, output->payload); } return output->error; } static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) { git_xdiff_output *xo = (git_xdiff_output *)output; git_xdiff_info info; git_diff_find_context_payload findctxt; memset(&info, 0, sizeof(info)); info.patch = patch; info.xo = xo; xo->callback.priv = &info; git_diff_find_context_init( &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); xo->config.find_func_priv = &findctxt; if (xo->config.find_func != NULL) xo->config.flags |= XDL_EMIT_FUNCNAMES; else xo->config.flags &= ~XDL_EMIT_FUNCNAMES; /* TODO: check ofile.opts_flags to see if driver-specific per-file * updates are needed to xo->params.flags */ git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch); if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { giterr_set(GITERR_INVALID, "files too large for diff"); return -1; } xdl_diff(&info.xd_old_data, &info.xd_new_data, &xo->params, &xo->config, &xo->callback); git_diff_find_context_clear(&findctxt); return xo->output.error; } void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) { uint32_t flags = opts ? opts->flags : 0; xo->output.diff_cb = git_xdiff; xo->config.ctxlen = opts ? opts->context_lines : 3; xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; if (flags & GIT_DIFF_IGNORE_WHITESPACE) xo->params.flags |= XDF_WHITESPACE_FLAGS; if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; if (flags & GIT_DIFF_PATIENCE) xo->params.flags |= XDF_PATIENCE_DIFF; if (flags & GIT_DIFF_MINIMAL) xo->params.flags |= XDF_NEED_MINIMAL; xo->callback.outf = git_xdiff_cb; }