|
| 1 | +/* |
| 2 | + * Diff process backend: communicates with a long-running external |
| 3 | + * tool via the pkt-line protocol to obtain custom line-matching |
| 4 | + * results. Unlike textconv, which transforms the displayed content, |
| 5 | + * hunks from a diff process reference original line numbers and |
| 6 | + * the display shows the actual file content. |
| 7 | + * |
| 8 | + * Protocol: pkt-line over stdin/stdout, following the pattern of |
| 9 | + * the long-running filter process protocol (see convert.c). |
| 10 | + * |
| 11 | + * Handshake: |
| 12 | + * git> git-diff-client / version=1 / flush |
| 13 | + * tool< git-diff-server / version=1 / flush |
| 14 | + * git> capability=hunks / flush |
| 15 | + * tool< capability=hunks / flush |
| 16 | + * |
| 17 | + * Per-file: |
| 18 | + * git> command=hunks / pathname=<path> / flush |
| 19 | + * git> <old content packetized> / flush |
| 20 | + * git> <new content packetized> / flush |
| 21 | + * tool< hunk <old_start> <old_count> <new_start> <new_count> |
| 22 | + * tool< ... / flush |
| 23 | + * tool< status=success / flush |
| 24 | + * |
| 25 | + * Zero hunks with status=success means the tool considers the |
| 26 | + * files equivalent. Git will skip the diff for that file. |
| 27 | + */ |
| 28 | + |
| 29 | +#include "git-compat-util.h" |
| 30 | +#include "diff-process.h" |
| 31 | +#include "userdiff.h" |
| 32 | +#include "sub-process.h" |
| 33 | +#include "pkt-line.h" |
| 34 | +#include "strbuf.h" |
| 35 | +#include "xdiff/xdiff.h" |
| 36 | + |
| 37 | +#define CAP_HUNKS (1u << 0) |
| 38 | + |
| 39 | +struct diff_subprocess { |
| 40 | + struct subprocess_entry subprocess; |
| 41 | + unsigned int supported_capabilities; |
| 42 | +}; |
| 43 | + |
| 44 | +static int subprocess_map_initialized; |
| 45 | +static struct hashmap subprocess_map; |
| 46 | + |
| 47 | +static int start_diff_process_fn(struct subprocess_entry *subprocess) |
| 48 | +{ |
| 49 | + static int versions[] = { 1, 0 }; |
| 50 | + static struct subprocess_capability capabilities[] = { |
| 51 | + { "hunks", CAP_HUNKS }, |
| 52 | + { NULL, 0 } |
| 53 | + }; |
| 54 | + struct diff_subprocess *entry = |
| 55 | + (struct diff_subprocess *)subprocess; |
| 56 | + |
| 57 | + /* Uses dying pkt-line variant, same as convert.c filters. */ |
| 58 | + return subprocess_handshake(subprocess, "git-diff", |
| 59 | + versions, NULL, |
| 60 | + capabilities, |
| 61 | + &entry->supported_capabilities); |
| 62 | +} |
| 63 | + |
| 64 | +static struct diff_subprocess *find_or_start_process(const char *cmd) |
| 65 | +{ |
| 66 | + struct diff_subprocess *entry; |
| 67 | + |
| 68 | + if (!subprocess_map_initialized) { |
| 69 | + subprocess_map_initialized = 1; |
| 70 | + hashmap_init(&subprocess_map, cmd2process_cmp, NULL, 0); |
| 71 | + } |
| 72 | + |
| 73 | + entry = (struct diff_subprocess *) |
| 74 | + subprocess_find_entry(&subprocess_map, cmd); |
| 75 | + if (entry) |
| 76 | + return entry; |
| 77 | + |
| 78 | + entry = xcalloc(1, sizeof(*entry)); |
| 79 | + if (subprocess_start(&subprocess_map, &entry->subprocess, |
| 80 | + cmd, start_diff_process_fn)) { |
| 81 | + free(entry); |
| 82 | + return NULL; |
| 83 | + } |
| 84 | + |
| 85 | + return entry; |
| 86 | +} |
| 87 | + |
| 88 | +static int send_file_content(int fd, const char *buf, long size) |
| 89 | +{ |
| 90 | + int ret; |
| 91 | + |
| 92 | + if (size > 0) |
| 93 | + ret = write_packetized_from_buf_no_flush(buf, size, fd); |
| 94 | + else |
| 95 | + ret = 0; |
| 96 | + if (ret) |
| 97 | + return ret; |
| 98 | + return packet_flush_gently(fd); |
| 99 | +} |
| 100 | + |
| 101 | +static int parse_hunk_line(const char *line, struct xdl_hunk *hunk) |
| 102 | +{ |
| 103 | + char *end; |
| 104 | + |
| 105 | + /* Format: "hunk <old_start> <old_count> <new_start> <new_count>" */ |
| 106 | + if (!skip_prefix(line, "hunk ", &line)) |
| 107 | + return -1; |
| 108 | + |
| 109 | + hunk->old_start = strtol(line, &end, 10); |
| 110 | + if (end == line || *end != ' ') |
| 111 | + return -1; |
| 112 | + line = end; |
| 113 | + |
| 114 | + hunk->old_count = strtol(line, &end, 10); |
| 115 | + if (end == line || *end != ' ') |
| 116 | + return -1; |
| 117 | + line = end; |
| 118 | + |
| 119 | + hunk->new_start = strtol(line, &end, 10); |
| 120 | + if (end == line || *end != ' ') |
| 121 | + return -1; |
| 122 | + line = end; |
| 123 | + |
| 124 | + hunk->new_count = strtol(line, &end, 10); |
| 125 | + if (end == line || *end != '\0') |
| 126 | + return -1; |
| 127 | + |
| 128 | + return 0; |
| 129 | +} |
| 130 | + |
| 131 | +int diff_process_get_hunks(struct userdiff_driver *drv, |
| 132 | + const char *path, |
| 133 | + const char *old_buf, long old_size, |
| 134 | + const char *new_buf, long new_size, |
| 135 | + struct xdl_hunk **hunks_out, |
| 136 | + size_t *nr_hunks_out) |
| 137 | +{ |
| 138 | + struct diff_subprocess *backend; |
| 139 | + struct child_process *process; |
| 140 | + int fd_in, fd_out; |
| 141 | + struct strbuf status = STRBUF_INIT; |
| 142 | + struct xdl_hunk *hunks = NULL; |
| 143 | + struct xdl_hunk hunk; |
| 144 | + size_t nr_hunks = 0, alloc_hunks = 0; |
| 145 | + int len; |
| 146 | + char *line; |
| 147 | + |
| 148 | + if (!drv || !drv->process) |
| 149 | + return -1; |
| 150 | + |
| 151 | + backend = find_or_start_process(drv->process); |
| 152 | + if (!backend) |
| 153 | + return -1; |
| 154 | + |
| 155 | + if (!(backend->supported_capabilities & CAP_HUNKS)) |
| 156 | + return -1; |
| 157 | + |
| 158 | + process = subprocess_get_child_process(&backend->subprocess); |
| 159 | + fd_in = process->in; |
| 160 | + fd_out = process->out; |
| 161 | + |
| 162 | + /* Send request */ |
| 163 | + if (packet_write_fmt_gently(fd_in, "command=hunks\n") || |
| 164 | + packet_write_fmt_gently(fd_in, "pathname=%s\n", path) || |
| 165 | + packet_flush_gently(fd_in)) |
| 166 | + goto error; |
| 167 | + |
| 168 | + /* Send old file content */ |
| 169 | + if (send_file_content(fd_in, old_buf, old_size)) |
| 170 | + goto error; |
| 171 | + |
| 172 | + /* Send new file content */ |
| 173 | + if (send_file_content(fd_in, new_buf, new_size)) |
| 174 | + goto error; |
| 175 | + |
| 176 | + /* Read hunks until flush packet */ |
| 177 | + while ((len = packet_read_line_gently(fd_out, NULL, &line)) >= 0 && |
| 178 | + line) { |
| 179 | + if (parse_hunk_line(line, &hunk) < 0) |
| 180 | + goto error; |
| 181 | + ALLOC_GROW(hunks, nr_hunks + 1, alloc_hunks); |
| 182 | + hunks[nr_hunks++] = hunk; |
| 183 | + } |
| 184 | + if (len < 0) |
| 185 | + goto error; |
| 186 | + |
| 187 | + /* Read status */ |
| 188 | + if (subprocess_read_status(fd_out, &status)) |
| 189 | + goto error; |
| 190 | + |
| 191 | + if (strcmp(status.buf, "success")) { |
| 192 | + if (!strcmp(status.buf, "abort")) |
| 193 | + backend->supported_capabilities &= ~CAP_HUNKS; |
| 194 | + goto error; |
| 195 | + } |
| 196 | + |
| 197 | + *hunks_out = hunks; |
| 198 | + *nr_hunks_out = nr_hunks; |
| 199 | + strbuf_release(&status); |
| 200 | + return 0; |
| 201 | + |
| 202 | +error: |
| 203 | + free(hunks); |
| 204 | + strbuf_release(&status); |
| 205 | + return -1; |
| 206 | +} |
0 commit comments