/src/git/builtin/cat-file.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * GIT - The information manager from hell |
3 | | * |
4 | | * Copyright (C) Linus Torvalds, 2005 |
5 | | */ |
6 | | |
7 | | #include "builtin.h" |
8 | | #include "config.h" |
9 | | #include "convert.h" |
10 | | #include "diff.h" |
11 | | #include "environment.h" |
12 | | #include "gettext.h" |
13 | | #include "hex.h" |
14 | | #include "ident.h" |
15 | | #include "parse-options.h" |
16 | | #include "userdiff.h" |
17 | | #include "streaming.h" |
18 | | #include "oid-array.h" |
19 | | #include "packfile.h" |
20 | | #include "object-file.h" |
21 | | #include "object-name.h" |
22 | | #include "object-store-ll.h" |
23 | | #include "replace-object.h" |
24 | | #include "promisor-remote.h" |
25 | | #include "mailmap.h" |
26 | | #include "write-or-die.h" |
27 | | |
28 | | enum batch_mode { |
29 | | BATCH_MODE_CONTENTS, |
30 | | BATCH_MODE_INFO, |
31 | | BATCH_MODE_QUEUE_AND_DISPATCH, |
32 | | }; |
33 | | |
34 | | struct batch_options { |
35 | | int enabled; |
36 | | int follow_symlinks; |
37 | | enum batch_mode batch_mode; |
38 | | int buffer_output; |
39 | | int all_objects; |
40 | | int unordered; |
41 | | int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */ |
42 | | char input_delim; |
43 | | char output_delim; |
44 | | const char *format; |
45 | | }; |
46 | | |
47 | | static const char *force_path; |
48 | | |
49 | | static struct string_list mailmap = STRING_LIST_INIT_NODUP; |
50 | | static int use_mailmap; |
51 | | |
52 | | static char *replace_idents_using_mailmap(char *, size_t *); |
53 | | |
54 | | static char *replace_idents_using_mailmap(char *object_buf, size_t *size) |
55 | 0 | { |
56 | 0 | struct strbuf sb = STRBUF_INIT; |
57 | 0 | const char *headers[] = { "author ", "committer ", "tagger ", NULL }; |
58 | |
|
59 | 0 | strbuf_attach(&sb, object_buf, *size, *size + 1); |
60 | 0 | apply_mailmap_to_header(&sb, headers, &mailmap); |
61 | 0 | *size = sb.len; |
62 | 0 | return strbuf_detach(&sb, NULL); |
63 | 0 | } |
64 | | |
65 | | static int filter_object(const char *path, unsigned mode, |
66 | | const struct object_id *oid, |
67 | | char **buf, unsigned long *size) |
68 | 0 | { |
69 | 0 | enum object_type type; |
70 | |
|
71 | 0 | *buf = repo_read_object_file(the_repository, oid, &type, size); |
72 | 0 | if (!*buf) |
73 | 0 | return error(_("cannot read object %s '%s'"), |
74 | 0 | oid_to_hex(oid), path); |
75 | 0 | if ((type == OBJ_BLOB) && S_ISREG(mode)) { |
76 | 0 | struct strbuf strbuf = STRBUF_INIT; |
77 | 0 | struct checkout_metadata meta; |
78 | |
|
79 | 0 | init_checkout_metadata(&meta, NULL, NULL, oid); |
80 | 0 | if (convert_to_working_tree(the_repository->index, path, *buf, *size, &strbuf, &meta)) { |
81 | 0 | free(*buf); |
82 | 0 | *size = strbuf.len; |
83 | 0 | *buf = strbuf_detach(&strbuf, NULL); |
84 | 0 | } |
85 | 0 | } |
86 | |
|
87 | 0 | return 0; |
88 | 0 | } |
89 | | |
90 | | static int stream_blob(const struct object_id *oid) |
91 | 0 | { |
92 | 0 | if (stream_blob_to_fd(1, oid, NULL, 0)) |
93 | 0 | die("unable to stream %s to stdout", oid_to_hex(oid)); |
94 | 0 | return 0; |
95 | 0 | } |
96 | | |
97 | | static int cat_one_file(int opt, const char *exp_type, const char *obj_name, |
98 | | int unknown_type) |
99 | 0 | { |
100 | 0 | int ret; |
101 | 0 | struct object_id oid; |
102 | 0 | enum object_type type; |
103 | 0 | char *buf; |
104 | 0 | unsigned long size; |
105 | 0 | struct object_context obj_context = {0}; |
106 | 0 | struct object_info oi = OBJECT_INFO_INIT; |
107 | 0 | struct strbuf sb = STRBUF_INIT; |
108 | 0 | unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; |
109 | 0 | unsigned get_oid_flags = |
110 | 0 | GET_OID_RECORD_PATH | |
111 | 0 | GET_OID_ONLY_TO_DIE | |
112 | 0 | GET_OID_HASH_ANY; |
113 | 0 | const char *path = force_path; |
114 | 0 | const int opt_cw = (opt == 'c' || opt == 'w'); |
115 | 0 | if (!path && opt_cw) |
116 | 0 | get_oid_flags |= GET_OID_REQUIRE_PATH; |
117 | |
|
118 | 0 | if (unknown_type) |
119 | 0 | flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; |
120 | |
|
121 | 0 | if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid, |
122 | 0 | &obj_context)) |
123 | 0 | die("Not a valid object name %s", obj_name); |
124 | | |
125 | 0 | if (!path) |
126 | 0 | path = obj_context.path; |
127 | 0 | if (obj_context.mode == S_IFINVALID) |
128 | 0 | obj_context.mode = 0100644; |
129 | |
|
130 | 0 | buf = NULL; |
131 | 0 | switch (opt) { |
132 | 0 | case 't': |
133 | 0 | oi.type_name = &sb; |
134 | 0 | if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) |
135 | 0 | die("git cat-file: could not get object info"); |
136 | 0 | if (sb.len) { |
137 | 0 | printf("%s\n", sb.buf); |
138 | 0 | strbuf_release(&sb); |
139 | 0 | ret = 0; |
140 | 0 | goto cleanup; |
141 | 0 | } |
142 | 0 | break; |
143 | | |
144 | 0 | case 's': |
145 | 0 | oi.sizep = &size; |
146 | |
|
147 | 0 | if (use_mailmap) { |
148 | 0 | oi.typep = &type; |
149 | 0 | oi.contentp = (void**)&buf; |
150 | 0 | } |
151 | |
|
152 | 0 | if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) |
153 | 0 | die("git cat-file: could not get object info"); |
154 | | |
155 | 0 | if (use_mailmap && (type == OBJ_COMMIT || type == OBJ_TAG)) { |
156 | 0 | size_t s = size; |
157 | 0 | buf = replace_idents_using_mailmap(buf, &s); |
158 | 0 | size = cast_size_t_to_ulong(s); |
159 | 0 | } |
160 | |
|
161 | 0 | printf("%"PRIuMAX"\n", (uintmax_t)size); |
162 | 0 | ret = 0; |
163 | 0 | goto cleanup; |
164 | | |
165 | 0 | case 'e': |
166 | 0 | ret = !repo_has_object_file(the_repository, &oid); |
167 | 0 | goto cleanup; |
168 | | |
169 | 0 | case 'w': |
170 | |
|
171 | 0 | if (filter_object(path, obj_context.mode, |
172 | 0 | &oid, &buf, &size)) { |
173 | 0 | ret = -1; |
174 | 0 | goto cleanup; |
175 | 0 | } |
176 | 0 | break; |
177 | | |
178 | 0 | case 'c': |
179 | 0 | if (textconv_object(the_repository, path, obj_context.mode, |
180 | 0 | &oid, 1, &buf, &size)) |
181 | 0 | break; |
182 | | /* else fallthrough */ |
183 | | |
184 | 0 | case 'p': |
185 | 0 | type = oid_object_info(the_repository, &oid, NULL); |
186 | 0 | if (type < 0) |
187 | 0 | die("Not a valid object name %s", obj_name); |
188 | | |
189 | | /* custom pretty-print here */ |
190 | 0 | if (type == OBJ_TREE) { |
191 | 0 | const char *ls_args[3] = { NULL }; |
192 | 0 | ls_args[0] = "ls-tree"; |
193 | 0 | ls_args[1] = obj_name; |
194 | 0 | ret = cmd_ls_tree(2, ls_args, NULL); |
195 | 0 | goto cleanup; |
196 | 0 | } |
197 | | |
198 | 0 | if (type == OBJ_BLOB) { |
199 | 0 | ret = stream_blob(&oid); |
200 | 0 | goto cleanup; |
201 | 0 | } |
202 | 0 | buf = repo_read_object_file(the_repository, &oid, &type, |
203 | 0 | &size); |
204 | 0 | if (!buf) |
205 | 0 | die("Cannot read object %s", obj_name); |
206 | | |
207 | 0 | if (use_mailmap) { |
208 | 0 | size_t s = size; |
209 | 0 | buf = replace_idents_using_mailmap(buf, &s); |
210 | 0 | size = cast_size_t_to_ulong(s); |
211 | 0 | } |
212 | | |
213 | | /* otherwise just spit out the data */ |
214 | 0 | break; |
215 | | |
216 | 0 | case 0: |
217 | 0 | { |
218 | 0 | enum object_type exp_type_id = type_from_string(exp_type); |
219 | |
|
220 | 0 | if (exp_type_id == OBJ_BLOB) { |
221 | 0 | struct object_id blob_oid; |
222 | 0 | if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) { |
223 | 0 | char *buffer = repo_read_object_file(the_repository, |
224 | 0 | &oid, |
225 | 0 | &type, |
226 | 0 | &size); |
227 | 0 | const char *target; |
228 | |
|
229 | 0 | if (!buffer) |
230 | 0 | die(_("unable to read %s"), oid_to_hex(&oid)); |
231 | | |
232 | 0 | if (!skip_prefix(buffer, "object ", &target) || |
233 | 0 | get_oid_hex_algop(target, &blob_oid, |
234 | 0 | &hash_algos[oid.algo])) |
235 | 0 | die("%s not a valid tag", oid_to_hex(&oid)); |
236 | 0 | free(buffer); |
237 | 0 | } else |
238 | 0 | oidcpy(&blob_oid, &oid); |
239 | | |
240 | 0 | if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) { |
241 | 0 | ret = stream_blob(&blob_oid); |
242 | 0 | goto cleanup; |
243 | 0 | } |
244 | | /* |
245 | | * we attempted to dereference a tag to a blob |
246 | | * and failed; there may be new dereference |
247 | | * mechanisms this code is not aware of. |
248 | | * fall-back to the usual case. |
249 | | */ |
250 | 0 | } |
251 | 0 | buf = read_object_with_reference(the_repository, &oid, |
252 | 0 | exp_type_id, &size, NULL); |
253 | |
|
254 | 0 | if (use_mailmap) { |
255 | 0 | size_t s = size; |
256 | 0 | buf = replace_idents_using_mailmap(buf, &s); |
257 | 0 | size = cast_size_t_to_ulong(s); |
258 | 0 | } |
259 | 0 | break; |
260 | 0 | } |
261 | 0 | default: |
262 | 0 | die("git cat-file: unknown option: %s", exp_type); |
263 | 0 | } |
264 | | |
265 | 0 | if (!buf) |
266 | 0 | die("git cat-file %s: bad file", obj_name); |
267 | | |
268 | 0 | write_or_die(1, buf, size); |
269 | 0 | ret = 0; |
270 | 0 | cleanup: |
271 | 0 | free(buf); |
272 | 0 | object_context_release(&obj_context); |
273 | 0 | return ret; |
274 | 0 | } |
275 | | |
276 | | struct expand_data { |
277 | | struct object_id oid; |
278 | | enum object_type type; |
279 | | unsigned long size; |
280 | | off_t disk_size; |
281 | | const char *rest; |
282 | | struct object_id delta_base_oid; |
283 | | |
284 | | /* |
285 | | * If mark_query is true, we do not expand anything, but rather |
286 | | * just mark the object_info with items we wish to query. |
287 | | */ |
288 | | int mark_query; |
289 | | |
290 | | /* |
291 | | * Whether to split the input on whitespace before feeding it to |
292 | | * get_sha1; this is decided during the mark_query phase based on |
293 | | * whether we have a %(rest) token in our format. |
294 | | */ |
295 | | int split_on_whitespace; |
296 | | |
297 | | /* |
298 | | * After a mark_query run, this object_info is set up to be |
299 | | * passed to oid_object_info_extended. It will point to the data |
300 | | * elements above, so you can retrieve the response from there. |
301 | | */ |
302 | | struct object_info info; |
303 | | |
304 | | /* |
305 | | * This flag will be true if the requested batch format and options |
306 | | * don't require us to call oid_object_info, which can then be |
307 | | * optimized out. |
308 | | */ |
309 | | unsigned skip_object_info : 1; |
310 | | }; |
311 | | |
312 | | static int is_atom(const char *atom, const char *s, int slen) |
313 | 0 | { |
314 | 0 | int alen = strlen(atom); |
315 | 0 | return alen == slen && !memcmp(atom, s, alen); |
316 | 0 | } |
317 | | |
318 | | static int expand_atom(struct strbuf *sb, const char *atom, int len, |
319 | | struct expand_data *data) |
320 | 0 | { |
321 | 0 | if (is_atom("objectname", atom, len)) { |
322 | 0 | if (!data->mark_query) |
323 | 0 | strbuf_addstr(sb, oid_to_hex(&data->oid)); |
324 | 0 | } else if (is_atom("objecttype", atom, len)) { |
325 | 0 | if (data->mark_query) |
326 | 0 | data->info.typep = &data->type; |
327 | 0 | else |
328 | 0 | strbuf_addstr(sb, type_name(data->type)); |
329 | 0 | } else if (is_atom("objectsize", atom, len)) { |
330 | 0 | if (data->mark_query) |
331 | 0 | data->info.sizep = &data->size; |
332 | 0 | else |
333 | 0 | strbuf_addf(sb, "%"PRIuMAX , (uintmax_t)data->size); |
334 | 0 | } else if (is_atom("objectsize:disk", atom, len)) { |
335 | 0 | if (data->mark_query) |
336 | 0 | data->info.disk_sizep = &data->disk_size; |
337 | 0 | else |
338 | 0 | strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size); |
339 | 0 | } else if (is_atom("rest", atom, len)) { |
340 | 0 | if (data->mark_query) |
341 | 0 | data->split_on_whitespace = 1; |
342 | 0 | else if (data->rest) |
343 | 0 | strbuf_addstr(sb, data->rest); |
344 | 0 | } else if (is_atom("deltabase", atom, len)) { |
345 | 0 | if (data->mark_query) |
346 | 0 | data->info.delta_base_oid = &data->delta_base_oid; |
347 | 0 | else |
348 | 0 | strbuf_addstr(sb, |
349 | 0 | oid_to_hex(&data->delta_base_oid)); |
350 | 0 | } else |
351 | 0 | return 0; |
352 | 0 | return 1; |
353 | 0 | } |
354 | | |
355 | | static void expand_format(struct strbuf *sb, const char *start, |
356 | | struct expand_data *data) |
357 | 0 | { |
358 | 0 | while (strbuf_expand_step(sb, &start)) { |
359 | 0 | const char *end; |
360 | |
|
361 | 0 | if (skip_prefix(start, "%", &start) || *start != '(') |
362 | 0 | strbuf_addch(sb, '%'); |
363 | 0 | else if ((end = strchr(start + 1, ')')) && |
364 | 0 | expand_atom(sb, start + 1, end - start - 1, data)) |
365 | 0 | start = end + 1; |
366 | 0 | else |
367 | 0 | strbuf_expand_bad_format(start, "cat-file"); |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | static void batch_write(struct batch_options *opt, const void *data, int len) |
372 | 0 | { |
373 | 0 | if (opt->buffer_output) { |
374 | 0 | if (fwrite(data, 1, len, stdout) != len) |
375 | 0 | die_errno("unable to write to stdout"); |
376 | 0 | } else |
377 | 0 | write_or_die(1, data, len); |
378 | 0 | } |
379 | | |
380 | | static void print_object_or_die(struct batch_options *opt, struct expand_data *data) |
381 | 0 | { |
382 | 0 | const struct object_id *oid = &data->oid; |
383 | |
|
384 | 0 | assert(data->info.typep); |
385 | | |
386 | 0 | if (data->type == OBJ_BLOB) { |
387 | 0 | if (opt->buffer_output) |
388 | 0 | fflush(stdout); |
389 | 0 | if (opt->transform_mode) { |
390 | 0 | char *contents; |
391 | 0 | unsigned long size; |
392 | |
|
393 | 0 | if (!data->rest) |
394 | 0 | die("missing path for '%s'", oid_to_hex(oid)); |
395 | | |
396 | 0 | if (opt->transform_mode == 'w') { |
397 | 0 | if (filter_object(data->rest, 0100644, oid, |
398 | 0 | &contents, &size)) |
399 | 0 | die("could not convert '%s' %s", |
400 | 0 | oid_to_hex(oid), data->rest); |
401 | 0 | } else if (opt->transform_mode == 'c') { |
402 | 0 | enum object_type type; |
403 | 0 | if (!textconv_object(the_repository, |
404 | 0 | data->rest, 0100644, oid, |
405 | 0 | 1, &contents, &size)) |
406 | 0 | contents = repo_read_object_file(the_repository, |
407 | 0 | oid, |
408 | 0 | &type, |
409 | 0 | &size); |
410 | 0 | if (!contents) |
411 | 0 | die("could not convert '%s' %s", |
412 | 0 | oid_to_hex(oid), data->rest); |
413 | 0 | } else |
414 | 0 | BUG("invalid transform_mode: %c", opt->transform_mode); |
415 | 0 | batch_write(opt, contents, size); |
416 | 0 | free(contents); |
417 | 0 | } else { |
418 | 0 | stream_blob(oid); |
419 | 0 | } |
420 | 0 | } |
421 | 0 | else { |
422 | 0 | enum object_type type; |
423 | 0 | unsigned long size; |
424 | 0 | void *contents; |
425 | |
|
426 | 0 | contents = repo_read_object_file(the_repository, oid, &type, |
427 | 0 | &size); |
428 | 0 | if (!contents) |
429 | 0 | die("object %s disappeared", oid_to_hex(oid)); |
430 | | |
431 | 0 | if (use_mailmap) { |
432 | 0 | size_t s = size; |
433 | 0 | contents = replace_idents_using_mailmap(contents, &s); |
434 | 0 | size = cast_size_t_to_ulong(s); |
435 | 0 | } |
436 | |
|
437 | 0 | if (type != data->type) |
438 | 0 | die("object %s changed type!?", oid_to_hex(oid)); |
439 | 0 | if (data->info.sizep && size != data->size && !use_mailmap) |
440 | 0 | die("object %s changed size!?", oid_to_hex(oid)); |
441 | | |
442 | 0 | batch_write(opt, contents, size); |
443 | 0 | free(contents); |
444 | 0 | } |
445 | 0 | } |
446 | | |
447 | | static void print_default_format(struct strbuf *scratch, struct expand_data *data, |
448 | | struct batch_options *opt) |
449 | 0 | { |
450 | 0 | strbuf_addf(scratch, "%s %s %"PRIuMAX"%c", oid_to_hex(&data->oid), |
451 | 0 | type_name(data->type), |
452 | 0 | (uintmax_t)data->size, opt->output_delim); |
453 | 0 | } |
454 | | |
455 | | /* |
456 | | * If "pack" is non-NULL, then "offset" is the byte offset within the pack from |
457 | | * which the object may be accessed (though note that we may also rely on |
458 | | * data->oid, too). If "pack" is NULL, then offset is ignored. |
459 | | */ |
460 | | static void batch_object_write(const char *obj_name, |
461 | | struct strbuf *scratch, |
462 | | struct batch_options *opt, |
463 | | struct expand_data *data, |
464 | | struct packed_git *pack, |
465 | | off_t offset) |
466 | 0 | { |
467 | 0 | if (!data->skip_object_info) { |
468 | 0 | int ret; |
469 | |
|
470 | 0 | if (use_mailmap) |
471 | 0 | data->info.typep = &data->type; |
472 | |
|
473 | 0 | if (pack) |
474 | 0 | ret = packed_object_info(the_repository, pack, offset, |
475 | 0 | &data->info); |
476 | 0 | else |
477 | 0 | ret = oid_object_info_extended(the_repository, |
478 | 0 | &data->oid, &data->info, |
479 | 0 | OBJECT_INFO_LOOKUP_REPLACE); |
480 | 0 | if (ret < 0) { |
481 | 0 | printf("%s missing%c", |
482 | 0 | obj_name ? obj_name : oid_to_hex(&data->oid), opt->output_delim); |
483 | 0 | fflush(stdout); |
484 | 0 | return; |
485 | 0 | } |
486 | | |
487 | 0 | if (use_mailmap && (data->type == OBJ_COMMIT || data->type == OBJ_TAG)) { |
488 | 0 | size_t s = data->size; |
489 | 0 | char *buf = NULL; |
490 | |
|
491 | 0 | buf = repo_read_object_file(the_repository, &data->oid, &data->type, |
492 | 0 | &data->size); |
493 | 0 | if (!buf) |
494 | 0 | die(_("unable to read %s"), oid_to_hex(&data->oid)); |
495 | 0 | buf = replace_idents_using_mailmap(buf, &s); |
496 | 0 | data->size = cast_size_t_to_ulong(s); |
497 | |
|
498 | 0 | free(buf); |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | strbuf_reset(scratch); |
503 | |
|
504 | 0 | if (!opt->format) { |
505 | 0 | print_default_format(scratch, data, opt); |
506 | 0 | } else { |
507 | 0 | expand_format(scratch, opt->format, data); |
508 | 0 | strbuf_addch(scratch, opt->output_delim); |
509 | 0 | } |
510 | |
|
511 | 0 | batch_write(opt, scratch->buf, scratch->len); |
512 | |
|
513 | 0 | if (opt->batch_mode == BATCH_MODE_CONTENTS) { |
514 | 0 | print_object_or_die(opt, data); |
515 | 0 | batch_write(opt, &opt->output_delim, 1); |
516 | 0 | } |
517 | 0 | } |
518 | | |
519 | | static void batch_one_object(const char *obj_name, |
520 | | struct strbuf *scratch, |
521 | | struct batch_options *opt, |
522 | | struct expand_data *data) |
523 | 0 | { |
524 | 0 | struct object_context ctx = {0}; |
525 | 0 | int flags = |
526 | 0 | GET_OID_HASH_ANY | |
527 | 0 | (opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0); |
528 | 0 | enum get_oid_result result; |
529 | |
|
530 | 0 | result = get_oid_with_context(the_repository, obj_name, |
531 | 0 | flags, &data->oid, &ctx); |
532 | 0 | if (result != FOUND) { |
533 | 0 | switch (result) { |
534 | 0 | case MISSING_OBJECT: |
535 | 0 | printf("%s missing%c", obj_name, opt->output_delim); |
536 | 0 | break; |
537 | 0 | case SHORT_NAME_AMBIGUOUS: |
538 | 0 | printf("%s ambiguous%c", obj_name, opt->output_delim); |
539 | 0 | break; |
540 | 0 | case DANGLING_SYMLINK: |
541 | 0 | printf("dangling %"PRIuMAX"%c%s%c", |
542 | 0 | (uintmax_t)strlen(obj_name), |
543 | 0 | opt->output_delim, obj_name, opt->output_delim); |
544 | 0 | break; |
545 | 0 | case SYMLINK_LOOP: |
546 | 0 | printf("loop %"PRIuMAX"%c%s%c", |
547 | 0 | (uintmax_t)strlen(obj_name), |
548 | 0 | opt->output_delim, obj_name, opt->output_delim); |
549 | 0 | break; |
550 | 0 | case NOT_DIR: |
551 | 0 | printf("notdir %"PRIuMAX"%c%s%c", |
552 | 0 | (uintmax_t)strlen(obj_name), |
553 | 0 | opt->output_delim, obj_name, opt->output_delim); |
554 | 0 | break; |
555 | 0 | default: |
556 | 0 | BUG("unknown get_sha1_with_context result %d\n", |
557 | 0 | result); |
558 | 0 | break; |
559 | 0 | } |
560 | 0 | fflush(stdout); |
561 | |
|
562 | 0 | goto out; |
563 | 0 | } |
564 | | |
565 | 0 | if (ctx.mode == 0) { |
566 | 0 | printf("symlink %"PRIuMAX"%c%s%c", |
567 | 0 | (uintmax_t)ctx.symlink_path.len, |
568 | 0 | opt->output_delim, ctx.symlink_path.buf, opt->output_delim); |
569 | 0 | fflush(stdout); |
570 | 0 | goto out; |
571 | 0 | } |
572 | | |
573 | 0 | batch_object_write(obj_name, scratch, opt, data, NULL, 0); |
574 | |
|
575 | 0 | out: |
576 | 0 | object_context_release(&ctx); |
577 | 0 | } |
578 | | |
579 | | struct object_cb_data { |
580 | | struct batch_options *opt; |
581 | | struct expand_data *expand; |
582 | | struct oidset *seen; |
583 | | struct strbuf *scratch; |
584 | | }; |
585 | | |
586 | | static int batch_object_cb(const struct object_id *oid, void *vdata) |
587 | 0 | { |
588 | 0 | struct object_cb_data *data = vdata; |
589 | 0 | oidcpy(&data->expand->oid, oid); |
590 | 0 | batch_object_write(NULL, data->scratch, data->opt, data->expand, |
591 | 0 | NULL, 0); |
592 | 0 | return 0; |
593 | 0 | } |
594 | | |
595 | | static int collect_loose_object(const struct object_id *oid, |
596 | | const char *path UNUSED, |
597 | | void *data) |
598 | 0 | { |
599 | 0 | oid_array_append(data, oid); |
600 | 0 | return 0; |
601 | 0 | } |
602 | | |
603 | | static int collect_packed_object(const struct object_id *oid, |
604 | | struct packed_git *pack UNUSED, |
605 | | uint32_t pos UNUSED, |
606 | | void *data) |
607 | 0 | { |
608 | 0 | oid_array_append(data, oid); |
609 | 0 | return 0; |
610 | 0 | } |
611 | | |
612 | | static int batch_unordered_object(const struct object_id *oid, |
613 | | struct packed_git *pack, off_t offset, |
614 | | void *vdata) |
615 | 0 | { |
616 | 0 | struct object_cb_data *data = vdata; |
617 | |
|
618 | 0 | if (oidset_insert(data->seen, oid)) |
619 | 0 | return 0; |
620 | | |
621 | 0 | oidcpy(&data->expand->oid, oid); |
622 | 0 | batch_object_write(NULL, data->scratch, data->opt, data->expand, |
623 | 0 | pack, offset); |
624 | 0 | return 0; |
625 | 0 | } |
626 | | |
627 | | static int batch_unordered_loose(const struct object_id *oid, |
628 | | const char *path UNUSED, |
629 | | void *data) |
630 | 0 | { |
631 | 0 | return batch_unordered_object(oid, NULL, 0, data); |
632 | 0 | } |
633 | | |
634 | | static int batch_unordered_packed(const struct object_id *oid, |
635 | | struct packed_git *pack, |
636 | | uint32_t pos, |
637 | | void *data) |
638 | 0 | { |
639 | 0 | return batch_unordered_object(oid, pack, |
640 | 0 | nth_packed_object_offset(pack, pos), |
641 | 0 | data); |
642 | 0 | } |
643 | | |
644 | | typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *, |
645 | | struct strbuf *, struct expand_data *); |
646 | | |
647 | | struct queued_cmd { |
648 | | parse_cmd_fn_t fn; |
649 | | char *line; |
650 | | }; |
651 | | |
652 | | static void parse_cmd_contents(struct batch_options *opt, |
653 | | const char *line, |
654 | | struct strbuf *output, |
655 | | struct expand_data *data) |
656 | 0 | { |
657 | 0 | opt->batch_mode = BATCH_MODE_CONTENTS; |
658 | 0 | batch_one_object(line, output, opt, data); |
659 | 0 | } |
660 | | |
661 | | static void parse_cmd_info(struct batch_options *opt, |
662 | | const char *line, |
663 | | struct strbuf *output, |
664 | | struct expand_data *data) |
665 | 0 | { |
666 | 0 | opt->batch_mode = BATCH_MODE_INFO; |
667 | 0 | batch_one_object(line, output, opt, data); |
668 | 0 | } |
669 | | |
670 | | static void dispatch_calls(struct batch_options *opt, |
671 | | struct strbuf *output, |
672 | | struct expand_data *data, |
673 | | struct queued_cmd *cmd, |
674 | | int nr) |
675 | 0 | { |
676 | 0 | int i; |
677 | |
|
678 | 0 | if (!opt->buffer_output) |
679 | 0 | die(_("flush is only for --buffer mode")); |
680 | | |
681 | 0 | for (i = 0; i < nr; i++) |
682 | 0 | cmd[i].fn(opt, cmd[i].line, output, data); |
683 | |
|
684 | 0 | fflush(stdout); |
685 | 0 | } |
686 | | |
687 | | static void free_cmds(struct queued_cmd *cmd, size_t *nr) |
688 | 0 | { |
689 | 0 | size_t i; |
690 | |
|
691 | 0 | for (i = 0; i < *nr; i++) |
692 | 0 | FREE_AND_NULL(cmd[i].line); |
693 | |
|
694 | 0 | *nr = 0; |
695 | 0 | } |
696 | | |
697 | | |
698 | | static const struct parse_cmd { |
699 | | const char *name; |
700 | | parse_cmd_fn_t fn; |
701 | | unsigned takes_args; |
702 | | } commands[] = { |
703 | | { "contents", parse_cmd_contents, 1}, |
704 | | { "info", parse_cmd_info, 1}, |
705 | | { "flush", NULL, 0}, |
706 | | }; |
707 | | |
708 | | static void batch_objects_command(struct batch_options *opt, |
709 | | struct strbuf *output, |
710 | | struct expand_data *data) |
711 | 0 | { |
712 | 0 | struct strbuf input = STRBUF_INIT; |
713 | 0 | struct queued_cmd *queued_cmd = NULL; |
714 | 0 | size_t alloc = 0, nr = 0; |
715 | |
|
716 | 0 | while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) { |
717 | 0 | int i; |
718 | 0 | const struct parse_cmd *cmd = NULL; |
719 | 0 | const char *p = NULL, *cmd_end; |
720 | 0 | struct queued_cmd call = {0}; |
721 | |
|
722 | 0 | if (!input.len) |
723 | 0 | die(_("empty command in input")); |
724 | 0 | if (isspace(*input.buf)) |
725 | 0 | die(_("whitespace before command: '%s'"), input.buf); |
726 | | |
727 | 0 | for (i = 0; i < ARRAY_SIZE(commands); i++) { |
728 | 0 | if (!skip_prefix(input.buf, commands[i].name, &cmd_end)) |
729 | 0 | continue; |
730 | | |
731 | 0 | cmd = &commands[i]; |
732 | 0 | if (cmd->takes_args) { |
733 | 0 | if (*cmd_end != ' ') |
734 | 0 | die(_("%s requires arguments"), |
735 | 0 | commands[i].name); |
736 | | |
737 | 0 | p = cmd_end + 1; |
738 | 0 | } else if (*cmd_end) { |
739 | 0 | die(_("%s takes no arguments"), |
740 | 0 | commands[i].name); |
741 | 0 | } |
742 | | |
743 | 0 | break; |
744 | 0 | } |
745 | | |
746 | 0 | if (!cmd) |
747 | 0 | die(_("unknown command: '%s'"), input.buf); |
748 | | |
749 | 0 | if (!strcmp(cmd->name, "flush")) { |
750 | 0 | dispatch_calls(opt, output, data, queued_cmd, nr); |
751 | 0 | free_cmds(queued_cmd, &nr); |
752 | 0 | } else if (!opt->buffer_output) { |
753 | 0 | cmd->fn(opt, p, output, data); |
754 | 0 | } else { |
755 | 0 | ALLOC_GROW(queued_cmd, nr + 1, alloc); |
756 | 0 | call.fn = cmd->fn; |
757 | 0 | call.line = xstrdup_or_null(p); |
758 | 0 | queued_cmd[nr++] = call; |
759 | 0 | } |
760 | 0 | } |
761 | | |
762 | 0 | if (opt->buffer_output && |
763 | 0 | nr && |
764 | 0 | !git_env_bool("GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT", 0)) { |
765 | 0 | dispatch_calls(opt, output, data, queued_cmd, nr); |
766 | 0 | free_cmds(queued_cmd, &nr); |
767 | 0 | } |
768 | |
|
769 | 0 | free_cmds(queued_cmd, &nr); |
770 | 0 | free(queued_cmd); |
771 | 0 | strbuf_release(&input); |
772 | 0 | } |
773 | | |
774 | 0 | #define DEFAULT_FORMAT "%(objectname) %(objecttype) %(objectsize)" |
775 | | |
776 | | static int batch_objects(struct batch_options *opt) |
777 | 0 | { |
778 | 0 | struct strbuf input = STRBUF_INIT; |
779 | 0 | struct strbuf output = STRBUF_INIT; |
780 | 0 | struct expand_data data; |
781 | 0 | int save_warning; |
782 | 0 | int retval = 0; |
783 | | |
784 | | /* |
785 | | * Expand once with our special mark_query flag, which will prime the |
786 | | * object_info to be handed to oid_object_info_extended for each |
787 | | * object. |
788 | | */ |
789 | 0 | memset(&data, 0, sizeof(data)); |
790 | 0 | data.mark_query = 1; |
791 | 0 | expand_format(&output, |
792 | 0 | opt->format ? opt->format : DEFAULT_FORMAT, |
793 | 0 | &data); |
794 | 0 | data.mark_query = 0; |
795 | 0 | strbuf_release(&output); |
796 | 0 | if (opt->transform_mode) |
797 | 0 | data.split_on_whitespace = 1; |
798 | |
|
799 | 0 | if (opt->format && !strcmp(opt->format, DEFAULT_FORMAT)) |
800 | 0 | opt->format = NULL; |
801 | | /* |
802 | | * If we are printing out the object, then always fill in the type, |
803 | | * since we will want to decide whether or not to stream. |
804 | | */ |
805 | 0 | if (opt->batch_mode == BATCH_MODE_CONTENTS) |
806 | 0 | data.info.typep = &data.type; |
807 | |
|
808 | 0 | if (opt->all_objects) { |
809 | 0 | struct object_cb_data cb; |
810 | 0 | struct object_info empty = OBJECT_INFO_INIT; |
811 | |
|
812 | 0 | if (!memcmp(&data.info, &empty, sizeof(empty))) |
813 | 0 | data.skip_object_info = 1; |
814 | |
|
815 | 0 | if (repo_has_promisor_remote(the_repository)) |
816 | 0 | warning("This repository uses promisor remotes. Some objects may not be loaded."); |
817 | |
|
818 | 0 | disable_replace_refs(); |
819 | |
|
820 | 0 | cb.opt = opt; |
821 | 0 | cb.expand = &data; |
822 | 0 | cb.scratch = &output; |
823 | |
|
824 | 0 | if (opt->unordered) { |
825 | 0 | struct oidset seen = OIDSET_INIT; |
826 | |
|
827 | 0 | cb.seen = &seen; |
828 | |
|
829 | 0 | for_each_loose_object(batch_unordered_loose, &cb, 0); |
830 | 0 | for_each_packed_object(batch_unordered_packed, &cb, |
831 | 0 | FOR_EACH_OBJECT_PACK_ORDER); |
832 | |
|
833 | 0 | oidset_clear(&seen); |
834 | 0 | } else { |
835 | 0 | struct oid_array sa = OID_ARRAY_INIT; |
836 | |
|
837 | 0 | for_each_loose_object(collect_loose_object, &sa, 0); |
838 | 0 | for_each_packed_object(collect_packed_object, &sa, 0); |
839 | |
|
840 | 0 | oid_array_for_each_unique(&sa, batch_object_cb, &cb); |
841 | |
|
842 | 0 | oid_array_clear(&sa); |
843 | 0 | } |
844 | |
|
845 | 0 | strbuf_release(&output); |
846 | 0 | return 0; |
847 | 0 | } |
848 | | |
849 | | /* |
850 | | * We are going to call get_sha1 on a potentially very large number of |
851 | | * objects. In most large cases, these will be actual object sha1s. The |
852 | | * cost to double-check that each one is not also a ref (just so we can |
853 | | * warn) ends up dwarfing the actual cost of the object lookups |
854 | | * themselves. We can work around it by just turning off the warning. |
855 | | */ |
856 | 0 | save_warning = warn_on_object_refname_ambiguity; |
857 | 0 | warn_on_object_refname_ambiguity = 0; |
858 | |
|
859 | 0 | if (opt->batch_mode == BATCH_MODE_QUEUE_AND_DISPATCH) { |
860 | 0 | batch_objects_command(opt, &output, &data); |
861 | 0 | goto cleanup; |
862 | 0 | } |
863 | | |
864 | 0 | while (strbuf_getdelim_strip_crlf(&input, stdin, opt->input_delim) != EOF) { |
865 | 0 | if (data.split_on_whitespace) { |
866 | | /* |
867 | | * Split at first whitespace, tying off the beginning |
868 | | * of the string and saving the remainder (or NULL) in |
869 | | * data.rest. |
870 | | */ |
871 | 0 | char *p = strpbrk(input.buf, " \t"); |
872 | 0 | if (p) { |
873 | 0 | while (*p && strchr(" \t", *p)) |
874 | 0 | *p++ = '\0'; |
875 | 0 | } |
876 | 0 | data.rest = p; |
877 | 0 | } |
878 | |
|
879 | 0 | batch_one_object(input.buf, &output, opt, &data); |
880 | 0 | } |
881 | |
|
882 | 0 | cleanup: |
883 | 0 | strbuf_release(&input); |
884 | 0 | strbuf_release(&output); |
885 | 0 | warn_on_object_refname_ambiguity = save_warning; |
886 | 0 | return retval; |
887 | 0 | } |
888 | | |
889 | | static int git_cat_file_config(const char *var, const char *value, |
890 | | const struct config_context *ctx, void *cb) |
891 | 0 | { |
892 | 0 | if (userdiff_config(var, value) < 0) |
893 | 0 | return -1; |
894 | | |
895 | 0 | return git_default_config(var, value, ctx, cb); |
896 | 0 | } |
897 | | |
898 | | static int batch_option_callback(const struct option *opt, |
899 | | const char *arg, |
900 | | int unset) |
901 | 0 | { |
902 | 0 | struct batch_options *bo = opt->value; |
903 | |
|
904 | 0 | BUG_ON_OPT_NEG(unset); |
905 | | |
906 | 0 | if (bo->enabled) { |
907 | 0 | return error(_("only one batch option may be specified")); |
908 | 0 | } |
909 | | |
910 | 0 | bo->enabled = 1; |
911 | |
|
912 | 0 | if (!strcmp(opt->long_name, "batch")) |
913 | 0 | bo->batch_mode = BATCH_MODE_CONTENTS; |
914 | 0 | else if (!strcmp(opt->long_name, "batch-check")) |
915 | 0 | bo->batch_mode = BATCH_MODE_INFO; |
916 | 0 | else if (!strcmp(opt->long_name, "batch-command")) |
917 | 0 | bo->batch_mode = BATCH_MODE_QUEUE_AND_DISPATCH; |
918 | 0 | else |
919 | 0 | BUG("%s given to batch-option-callback", opt->long_name); |
920 | | |
921 | 0 | bo->format = arg; |
922 | |
|
923 | 0 | return 0; |
924 | 0 | } |
925 | | |
926 | | int cmd_cat_file(int argc, const char **argv, const char *prefix) |
927 | 0 | { |
928 | 0 | int opt = 0; |
929 | 0 | int opt_cw = 0; |
930 | 0 | int opt_epts = 0; |
931 | 0 | const char *exp_type = NULL, *obj_name = NULL; |
932 | 0 | struct batch_options batch = {0}; |
933 | 0 | int unknown_type = 0; |
934 | 0 | int input_nul_terminated = 0; |
935 | 0 | int nul_terminated = 0; |
936 | |
|
937 | 0 | const char * const usage[] = { |
938 | 0 | N_("git cat-file <type> <object>"), |
939 | 0 | N_("git cat-file (-e | -p) <object>"), |
940 | 0 | N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"), |
941 | 0 | N_("git cat-file (--textconv | --filters)\n" |
942 | 0 | " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"), |
943 | 0 | N_("git cat-file (--batch | --batch-check | --batch-command) [--batch-all-objects]\n" |
944 | 0 | " [--buffer] [--follow-symlinks] [--unordered]\n" |
945 | 0 | " [--textconv | --filters] [-Z]"), |
946 | 0 | NULL |
947 | 0 | }; |
948 | 0 | const struct option options[] = { |
949 | | /* Simple queries */ |
950 | 0 | OPT_GROUP(N_("Check object existence or emit object contents")), |
951 | 0 | OPT_CMDMODE('e', NULL, &opt, |
952 | 0 | N_("check if <object> exists"), 'e'), |
953 | 0 | OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'), |
954 | |
|
955 | 0 | OPT_GROUP(N_("Emit [broken] object attributes")), |
956 | 0 | OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'), |
957 | 0 | OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), |
958 | 0 | OPT_BOOL(0, "allow-unknown-type", &unknown_type, |
959 | 0 | N_("allow -s and -t to work with broken/corrupt objects")), |
960 | 0 | OPT_BOOL(0, "use-mailmap", &use_mailmap, N_("use mail map file")), |
961 | 0 | OPT_ALIAS(0, "mailmap", "use-mailmap"), |
962 | | /* Batch mode */ |
963 | 0 | OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), |
964 | 0 | OPT_CALLBACK_F(0, "batch", &batch, N_("format"), |
965 | 0 | N_("show full <object> or <rev> contents"), |
966 | 0 | PARSE_OPT_OPTARG | PARSE_OPT_NONEG, |
967 | 0 | batch_option_callback), |
968 | 0 | OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"), |
969 | 0 | N_("like --batch, but don't emit <contents>"), |
970 | 0 | PARSE_OPT_OPTARG | PARSE_OPT_NONEG, |
971 | 0 | batch_option_callback), |
972 | 0 | OPT_BOOL_F('z', NULL, &input_nul_terminated, N_("stdin is NUL-terminated"), |
973 | 0 | PARSE_OPT_HIDDEN), |
974 | 0 | OPT_BOOL('Z', NULL, &nul_terminated, N_("stdin and stdout is NUL-terminated")), |
975 | 0 | OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"), |
976 | 0 | N_("read commands from stdin"), |
977 | 0 | PARSE_OPT_OPTARG | PARSE_OPT_NONEG, |
978 | 0 | batch_option_callback), |
979 | 0 | OPT_CMDMODE(0, "batch-all-objects", &opt, |
980 | 0 | N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'), |
981 | | /* Batch-specific options */ |
982 | 0 | OPT_GROUP(N_("Change or optimize batch output")), |
983 | 0 | OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), |
984 | 0 | OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, |
985 | 0 | N_("follow in-tree symlinks")), |
986 | 0 | OPT_BOOL(0, "unordered", &batch.unordered, |
987 | 0 | N_("do not order objects before emitting them")), |
988 | | /* Textconv options, stand-ole*/ |
989 | 0 | OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")), |
990 | 0 | OPT_CMDMODE(0, "textconv", &opt, |
991 | 0 | N_("run textconv on object's content"), 'c'), |
992 | 0 | OPT_CMDMODE(0, "filters", &opt, |
993 | 0 | N_("run filters on object's content"), 'w'), |
994 | 0 | OPT_STRING(0, "path", &force_path, N_("blob|tree"), |
995 | 0 | N_("use a <path> for (--textconv | --filters); Not with 'batch'")), |
996 | 0 | OPT_END() |
997 | 0 | }; |
998 | |
|
999 | 0 | git_config(git_cat_file_config, NULL); |
1000 | |
|
1001 | 0 | batch.buffer_output = -1; |
1002 | |
|
1003 | 0 | argc = parse_options(argc, argv, prefix, options, usage, 0); |
1004 | 0 | opt_cw = (opt == 'c' || opt == 'w'); |
1005 | 0 | opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); |
1006 | |
|
1007 | 0 | if (use_mailmap) |
1008 | 0 | read_mailmap(&mailmap); |
1009 | | |
1010 | | /* --batch-all-objects? */ |
1011 | 0 | if (opt == 'b') |
1012 | 0 | batch.all_objects = 1; |
1013 | | |
1014 | | /* Option compatibility */ |
1015 | 0 | if (force_path && !opt_cw) |
1016 | 0 | usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"), |
1017 | 0 | usage, options, |
1018 | 0 | "--path", _("path|tree-ish"), "--filters", |
1019 | 0 | "--textconv"); |
1020 | | |
1021 | | /* Option compatibility with batch mode */ |
1022 | 0 | if (batch.enabled) |
1023 | 0 | ; |
1024 | 0 | else if (batch.follow_symlinks) |
1025 | 0 | usage_msg_optf(_("'%s' requires a batch mode"), usage, options, |
1026 | 0 | "--follow-symlinks"); |
1027 | 0 | else if (batch.buffer_output >= 0) |
1028 | 0 | usage_msg_optf(_("'%s' requires a batch mode"), usage, options, |
1029 | 0 | "--buffer"); |
1030 | 0 | else if (batch.all_objects) |
1031 | 0 | usage_msg_optf(_("'%s' requires a batch mode"), usage, options, |
1032 | 0 | "--batch-all-objects"); |
1033 | 0 | else if (input_nul_terminated) |
1034 | 0 | usage_msg_optf(_("'%s' requires a batch mode"), usage, options, |
1035 | 0 | "-z"); |
1036 | 0 | else if (nul_terminated) |
1037 | 0 | usage_msg_optf(_("'%s' requires a batch mode"), usage, options, |
1038 | 0 | "-Z"); |
1039 | | |
1040 | 0 | batch.input_delim = batch.output_delim = '\n'; |
1041 | 0 | if (input_nul_terminated) |
1042 | 0 | batch.input_delim = '\0'; |
1043 | 0 | if (nul_terminated) |
1044 | 0 | batch.input_delim = batch.output_delim = '\0'; |
1045 | | |
1046 | | /* Batch defaults */ |
1047 | 0 | if (batch.buffer_output < 0) |
1048 | 0 | batch.buffer_output = batch.all_objects; |
1049 | |
|
1050 | 0 | prepare_repo_settings(the_repository); |
1051 | 0 | the_repository->settings.command_requires_full_index = 0; |
1052 | | |
1053 | | /* Return early if we're in batch mode? */ |
1054 | 0 | if (batch.enabled) { |
1055 | 0 | if (opt_cw) |
1056 | 0 | batch.transform_mode = opt; |
1057 | 0 | else if (opt && opt != 'b') |
1058 | 0 | usage_msg_optf(_("'-%c' is incompatible with batch mode"), |
1059 | 0 | usage, options, opt); |
1060 | 0 | else if (argc) |
1061 | 0 | usage_msg_opt(_("batch modes take no arguments"), usage, |
1062 | 0 | options); |
1063 | | |
1064 | 0 | return batch_objects(&batch); |
1065 | 0 | } |
1066 | | |
1067 | 0 | if (opt) { |
1068 | 0 | if (!argc && opt == 'c') |
1069 | 0 | usage_msg_optf(_("<rev> required with '%s'"), |
1070 | 0 | usage, options, "--textconv"); |
1071 | 0 | else if (!argc && opt == 'w') |
1072 | 0 | usage_msg_optf(_("<rev> required with '%s'"), |
1073 | 0 | usage, options, "--filters"); |
1074 | 0 | else if (!argc && opt_epts) |
1075 | 0 | usage_msg_optf(_("<object> required with '-%c'"), |
1076 | 0 | usage, options, opt); |
1077 | 0 | else if (argc == 1) |
1078 | 0 | obj_name = argv[0]; |
1079 | 0 | else |
1080 | 0 | usage_msg_opt(_("too many arguments"), usage, options); |
1081 | 0 | } else if (!argc) { |
1082 | 0 | usage_with_options(usage, options); |
1083 | 0 | } else if (argc != 2) { |
1084 | 0 | usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"), |
1085 | 0 | usage, options, argc); |
1086 | 0 | } else if (argc) { |
1087 | 0 | exp_type = argv[0]; |
1088 | 0 | obj_name = argv[1]; |
1089 | 0 | } |
1090 | | |
1091 | 0 | if (unknown_type && opt != 't' && opt != 's') |
1092 | 0 | die("git cat-file --allow-unknown-type: use with -s or -t"); |
1093 | 0 | return cat_one_file(opt, exp_type, obj_name, unknown_type); |
1094 | 0 | } |