Skip to content

Commit 67f0b6f

Browse files
committed
Merge branch 'dt/cat-file-follow-symlinks'
"git cat-file --batch(-check)" learned the "--follow-symlinks" option that follows an in-tree symbolic link when asked about an object via extended SHA-1 syntax, e.g. HEAD:RelNotes that points at Documentation/RelNotes/2.5.0.txt. With the new option, the command behaves as if HEAD:Documentation/RelNotes/2.5.0.txt was given as input instead. * dt/cat-file-follow-symlinks: cat-file: add --follow-symlinks to --batch sha1_name: get_sha1_with_context learns to follow symlinks tree-walk: learn get_tree_entry_follow_symlinks
2 parents 4ba5bb5 + 122d534 commit 67f0b6f

File tree

7 files changed

+601
-19
lines changed

7 files changed

+601
-19
lines changed

Documentation/git-cat-file.txt

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -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>
1414

1515
DESCRIPTION
1616
-----------
@@ -72,6 +72,62 @@ OPTIONS
7272
--allow-unknown-type::
7373
Allow -s or -t to query broken/corrupt objects of unknown type.
7474

75+
--follow-symlinks::
76+
With --batch or --batch-check, follow symlinks inside the
77+
repository when requesting objects with extended SHA-1
78+
expressions of the form tree-ish:path-in-tree. Instead of
79+
providing output about the link itself, provide output about
80+
the linked-to object. If a symlink points outside the
81+
tree-ish (e.g. a link to /foo or a root-level link to ../foo),
82+
the portion of the link which is outside the tree will be
83+
printed.
84+
+
85+
This option does not (currently) work correctly when an object in the
86+
index is specified (e.g. `:link` instead of `HEAD:link`) rather than
87+
one in the tree.
88+
+
89+
This option cannot (currently) be used unless `--batch` or
90+
`--batch-check` is used.
91+
+
92+
For example, consider a git repository containing:
93+
+
94+
--
95+
f: a file containing "hello\n"
96+
link: a symlink to f
97+
dir/link: a symlink to ../f
98+
plink: a symlink to ../f
99+
alink: a symlink to /etc/passwd
100+
--
101+
+
102+
For a regular file `f`, `echo HEAD:f | git cat-file --batch` would print
103+
+
104+
--
105+
ce013625030ba8dba906f756967f9e9ca394464a blob 6
106+
--
107+
+
108+
And `echo HEAD:link | git cat-file --batch --follow-symlinks` would
109+
print the same thing, as would `HEAD:dir/link`, as they both point at
110+
`HEAD:f`.
111+
+
112+
Without `--follow-symlinks`, these would print data about the symlink
113+
itself. In the case of `HEAD:link`, you would see
114+
+
115+
--
116+
4d1ae35ba2c8ec712fa2a379db44ad639ca277bd blob 1
117+
--
118+
+
119+
Both `plink` and `alink` point outside the tree, so they would
120+
respectively print:
121+
+
122+
--
123+
symlink 4
124+
../f
125+
126+
symlink 11
127+
/etc/passwd
128+
--
129+
130+
75131
OUTPUT
76132
------
77133
If '-t' is specified, one of the <type>.
@@ -151,6 +207,47 @@ the repository, then `cat-file` will ignore any custom format and print:
151207
<object> SP missing LF
152208
------------
153209

210+
If --follow-symlinks is used, and a symlink in the repository points
211+
outside the repository, then `cat-file` will ignore any custom format
212+
and print:
213+
214+
------------
215+
symlink SP <size> LF
216+
<symlink> LF
217+
------------
218+
219+
The symlink will either be absolute (beginning with a /), or relative
220+
to the tree root. For instance, if dir/link points to ../../foo, then
221+
<symlink> will be ../foo. <size> is the size of the symlink in bytes.
222+
223+
If --follow-symlinks is used, the following error messages will be
224+
displayed:
225+
226+
------------
227+
<object> SP missing LF
228+
------------
229+
is printed when the initial symlink requested does not exist.
230+
231+
------------
232+
dangling SP <size> LF
233+
<object> LF
234+
------------
235+
is printed when the initial symlink exists, but something that
236+
it (transitive-of) points to does not.
237+
238+
------------
239+
loop SP <size> LF
240+
<object> LF
241+
------------
242+
is printed for symlink loops (or any symlinks that
243+
require more than 40 link resolutions to resolve).
244+
245+
------------
246+
notdir SP <size> LF
247+
<object> LF
248+
------------
249+
is printed when, during symlink resolution, a file is used as a
250+
directory name.
154251

155252
CAVEATS
156253
-------

builtin/cat-file.c

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "parse-options.h"
99
#include "userdiff.h"
1010
#include "streaming.h"
11+
#include "tree-walk.h"
1112

1213
static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
1314
int unknown_type)
@@ -233,6 +234,7 @@ static void print_object_or_die(int fd, struct expand_data *data)
233234

