Git fork
1#include "builtin.h"
2#include "config.h"
3#include "diff.h"
4#include "diffcore.h"
5#include "gettext.h"
6#include "hash.h"
7#include "hex.h"
8#include "object.h"
9#include "parse-options.h"
10#include "revision.h"
11#include "strbuf.h"
12
13static unsigned parse_mode_or_die(const char *mode, const char **end)
14{
15 uint16_t ret;
16
17 *end = parse_mode(mode, &ret);
18 if (!*end)
19 die(_("unable to parse mode: %s"), mode);
20 return ret;
21}
22
23static void parse_oid_or_die(const char *hex, struct object_id *oid,
24 const char **end, const struct git_hash_algo *algop)
25{
26 if (parse_oid_hex_algop(hex, oid, end, algop) || *(*end)++ != ' ')
27 die(_("unable to parse object id: %s"), hex);
28}
29
30int cmd_diff_pairs(int argc, const char **argv, const char *prefix,
31 struct repository *repo)
32{
33 struct strbuf path_dst = STRBUF_INIT;
34 struct strbuf path = STRBUF_INIT;
35 struct strbuf meta = STRBUF_INIT;
36 struct option *parseopts;
37 struct rev_info revs;
38 int line_term = '\0';
39 int ret;
40
41 const char * const builtin_diff_pairs_usage[] = {
42 N_("git diff-pairs -z [<diff-options>]"),
43 NULL
44 };
45 struct option builtin_diff_pairs_options[] = {
46 OPT_END()
47 };
48
49 repo_init_revisions(repo, &revs, prefix);
50
51 /*
52 * Diff options are usually parsed implicitly as part of
53 * setup_revisions(). Explicitly handle parsing to ensure options are
54 * printed in the usage message.
55 */
56 parseopts = add_diff_options(builtin_diff_pairs_options, &revs.diffopt);
57 show_usage_with_options_if_asked(argc, argv, builtin_diff_pairs_usage, parseopts);
58
59 repo_config(repo, git_diff_basic_config, NULL);
60 revs.diffopt.no_free = 1;
61 revs.disable_stdin = 1;
62 revs.abbrev = 0;
63 revs.diff = 1;
64
65 argc = parse_options(argc, argv, prefix, parseopts, builtin_diff_pairs_usage,
66 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_DASHDASH);
67
68 if (setup_revisions(argc, argv, &revs, NULL) > 1)
69 usagef(_("unrecognized argument: %s"), argv[0]);
70
71 /*
72 * With the -z option, both command input and raw output are
73 * NUL-delimited (this mode does not affect patch output). At present
74 * only NUL-delimited raw diff formatted input is supported.
75 */
76 if (revs.diffopt.line_termination)
77 usage(_("working without -z is not supported"));
78
79 if (revs.prune_data.nr)
80 usage(_("pathspec arguments not supported"));
81
82 if (revs.pending.nr || revs.max_count != -1 ||
83 revs.min_age != (timestamp_t)-1 ||
84 revs.max_age != (timestamp_t)-1)
85 usage(_("revision arguments not allowed"));
86
87 if (!revs.diffopt.output_format)
88 revs.diffopt.output_format = DIFF_FORMAT_PATCH;
89
90 /*
91 * If rename detection is not requested, use rename information from the
92 * raw diff formatted input. Setting skip_resolving_statuses ensures
93 * diffcore_std() does not mess with rename information already present
94 * in queued filepairs.
95 */
96 if (!revs.diffopt.detect_rename)
97 revs.diffopt.skip_resolving_statuses = 1;
98
99 while (1) {
100 struct object_id oid_a, oid_b;
101 struct diff_filepair *pair;
102 unsigned mode_a, mode_b;
103 const char *p;
104 char status;
105
106 if (strbuf_getwholeline(&meta, stdin, line_term) == EOF)
107 break;
108
109 p = meta.buf;
110 if (!*p) {
111 diffcore_std(&revs.diffopt);
112 diff_flush(&revs.diffopt);
113 /*
114 * When the diff queue is explicitly flushed, append a
115 * NUL byte to separate batches of diffs.
116 */
117 fputc('\0', revs.diffopt.file);
118 fflush(revs.diffopt.file);
119 continue;
120 }
121
122 if (*p != ':')
123 die(_("invalid raw diff input"));
124 p++;
125
126 mode_a = parse_mode_or_die(p, &p);
127 mode_b = parse_mode_or_die(p, &p);
128
129 if (S_ISDIR(mode_a) || S_ISDIR(mode_b))
130 die(_("tree objects not supported"));
131
132 parse_oid_or_die(p, &oid_a, &p, repo->hash_algo);
133 parse_oid_or_die(p, &oid_b, &p, repo->hash_algo);
134
135 status = *p++;
136
137 if (strbuf_getwholeline(&path, stdin, line_term) == EOF)
138 die(_("got EOF while reading path"));
139
140 switch (status) {
141 case DIFF_STATUS_ADDED:
142 pair = diff_queue_addremove(&diff_queued_diff,
143 &revs.diffopt, '+', mode_b,
144 &oid_b, 1, path.buf, 0);
145 if (pair)
146 pair->status = status;
147 break;
148
149 case DIFF_STATUS_DELETED:
150 pair = diff_queue_addremove(&diff_queued_diff,
151 &revs.diffopt, '-', mode_a,
152 &oid_a, 1, path.buf, 0);
153 if (pair)
154 pair->status = status;
155 break;
156
157 case DIFF_STATUS_TYPE_CHANGED:
158 case DIFF_STATUS_MODIFIED:
159 pair = diff_queue_change(&diff_queued_diff, &revs.diffopt,
160 mode_a, mode_b, &oid_a, &oid_b,
161 1, 1, path.buf, 0, 0);
162 if (pair)
163 pair->status = status;
164 break;
165
166 case DIFF_STATUS_RENAMED:
167 case DIFF_STATUS_COPIED: {
168 struct diff_filespec *a, *b;
169 unsigned int score;
170
171 if (strbuf_getwholeline(&path_dst, stdin, line_term) == EOF)
172 die(_("got EOF while reading destination path"));
173
174 a = alloc_filespec(path.buf);
175 b = alloc_filespec(path_dst.buf);
176 fill_filespec(a, &oid_a, 1, mode_a);
177 fill_filespec(b, &oid_b, 1, mode_b);
178
179 pair = diff_queue(&diff_queued_diff, a, b);
180
181 if (strtoul_ui(p, 10, &score))
182 die(_("unable to parse rename/copy score: %s"), p);
183
184 pair->score = score * MAX_SCORE / 100;
185 pair->status = status;
186 pair->renamed_pair = 1;
187 }
188 break;
189
190 default:
191 die(_("unknown diff status: %c"), status);
192 }
193 }
194
195 revs.diffopt.no_free = 0;
196 diffcore_std(&revs.diffopt);
197 diff_flush(&revs.diffopt);
198 ret = diff_result_code(&revs);
199
200 strbuf_release(&path_dst);
201 strbuf_release(&path);
202 strbuf_release(&meta);
203 release_revisions(&revs);
204 FREE_AND_NULL(parseopts);
205
206 return ret;
207}