|
| 1 | +/* |
| 2 | + * Built-in diff process that returns zero hunks for files whose |
| 3 | + * only differences are whitespace, and status=error otherwise. |
| 4 | + * See diff-process.c for the protocol and gitattributes(5) for usage. |
| 5 | + * |
| 6 | + * Uses xdiff_compare_lines() with XDF_IGNORE_WHITESPACE to compare |
| 7 | + * lines, giving the same whitespace handling as "git diff -w". |
| 8 | + */ |
| 9 | + |
| 10 | +#include "builtin.h" |
| 11 | +#include "pkt-line.h" |
| 12 | +#include "strbuf.h" |
| 13 | +#include "xdiff-interface.h" |
| 14 | + |
| 15 | +/* |
| 16 | + * Read a single pkt-line. Returns 1 for data, 0 for flush, -1 for EOF. |
| 17 | + */ |
| 18 | +static int read_pkt(int fd, struct strbuf *line) |
| 19 | +{ |
| 20 | + int len; |
| 21 | + char *data; |
| 22 | + |
| 23 | + if (packet_read_line_gently(fd, &len, &data) < 0) |
| 24 | + return -1; |
| 25 | + if (!data || !len) |
| 26 | + return 0; /* flush */ |
| 27 | + strbuf_reset(line); |
| 28 | + strbuf_add(line, data, len); |
| 29 | + strbuf_rtrim(line); |
| 30 | + return 1; |
| 31 | +} |
| 32 | + |
| 33 | +/* |
| 34 | + * Read packetized content until a flush packet. |
| 35 | + */ |
| 36 | +static int read_content(int fd, struct strbuf *out) |
| 37 | +{ |
| 38 | + strbuf_reset(out); |
| 39 | + if (read_packetized_to_strbuf(fd, out, PACKET_READ_GENTLE_ON_EOF) < 0) |
| 40 | + return -1; |
| 41 | + return 0; |
| 42 | +} |
| 43 | + |
| 44 | +/* |
| 45 | + * Compare two buffers line by line using xdiff_compare_lines() with |
| 46 | + * XDF_IGNORE_WHITESPACE (same logic as "git diff -w"). |
| 47 | + * Returns 1 if all lines match, 0 otherwise. |
| 48 | + */ |
| 49 | +static int whitespace_equivalent(const char *a, long size_a, |
| 50 | + const char *b, long size_b) |
| 51 | +{ |
| 52 | + const char *ea = a + size_a; |
| 53 | + const char *eb = b + size_b; |
| 54 | + |
| 55 | + while (a < ea && b < eb) { |
| 56 | + const char *eol_a = memchr(a, '\n', ea - a); |
| 57 | + const char *eol_b = memchr(b, '\n', eb - b); |
| 58 | + long len_a = (eol_a ? eol_a : ea) - a; |
| 59 | + long len_b = (eol_b ? eol_b : eb) - b; |
| 60 | + |
| 61 | + if (!xdiff_compare_lines(a, len_a, b, len_b, |
| 62 | + XDF_IGNORE_WHITESPACE)) |
| 63 | + return 0; |
| 64 | + |
| 65 | + a += len_a + (eol_a ? 1 : 0); |
| 66 | + b += len_b + (eol_b ? 1 : 0); |
| 67 | + } |
| 68 | + |
| 69 | + /* Both sides must be exhausted */ |
| 70 | + return a >= ea && b >= eb; |
| 71 | +} |
| 72 | + |
| 73 | +int cmd_diff_process_normalize(int argc UNUSED, const char **argv UNUSED, |
| 74 | + const char *prefix UNUSED, |
| 75 | + struct repository *repo UNUSED) |
| 76 | +{ |
| 77 | + struct strbuf line = STRBUF_INIT; |
| 78 | + struct strbuf old_content = STRBUF_INIT; |
| 79 | + struct strbuf new_content = STRBUF_INIT; |
| 80 | + int ret; |
| 81 | + |
| 82 | + /* Handshake: read client greeting */ |
| 83 | + ret = read_pkt(0, &line); |
| 84 | + if (ret <= 0 || strcmp(line.buf, "git-diff-client")) |
| 85 | + return 1; |
| 86 | + ret = read_pkt(0, &line); |
| 87 | + if (ret <= 0 || strcmp(line.buf, "version=1")) |
| 88 | + return 1; |
| 89 | + read_pkt(0, &line); /* flush */ |
| 90 | + |
| 91 | + /* Send server greeting */ |
| 92 | + packet_write_fmt(1, "git-diff-server\n"); |
| 93 | + packet_write_fmt(1, "version=1\n"); |
| 94 | + packet_flush(1); |
| 95 | + |
| 96 | + /* Read client capabilities until flush */ |
| 97 | + while ((ret = read_pkt(0, &line)) > 0) |
| 98 | + ; /* consume */ |
| 99 | + |
| 100 | + /* Send our capabilities */ |
| 101 | + packet_write_fmt(1, "capability=hunks\n"); |
| 102 | + packet_flush(1); |
| 103 | + |
| 104 | + /* Main loop: process file pairs */ |
| 105 | + for (;;) { |
| 106 | + int have_command = 0; |
| 107 | + |
| 108 | + /* Read request headers until flush */ |
| 109 | + while ((ret = read_pkt(0, &line)) > 0) { |
| 110 | + if (starts_with(line.buf, "command=")) |
| 111 | + have_command = 1; |
| 112 | + } |
| 113 | + if (ret < 0) |
| 114 | + break; /* EOF: client closed connection */ |
| 115 | + if (!have_command) |
| 116 | + break; |
| 117 | + |
| 118 | + /* Read old file content */ |
| 119 | + if (read_content(0, &old_content) < 0) |
| 120 | + break; |
| 121 | + /* Read new file content */ |
| 122 | + if (read_content(0, &new_content) < 0) |
| 123 | + break; |
| 124 | + |
| 125 | + if (whitespace_equivalent(old_content.buf, old_content.len, |
| 126 | + new_content.buf, new_content.len)) { |
| 127 | + /* Whitespace-only differences */ |
| 128 | + packet_flush(1); /* zero hunks */ |
| 129 | + packet_write_fmt(1, "status=success\n"); |
| 130 | + packet_flush(1); |
| 131 | + } else { |
| 132 | + /* Non-whitespace differences: fall back */ |
| 133 | + packet_flush(1); |
| 134 | + packet_write_fmt(1, "status=error\n"); |
| 135 | + packet_flush(1); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + strbuf_release(&line); |
| 140 | + strbuf_release(&old_content); |
| 141 | + strbuf_release(&new_content); |
| 142 | + return 0; |
| 143 | +} |
0 commit comments