234235
struct batch_options {
235236
int enabled;
237+
int follow_symlinks;
236238
int print_contents;
237239
const char *format;
238240
};
@@ -241,12 +243,44 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
241243
struct expand_data *data)
242244
{
243245
struct strbuf buf = STRBUF_INIT;
246+
struct object_context ctx;
247+
int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
248+
enum follow_symlinks_result result;
244249

245250
if (!obj_name)
246251
return 1;
247252

248-
if (get_sha1(obj_name, data->sha1)) {
249-
printf("%s missing\n", obj_name);
253+
result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
254+
if (result != FOUND) {
255+
switch (result) {
256+
case MISSING_OBJECT:
257+
printf("%s missing\n", obj_name);
258+
break;
259+
case DANGLING_SYMLINK:
260+
printf("dangling %"PRIuMAX"\n%s\n",
261+
(uintmax_t)strlen(obj_name), obj_name);
262+
break;
263+
case SYMLINK_LOOP:
264+
printf("loop %"PRIuMAX"\n%s\n",
265+
(uintmax_t)strlen(obj_name), obj_name);
266+
break;
267+
case NOT_DIR:
268+
printf("notdir %"PRIuMAX"\n%s\n",
269+
(uintmax_t)strlen(obj_name), obj_name);
270+
break;
271+
default:
272+
die("BUG: unknown get_sha1_with_context result %d\n",
273+
result);
274+
break;
275+
}
276+
fflush(stdout);
277+
return 0;
278+
}
279+
280+
if (ctx.mode == 0) {
281+
printf("symlink %"PRIuMAX"\n%s\n",
282+
(uintmax_t)ctx.symlink_path.len,
283+
ctx.symlink_path.buf);
250284
fflush(stdout);
251285
return 0;
252286
}
@@ -333,7 +367,7 @@ static int batch_objects(struct batch_options *opt)
333367

334368
static const char * const cat_file_usage[] = {
335369
N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"),
336-
N_("git cat-file (--batch | --batch-check) < <list-of-objects>"),
370+
N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"),
337371
NULL
338372
};
339373

@@ -351,9 +385,8 @@ static int batch_option_callback(const struct option *opt,
351385
{
352386
struct batch_options *bo = opt->value;
353387

354-
if (unset) {
355-
memset(bo, 0, sizeof(*bo));
356-
return 0;
388+
if (bo->enabled) {
389+
return 1;
357390
}
358391

359392
bo->enabled = 1;
@@ -387,6 +420,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
387420
{ OPTION_CALLBACK, 0, "batch-check", &batch, "format",
388421
N_("show info about objects fed from the standard input"),
389422
PARSE_OPT_OPTARG, batch_option_callback },
423+
OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
424+
N_("follow in-tree symlinks (used with --batch or --batch-check)")),
390425
OPT_END()
391426
};
392427

@@ -411,6 +446,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
411446
usage_with_options(cat_file_usage, options);
412447
}
413448

449+
if (batch.follow_symlinks && !batch.enabled) {
450+
usage_with_options(cat_file_usage, options);
451+
}
452+
414453
if (batch.enabled)
415454
return batch_objects(&batch);
416455

cache.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -971,15 +971,21 @@ struct object_context {
971971
unsigned char tree[20];
972972
char path[PATH_MAX];
973973
unsigned mode;
974+
/*
975+
* symlink_path is only used by get_tree_entry_follow_symlinks,
976+
* and only for symlinks that point outside the repository.
977+
*/
978+
struct strbuf symlink_path;
974979
};
975980

976-
#define GET_SHA1_QUIETLY 01
977-
#define GET_SHA1_COMMIT 02
978-
#define GET_SHA1_COMMITTISH 04
979-
#define GET_SHA1_TREE 010
980-
#define GET_SHA1_TREEISH 020
981-
#define GET_SHA1_BLOB 040
982-
#define GET_SHA1_ONLY_TO_DIE 04000
981+
#define GET_SHA1_QUIETLY 01
982+
#define GET_SHA1_COMMIT 02
983+
#define GET_SHA1_COMMITTISH 04
984+
#define GET_SHA1_TREE 010
985+
#define GET_SHA1_TREEISH 020
986+
#define GET_SHA1_BLOB 040
987+
#define GET_SHA1_FOLLOW_SYMLINKS 0100
988+
#define GET_SHA1_ONLY_TO_DIE 04000
983989

984990
extern int get_sha1(const char *str, unsigned char *sha1);
985991
extern int get_sha1_commit(const char *str, unsigned char *sha1);

sha1_name.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,11 +1433,19 @@ static int get_sha1_with_context_1(const char *name,
14331433
new_filename = resolve_relative_path(filename);
14341434
if (new_filename)
14351435
filename = new_filename;
1436-
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
1437-
if (ret && only_to_die) {
1438-
diagnose_invalid_sha1_path(prefix, filename,
1439-
tree_sha1,
1440-
name, len);
1436+
if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
1437+
ret = get_tree_entry_follow_symlinks(tree_sha1,
1438+
filename, sha1, &oc->symlink_path,
1439+
&oc->mode);
1440+
} else {
1441+
ret = get_tree_entry(tree_sha1, filename,
1442+
sha1, &oc->mode);
1443+
if (ret && only_to_die) {
1444+
diagnose_invalid_sha1_path(prefix,
1445+
filename,
1446+
tree_sha1,
1447+
name, len);
1448+
}
14411449
}
14421450
hashcpy(oc->tree, tree_sha1);
14431451
strlcpy(oc->path, filename, sizeof(oc->path));
@@ -1468,5 +1476,7 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)
14681476

14691477
int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
14701478
{
1479+
if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
1480+
die("BUG: incompatible flags for get_sha1_with_context");
14711481
return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
14721482
}

0 commit comments

Comments
 (0)