mirror of
https://github.com/git-for-windows/git.git
synced 2026-06-27 11:47:49 -05:00
The source files for libgit.a have been moved into a new "lib/" directory to clean up the top-level directory and clearly separate library code. * ps/libgit-in-subdir: Move libgit.a sources into separate "lib/" directory t/helper: prepare "test-example-tap.c" for introduction of "lib/"
298 lines
7.3 KiB
C
298 lines
7.3 KiB
C
/*
|
|
* Diff process backend: communicates with a long-running external
|
|
* tool via the pkt-line protocol to obtain custom line-matching
|
|
* results. The tool controls which lines are marked as changed
|
|
* while the display shows the file content (after any textconv
|
|
* transformation, if configured).
|
|
*
|
|
* Protocol: pkt-line over stdin/stdout, following the pattern of
|
|
* the long-running filter process protocol (see convert.c).
|
|
*
|
|
* Handshake:
|
|
* git> git-diff-client / version=1 / flush
|
|
* tool< git-diff-server / version=1 / flush
|
|
* git> capability=hunks / flush
|
|
* tool< capability=hunks / flush
|
|
*
|
|
* Per-file:
|
|
* git> command=hunks / pathname=<path> / flush
|
|
* git> <old content packetized> / flush
|
|
* git> <new content packetized> / flush
|
|
* tool< hunk <old_start> <old_count> <new_start> <new_count>
|
|
* tool< ... / flush
|
|
* tool< status=success / flush
|
|
*
|
|
* When the tool returns no hunks with status=success, it considers
|
|
* the files equivalent. Git will skip the diff for that file.
|
|
*/
|
|
|
|
#include "git-compat-util.h"
|
|
#include "diff-process.h"
|
|
#include "diff.h"
|
|
#include "gettext.h"
|
|
#include "repository.h"
|
|
#include "sigchain.h"
|
|
#include "userdiff.h"
|
|
#include "sub-process.h"
|
|
#include "pkt-line.h"
|
|
#include "strbuf.h"
|
|
#include "xdiff/xdiff.h"
|
|
|
|
#define CAP_HUNKS (1u << 0)
|
|
|
|
struct diff_subprocess {
|
|
struct subprocess_entry subprocess;
|
|
unsigned int supported_capabilities;
|
|
};
|
|
|
|
static int start_diff_process_fn(struct subprocess_entry *subprocess)
|
|
{
|
|
static int versions[] = { 1, 0 };
|
|
static struct subprocess_capability capabilities[] = {
|
|
{ "hunks", CAP_HUNKS },
|
|
{ NULL, 0 }
|
|
};
|
|
struct diff_subprocess *entry =
|
|
container_of(subprocess, struct diff_subprocess, subprocess);
|
|
|
|
return subprocess_handshake(subprocess, "git-diff",
|
|
versions, NULL,
|
|
capabilities,
|
|
&entry->supported_capabilities);
|
|
}
|
|
|
|
static struct diff_subprocess *get_or_launch_process(
|
|
struct userdiff_driver *drv)
|
|
{
|
|
struct diff_subprocess *entry;
|
|
|
|
if (drv->diff_subprocess)
|
|
return drv->diff_subprocess;
|
|
|
|
entry = xcalloc(1, sizeof(*entry));
|
|
if (subprocess_start_command(&entry->subprocess, drv->process,
|
|
start_diff_process_fn)) {
|
|
free(entry);
|
|
drv->diff_process_failed = 1;
|
|
return NULL;
|
|
}
|
|
|
|
drv->diff_subprocess = entry;
|
|
return entry;
|
|
}
|
|
|
|
static int send_file_content(int fd, const char *buf, long size)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (size < 0)
|
|
return -1;
|
|
if (size > 0)
|
|
ret = write_packetized_from_buf_no_flush(buf, size, fd);
|
|
if (ret)
|
|
return ret;
|
|
return packet_flush_gently(fd);
|
|
}
|
|
|
|
static int parse_hunk_line(const char *line, struct xdl_hunk *hunk)
|
|
{
|
|
char *end;
|
|
|
|
/*
|
|
* Format: "hunk <old_start> <old_count> <new_start> <new_count>"
|
|
* All numbers must be non-negative decimal with no leading
|
|
* whitespace or sign characters.
|
|
*/
|
|
if (!skip_prefix(line, "hunk ", &line))
|
|
return -1;
|
|
|
|
if (!isdigit(*line))
|
|
return -1;
|
|
errno = 0;
|
|
hunk->old_start = strtol(line, &end, 10);
|
|
if (errno || end == line || *end++ != ' ')
|
|
return -1;
|
|
line = end;
|
|
|
|
if (!isdigit(*line))
|
|
return -1;
|
|
errno = 0;
|
|
hunk->old_count = strtol(line, &end, 10);
|
|
if (errno || end == line || *end++ != ' ')
|
|
return -1;
|
|
line = end;
|
|
|
|
if (!isdigit(*line))
|
|
return -1;
|
|
errno = 0;
|
|
hunk->new_start = strtol(line, &end, 10);
|
|
if (errno || end == line || *end++ != ' ')
|
|
return -1;
|
|
line = end;
|
|
|
|
if (!isdigit(*line))
|
|
return -1;
|
|
errno = 0;
|
|
hunk->new_count = strtol(line, &end, 10);
|
|
if (errno || end == line || *end != '\0')
|
|
return -1;
|
|
|
|
/*
|
|
* git diff emits start=0 when count=0 (empty file side).
|
|
* Normalize to 1-based so downstream validation can assume start >= 1.
|
|
*/
|
|
if (!hunk->old_count && !hunk->old_start)
|
|
hunk->old_start = 1;
|
|
if (!hunk->new_count && !hunk->new_start)
|
|
hunk->new_start = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum diff_process_result get_hunks(
|
|
struct userdiff_driver *drv,
|
|
const char *path,
|
|
const char *old_buf, long old_size,
|
|
const char *new_buf, long new_size,
|
|
struct xdl_hunk **hunks_out,
|
|
size_t *nr_hunks_out)
|
|
{
|
|
struct diff_subprocess *backend;
|
|
struct child_process *process;
|
|
int fd_in, fd_out;
|
|
struct strbuf status = STRBUF_INIT;
|
|
struct xdl_hunk *hunks = NULL;
|
|
struct xdl_hunk hunk;
|
|
size_t nr_hunks = 0, alloc_hunks = 0;
|
|
int len;
|
|
char *line;
|
|
|
|
backend = get_or_launch_process(drv);
|
|
if (!backend)
|
|
return DIFF_PROCESS_ERROR;
|
|
|
|
if (!(backend->supported_capabilities & CAP_HUNKS))
|
|
return DIFF_PROCESS_SKIP;
|
|
|
|
process = subprocess_get_child_process(&backend->subprocess);
|
|
fd_in = process->in;
|
|
fd_out = process->out;
|
|
|
|
sigchain_push(SIGPIPE, SIG_IGN);
|
|
|
|
/* Send request */
|
|
if (packet_write_fmt_gently(fd_in, "command=hunks\n") ||
|
|
packet_write_fmt_gently(fd_in, "pathname=%s\n", path) ||
|
|
packet_flush_gently(fd_in))
|
|
goto comm_error;
|
|
|
|
/* Send old file content */
|
|
if (send_file_content(fd_in, old_buf, old_size))
|
|
goto comm_error;
|
|
|
|
/* Send new file content */
|
|
if (send_file_content(fd_in, new_buf, new_size))
|
|
goto comm_error;
|
|
|
|
/* Read hunks until flush packet */
|
|
while ((len = packet_read_line_gently(fd_out, NULL, &line)) >= 0 &&
|
|
line) {
|
|
if (parse_hunk_line(line, &hunk) < 0)
|
|
goto comm_error;
|
|
ALLOC_GROW(hunks, nr_hunks + 1, alloc_hunks);
|
|
hunks[nr_hunks++] = hunk;
|
|
}
|
|
if (len < 0)
|
|
goto comm_error;
|
|
|
|
/* Read status */
|
|
if (subprocess_read_status(fd_out, &status))
|
|
goto comm_error;
|
|
|
|
if (!strcmp(status.buf, "success")) {
|
|
*hunks_out = hunks;
|
|
*nr_hunks_out = nr_hunks;
|
|
strbuf_release(&status);
|
|
sigchain_pop(SIGPIPE);
|
|
return DIFF_PROCESS_OK;
|
|
}
|
|
|
|
if (!strcmp(status.buf, "abort")) {
|
|
/*
|
|
* The tool voluntarily withdrew: stop sending requests
|
|
* but do not warn (this is not a failure).
|
|
*/
|
|
backend->supported_capabilities &= ~CAP_HUNKS;
|
|
free(hunks);
|
|
strbuf_release(&status);
|
|
sigchain_pop(SIGPIPE);
|
|
return DIFF_PROCESS_SKIP;
|
|
}
|
|
|
|
/* status=error or unknown status */
|
|
free(hunks);
|
|
strbuf_release(&status);
|
|
sigchain_pop(SIGPIPE);
|
|
return DIFF_PROCESS_ERROR;
|
|
|
|
comm_error:
|
|
/*
|
|
* Communication failure (broken pipe, malformed response).
|
|
* Tear down the process and mark as failed so we do not
|
|
* retry on every subsequent file.
|
|
*/
|
|
drv->diff_process_failed = 1;
|
|
drv->diff_subprocess = NULL;
|
|
subprocess_stop_command(&backend->subprocess);
|
|
free(backend);
|
|
free(hunks);
|
|
strbuf_release(&status);
|
|
sigchain_pop(SIGPIPE);
|
|
return DIFF_PROCESS_ERROR;
|
|
}
|
|
|
|
enum diff_process_result diff_process_fill_hunks(
|
|
struct diff_options *diffopt,
|
|
const char *path,
|
|
const mmfile_t *file_a,
|
|
const mmfile_t *file_b,
|
|
xpparam_t *xpp)
|
|
{
|
|
struct userdiff_driver *drv;
|
|
struct xdl_hunk *ext_hunks = NULL;
|
|
size_t nr = 0;
|
|
enum diff_process_result res;
|
|
|
|
if (!diffopt || !path)
|
|
return DIFF_PROCESS_SKIP;
|
|
if (diffopt->flags.no_diff_process || diffopt->ignore_driver_algorithm)
|
|
return DIFF_PROCESS_SKIP;
|
|
|
|
drv = userdiff_find_by_path(diffopt->repo->index, path);
|
|
if (!drv || !drv->process)
|
|
return DIFF_PROCESS_SKIP;
|
|
if (drv->diff_process_failed)
|
|
return DIFF_PROCESS_SKIP;
|
|
|
|
res = get_hunks(drv, path,
|
|
file_a->ptr, file_a->size,
|
|
file_b->ptr, file_b->size,
|
|
&ext_hunks, &nr);
|
|
if (res == DIFF_PROCESS_OK) {
|
|
if (!nr) {
|
|
free(ext_hunks);
|
|
return DIFF_PROCESS_EQUIVALENT;
|
|
}
|
|
xpp->external_hunks = ext_hunks;
|
|
xpp->external_hunks_nr = nr;
|
|
return DIFF_PROCESS_OK;
|
|
}
|
|
if (res == DIFF_PROCESS_ERROR) {
|
|
warning(_("diff process '%s' failed for '%s',"
|
|
" falling back to builtin diff"),
|
|
drv->process, path);
|
|
return DIFF_PROCESS_ERROR;
|
|
}
|
|
return DIFF_PROCESS_SKIP;
|
|
}
|