cat-file: add --follow-symlinks to --batch
This wires the in-repo-symlink following code through to the cat-file builtin. In the event of an out-of-repo link, cat-file will print the link in a new format. Signed-off-by: David Turner <dturner@twopensource.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
David Turner committed
May 20, 2015 at 13:03 UTC
122d53464b29d3ac20891c5ee2f75ac5ecbb7b20
3 files changed
+348
-7
Documentation/git-cat-file.txt
+98
-1
@@ -10,7 +10,7 @@ SYNOPSIS
10
--------
11
[verse]
12
'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object>
13
-'git cat-file' (--batch | --batch-check) < <list-of-objects>
13
+'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>
14
15
DESCRIPTION
16
-----------
@@ -69,6 +69,62 @@ OPTIONS
69
not be combined with any other options or arguments. See the
70
section `BATCH OUTPUT` below for details.
71
72
+--follow-symlinks::
73
+ With --batch or --batch-check, follow symlinks inside the
74
+ repository when requesting objects with extended SHA-1
75
+ expressions of the form tree-ish:path-in-tree. Instead of
76
+ providing output about the link itself, provide output about
77
+ the linked-to object. If a symlink points outside the
78
+ tree-ish (e.g. a link to /foo or a root-level link to ../foo),
79
+ the portion of the link which is outside the tree will be
80
+ printed.
81
++
82
+This option does not (currently) work correctly when an object in the
83
+index is specified (e.g. `:link` instead of `HEAD:link`) rather than
84
+one in the tree.
85
++
86
+This option cannot (currently) be used unless `--batch` or
87
+`--batch-check` is used.
88
++
89
+For example, consider a git repository containing:
90
++
91
+--
92
+ f: a file containing "hello\n"
93
+ link: a symlink to f
94
+ dir/link: a symlink to ../f
95
+ plink: a symlink to ../f
96
+ alink: a symlink to /etc/passwd
97
+--
98
++
99
+For a regular file `f`, `echo HEAD:f | git cat-file --batch` would print
100
++
101
+--
102
+ ce013625030ba8dba906f756967f9e9ca394464a blob 6
103
+--
104
++
105
+And `echo HEAD:link | git cat-file --batch --follow-symlinks` would
106
+print the same thing, as would `HEAD:dir/link`, as they both point at
107
+`HEAD:f`.
108
++
109
+Without `--follow-symlinks`, these would print data about the symlink
110
+itself. In the case of `HEAD:link`, you would see
111
++
112
+--
113
+ 4d1ae35ba2c8ec712fa2a379db44ad639ca277bd blob 1
114
+--
115
++
116
+Both `plink` and `alink` point outside the tree, so they would
117
+respectively print:
118
++
119
+--
120
+ symlink 4
121
+ ../f
122
+
123
+ symlink 11
124
+ /etc/passwd
125
+--
126
+
127
+
128
OUTPUT
129
------
130
If '-t' is specified, one of the <type>.
@@ -148,6 +204,47 @@ the repository, then `cat-file` will ignore any custom format and print:
204
<object> SP missing LF
205
------------
206
207
+If --follow-symlinks is used, and a symlink in the repository points
208
+outside the repository, then `cat-file` will ignore any custom format
209
+and print:
210
+
211
+------------
212
+symlink SP <size> LF
213
+<symlink> LF
214
+------------
215
+
216
+The symlink will either be absolute (beginning with a /), or relative
217
+to the tree root. For instance, if dir/link points to ../../foo, then
218
+<symlink> will be ../foo. <size> is the size of the symlink in bytes.
219
+
220
+If --follow-symlinks is used, the following error messages will be
221
+displayed:
222
+
223
+------------
224
+<object> SP missing LF
225
+------------
226
+is printed when the initial symlink requested does not exist.
227
+
228
+------------
229
+dangling SP <size> LF
230
+<object> LF
231
+------------
232
+is printed when the initial symlink exists, but something that
233
+it (transitive-of) points to does not.
234
+
235
+------------
236
+loop SP <size> LF
237
+<object> LF
238
+------------
239
+is printed for symlink loops (or any symlinks that
240
+require more than 40 link resolutions to resolve).
241
+
242
+------------
243
+notdir SP <size> LF
244
+<object> LF
245
+------------
246
+is printed when, during symlink resolution, a file is used as a
247
+directory name.
248
249
CAVEATS
250
-------
builtin/cat-file.c
+45
-6
@@ -8,6 +8,7 @@
8
#include "parse-options.h"
9
#include "userdiff.h"
10
#include "streaming.h"
11
+#include "tree-walk.h"
12
13
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
14
{
@@ -224,6 +225,7 @@ static void print_object_or_die(int fd, struct expand_data *data)
225
226
struct batch_options {
227
int enabled;
228
+ int follow_symlinks;
229
int print_contents;
230
const char *format;
231
};
@@ -232,12 +234,44 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
234
struct expand_data *data)
235
{
236
struct strbuf buf = STRBUF_INIT;
237
+ struct object_context ctx;
238
+ int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
239
+ enum follow_symlinks_result result;
240
241
if (!obj_name)
242
return 1;
243
239
- if (get_sha1(obj_name, data->sha1)) {
240
- printf("%s missing\n", obj_name);
244
+ result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
245
+ if (result != FOUND) {
246
+ switch (result) {
247
+ case MISSING_OBJECT:
248
+ printf("%s missing\n", obj_name);
249
+ break;
250
+ case DANGLING_SYMLINK:
251
+ printf("dangling %"PRIuMAX"\n%s\n",
252
+ (uintmax_t)strlen(obj_name), obj_name);
253
+ break;
254
+ case SYMLINK_LOOP:
255
+ printf("loop %"PRIuMAX"\n%s\n",
256
+ (uintmax_t)strlen(obj_name), obj_name);
257
+ break;
258
+ case NOT_DIR:
259
+ printf("notdir %"PRIuMAX"\n%s\n",
260
+ (uintmax_t)strlen(obj_name), obj_name);
261
+ break;
262
+ default:
263
+ die("BUG: unknown get_sha1_with_context result %d\n",
264
+ result);
265
+ break;
266
+ }
267
+ fflush(stdout);
268
+ return 0;
269
+ }
270
+
271
+ if (ctx.mode == 0) {
272
+ printf("symlink %"PRIuMAX"\n%s\n",
273
+ (uintmax_t)ctx.symlink_path.len,
274
+ ctx.symlink_path.buf);
275
fflush(stdout);
276
return 0;
277
}
@@ -324,7 +358,7 @@ static int batch_objects(struct batch_options *opt)
358
359
static const char * const cat_file_usage[] = {
360
N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"),
327
- N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
361
+ N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"),
362
NULL
363
};
364
@@ -342,9 +376,8 @@ static int batch_option_callback(const struct option *opt,
376
{
377
struct batch_options *bo = opt->value;
378
345
- if (unset) {
346
- memset(bo, 0, sizeof(*bo));
347
- return 0;
379
+ if (bo->enabled) {
380
+ return 1;
381
}
382
383
bo->enabled = 1;
@@ -375,6 +408,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
408
{ OPTION_CALLBACK, 0, "batch-check", &batch, "format",
409
N_("show info about objects fed from the standard input"),
410
PARSE_OPT_OPTARG, batch_option_callback },
411
+ OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
412
+ N_("follow in-tree symlinks (used with --batch or --batch-check)")),
413
OPT_END()
414
};
415
@@ -402,6 +437,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
437
usage_with_options(cat_file_usage, options);
438
}
439
440
+ if (batch.follow_symlinks && !batch.enabled) {
441
+ usage_with_options(cat_file_usage, options);
442
+ }
443
+
444
if (batch.enabled)
445
return batch_objects(&batch);
446
t/t1006-cat-file.sh
+205
@@ -189,6 +189,13 @@ do
189
'
190
done
191
192
+for opt in t s e p
193
+do
194
+ test_expect_success "Passing -$opt with --follow-symlinks fails" '
195
+ test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1
196
+ '
197
+done
198
+
199
test_expect_success "--batch-check for a non-existent named object" '
200
test "foobar42 missing
201
foobar84 missing" = \
@@ -296,4 +303,202 @@ test_expect_success '%(deltabase) reports packed delta bases' '
303
}
304
'
305
306
+# Tests for git cat-file --follow-symlinks
307
+test_expect_success 'prep for symlink tests' '
308
+ echo_without_newline "$hello_content" >morx &&
309
+ test_ln_s_add morx same-dir-link &&
310
+ test_ln_s_add dir link-to-dir &&
311
+ test_ln_s_add ../fleem out-of-repo-link &&
312
+ test_ln_s_add .. out-of-repo-link-dir &&
313
+ test_ln_s_add same-dir-link link-to-link &&
314
+ test_ln_s_add nope broken-same-dir-link &&
315
+ mkdir dir &&
316
+ test_ln_s_add ../morx dir/parent-dir-link &&
317
+ test_ln_s_add .. dir/link-dir &&
318
+ test_ln_s_add ../../escape dir/out-of-repo-link &&
319
+ test_ln_s_add ../.. dir/out-of-repo-link-dir &&
320
+ test_ln_s_add nope dir/broken-link-in-dir &&
321
+ mkdir dir/subdir &&
322
+ test_ln_s_add ../../morx dir/subdir/grandparent-dir-link &&
323
+ test_ln_s_add ../../../great-escape dir/subdir/out-of-repo-link &&
324
+ test_ln_s_add ../../.. dir/subdir/out-of-repo-link-dir &&
325
+ test_ln_s_add ../../../ dir/subdir/out-of-repo-link-dir-trailing &&
326
+ test_ln_s_add ../parent-dir-link dir/subdir/parent-dir-link-to-link &&
327
+ echo_without_newline "$hello_content" >dir/subdir/ind2 &&
328
+ echo_without_newline "$hello_content" >dir/ind1 &&
329
+ test_ln_s_add dir dirlink &&
330
+ test_ln_s_add dir/subdir subdirlink &&
331
+ test_ln_s_add subdir/ind2 dir/link-to-child &&
332
+ test_ln_s_add dir/link-to-child link-to-down-link &&
333
+ test_ln_s_add dir/.. up-down &&
334
+ test_ln_s_add dir/../ up-down-trailing &&
335
+ test_ln_s_add dir/../morx up-down-file &&
336
+ test_ln_s_add dir/../../morx up-up-down-file &&
337
+ test_ln_s_add subdirlink/../../morx up-two-down-file &&
338
+ test_ln_s_add loop1 loop2 &&
339
+ test_ln_s_add loop2 loop1 &&
340
+ git add morx dir/subdir/ind2 dir/ind1 &&
341
+ git commit -am "test" &&
342
+ echo $hello_sha1 blob $hello_size >found
343
+'
344
+
345
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' '
346
+ echo HEAD:morx | git cat-file --batch-check --follow-symlinks >actual &&
347
+ test_cmp found actual &&
348
+ echo HEAD:nope missing >expect &&
349
+ echo HEAD:nope | git cat-file --batch-check --follow-symlinks >actual &&
350
+ test_cmp expect actual
351
+'
352
+
353
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '
354
+ echo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
355
+ test_cmp found actual
356
+'
357
+
358
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, links to dirs' '
359
+ echo HEAD:link-to-dir/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
360
+ test_cmp found actual
361
+'
362
+
363
+
364
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for broken in-repo, same-dir links' '
365
+ echo dangling 25 >expect &&
366
+ echo HEAD:broken-same-dir-link >>expect &&
367
+ echo HEAD:broken-same-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
368
+ test_cmp expect actual
369
+'
370
+
371
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for same-dir links-to-links' '
372
+ echo HEAD:link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
373
+ test_cmp found actual
374
+'
375
+
376
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for parent-dir links' '
377
+ echo HEAD:dir/parent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
378
+ test_cmp found actual &&
379
+ echo notdir 29 >expect &&
380
+ echo HEAD:dir/parent-dir-link/nope >>expect &&
381
+ echo HEAD:dir/parent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
382
+ test_cmp expect actual
383
+'
384
+
385
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for .. links' '
386
+ echo dangling 22 >expect &&
387
+ echo HEAD:dir/link-dir/nope >>expect &&
388
+ echo HEAD:dir/link-dir/nope | git cat-file --batch-check --follow-symlinks >actual &&
389
+ test_cmp expect actual &&
390
+ echo HEAD:dir/link-dir/morx | git cat-file --batch-check --follow-symlinks >actual &&
391
+ test_cmp found actual &&
392
+ echo dangling 27 >expect &&
393
+ echo HEAD:dir/broken-link-in-dir >>expect &&
394
+ echo HEAD:dir/broken-link-in-dir | git cat-file --batch-check --follow-symlinks >actual &&
395
+ test_cmp expect actual
396
+'
397
+
398
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for ../.. links' '
399
+ echo notdir 41 >expect &&
400
+ echo HEAD:dir/subdir/grandparent-dir-link/nope >>expect &&
401
+ echo HEAD:dir/subdir/grandparent-dir-link/nope | git cat-file --batch-check --follow-symlinks >actual &&
402
+ test_cmp expect actual &&
403
+ echo HEAD:dir/subdir/grandparent-dir-link | git cat-file --batch-check --follow-symlinks >actual &&
404
+ test_cmp found actual &&
405
+ echo HEAD:dir/subdir/parent-dir-link-to-link | git cat-file --batch-check --follow-symlinks >actual &&
406
+ test_cmp found actual
407
+'
408
+
409
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ links' '
410
+ echo dangling 17 >expect &&
411
+ echo HEAD:dirlink/morx >>expect &&
412
+ echo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
413
+ test_cmp expect actual &&
414
+ echo $hello_sha1 blob $hello_size >expect &&
415
+ echo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks >actual &&
416
+ test_cmp expect actual
417
+'
418
+
419
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/subdir links' '
420
+ echo dangling 20 >expect &&
421
+ echo HEAD:subdirlink/morx >>expect &&
422
+ echo HEAD:subdirlink/morx | git cat-file --batch-check --follow-symlinks >actual &&
423
+ test_cmp expect actual &&
424
+ echo HEAD:subdirlink/ind2 | git cat-file --batch-check --follow-symlinks >actual &&
425
+ test_cmp found actual
426
+'
427
+
428
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir ->subdir links' '
429
+ echo notdir 27 >expect &&
430
+ echo HEAD:dir/link-to-child/morx >>expect &&
431
+ echo HEAD:dir/link-to-child/morx | git cat-file --batch-check --follow-symlinks >actual &&
432
+ test_cmp expect actual &&
433
+ echo HEAD:dir/link-to-child | git cat-file --batch-check --follow-symlinks >actual &&
434
+ test_cmp found actual &&
435
+ echo HEAD:link-to-down-link | git cat-file --batch-check --follow-symlinks >actual &&
436
+ test_cmp found actual
437
+'
438
+
439
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks' '
440
+ echo symlink 8 >expect &&
441
+ echo ../fleem >>expect &&
442
+ echo HEAD:out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
443
+ test_cmp expect actual &&
444
+ echo symlink 2 >expect &&
445
+ echo .. >>expect &&
446
+ echo HEAD:out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
447
+ test_cmp expect actual
448
+'
449
+
450
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in dirs' '
451
+ echo symlink 9 >expect &&
452
+ echo ../escape >>expect &&
453
+ echo HEAD:dir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
454
+ test_cmp expect actual &&
455
+ echo symlink 2 >expect &&
456
+ echo .. >>expect &&
457
+ echo HEAD:dir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
458
+ test_cmp expect actual
459
+'
460
+
461
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in subdirs' '
462
+ echo symlink 15 >expect &&
463
+ echo ../great-escape >>expect &&
464
+ echo HEAD:dir/subdir/out-of-repo-link | git cat-file --batch-check --follow-symlinks >actual &&
465
+ test_cmp expect actual &&
466
+ echo symlink 2 >expect &&
467
+ echo .. >>expect &&
468
+ echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
469
+ test_cmp expect actual &&
470
+ echo symlink 3 >expect &&
471
+ echo ../ >>expect &&
472
+ echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual &&
473
+ test_cmp expect actual
474
+'
475
+
476
+test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '
477
+ echo HEAD: | git cat-file --batch-check >expect &&
478
+ echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
479
+ test_cmp expect actual &&
480
+ echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
481
+ test_cmp expect actual &&
482
+ echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
483
+ test_cmp found actual &&
484
+ echo symlink 7 >expect &&
485
+ echo ../morx >>expect &&
486
+ echo HEAD:up-up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
487
+ test_cmp expect actual &&
488
+ echo HEAD:up-two-down-file | git cat-file --batch-check --follow-symlinks >actual &&
489
+ test_cmp found actual
490
+'
491
+
492
+test_expect_success 'git cat-file --batch-check --follow-symlink breaks loops' '
493
+ echo loop 10 >expect &&
494
+ echo HEAD:loop1 >>expect &&
495
+ echo HEAD:loop1 | git cat-file --batch-check --follow-symlinks >actual &&
496
+ test_cmp expect actual
497
+'
498
+
499
+test_expect_success 'git cat-file --batch --follow-symlink returns correct sha and mode' '
500
+ echo HEAD:morx | git cat-file --batch >expect &&
501
+ echo HEAD:morx | git cat-file --batch --follow-symlinks >actual &&
502
+ test_cmp expect actual
503
+'
504
test_done