Skip to content

Commit

Permalink
Merge branch 'dt/cat-file-follow-symlinks'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
Junio C Hamano committed Jun 1, 2015
2 parents 4ba5bb5 + 122d534 commit 67f0b6f
Show file tree
Hide file tree
Showing 7 changed files with 601 additions and 19 deletions.
99 changes: 98 additions & 1 deletion Documentation/git-cat-file.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object>
'git cat-file' (--batch | --batch-check) < <list-of-objects>
'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>

DESCRIPTION
-----------
Expand Down Expand Up @@ -72,6 +72,62 @@ OPTIONS
--allow-unknown-type::
Allow -s or -t to query broken/corrupt objects of unknown type.

--follow-symlinks::
With --batch or --batch-check, follow symlinks inside the
repository when requesting objects with extended SHA-1
expressions of the form tree-ish:path-in-tree. Instead of
providing output about the link itself, provide output about
the linked-to object. If a symlink points outside the
tree-ish (e.g. a link to /foo or a root-level link to ../foo),
the portion of the link which is outside the tree will be
printed.
+
This option does not (currently) work correctly when an object in the
index is specified (e.g. `:link` instead of `HEAD:link`) rather than
one in the tree.
+
This option cannot (currently) be used unless `--batch` or
`--batch-check` is used.
+
For example, consider a git repository containing:
+
--
f: a file containing "hello\n"
link: a symlink to f
dir/link: a symlink to ../f
plink: a symlink to ../f
alink: a symlink to /etc/passwd
--
+
For a regular file `f`, `echo HEAD:f | git cat-file --batch` would print
+
--
ce013625030ba8dba906f756967f9e9ca394464a blob 6
--
+
And `echo HEAD:link | git cat-file --batch --follow-symlinks` would
print the same thing, as would `HEAD:dir/link`, as they both point at
`HEAD:f`.
+
Without `--follow-symlinks`, these would print data about the symlink
itself. In the case of `HEAD:link`, you would see
+
--
4d1ae35ba2c8ec712fa2a379db44ad639ca277bd blob 1
--
+
Both `plink` and `alink` point outside the tree, so they would
respectively print:
+
--
symlink 4
../f

symlink 11
/etc/passwd
--


OUTPUT
------
If '-t' is specified, one of the <type>.
Expand Down Expand Up @@ -151,6 +207,47 @@ the repository, then `cat-file` will ignore any custom format and print:
<object> SP missing LF
------------

If --follow-symlinks is used, and a symlink in the repository points
outside the repository, then `cat-file` will ignore any custom format
and print:

------------
symlink SP <size> LF
<symlink> LF
------------

The symlink will either be absolute (beginning with a /), or relative
to the tree root. For instance, if dir/link points to ../../foo, then
<symlink> will be ../foo. <size> is the size of the symlink in bytes.

If --follow-symlinks is used, the following error messages will be
displayed:

------------
<object> SP missing LF
------------
is printed when the initial symlink requested does not exist.

------------
dangling SP <size> LF
<object> LF
------------
is printed when the initial symlink exists, but something that
it (transitive-of) points to does not.

------------
loop SP <size> LF
<object> LF
------------
is printed for symlink loops (or any symlinks that
require more than 40 link resolutions to resolve).

------------
notdir SP <size> LF
<object> LF
------------
is printed when, during symlink resolution, a file is used as a
directory name.

CAVEATS
-------
Expand Down
51 changes: 45 additions & 6 deletions builtin/cat-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "parse-options.h"
#include "userdiff.h"
#include "streaming.h"
#include "tree-walk.h"

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

struct batch_options {
int enabled;
int follow_symlinks;
int print_contents;
const char *format;
};
Expand All @@ -241,12 +243,44 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,
struct expand_data *data)
{
struct strbuf buf = STRBUF_INIT;
struct object_context ctx;
int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
enum follow_symlinks_result result;

if (!obj_name)
return 1;

if (get_sha1(obj_name, data->sha1)) {
printf("%s missing\n", obj_name);
result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx);
if (result != FOUND) {
switch (result) {
case MISSING_OBJECT:
printf("%s missing\n", obj_name);
break;
case DANGLING_SYMLINK:
printf("dangling %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
case SYMLINK_LOOP:
printf("loop %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
case NOT_DIR:
printf("notdir %"PRIuMAX"\n%s\n",
(uintmax_t)strlen(obj_name), obj_name);
break;
default:
die("BUG: unknown get_sha1_with_context result %d\n",
result);
break;
}
fflush(stdout);
return 0;
}

if (ctx.mode == 0) {
printf("symlink %"PRIuMAX"\n%s\n",
(uintmax_t)ctx.symlink_path.len,
ctx.symlink_path.buf);
fflush(stdout);
return 0;
}
Expand Down Expand Up @@ -333,7 +367,7 @@ static int batch_objects(struct batch_options *opt)

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

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

if (unset) {
memset(bo, 0, sizeof(*bo));
return 0;
if (bo->enabled) {
return 1;
}

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

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

if (batch.follow_symlinks && !batch.enabled) {
usage_with_options(cat_file_usage, options);
}

if (batch.enabled)
return batch_objects(&batch);

Expand Down
20 changes: 13 additions & 7 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -971,15 +971,21 @@ struct object_context {
unsigned char tree[20];
char path[PATH_MAX];
unsigned mode;
/*
* symlink_path is only used by get_tree_entry_follow_symlinks,
* and only for symlinks that point outside the repository.
*/
struct strbuf symlink_path;
};

#define GET_SHA1_QUIETLY 01
#define GET_SHA1_COMMIT 02
#define GET_SHA1_COMMITTISH 04
#define GET_SHA1_TREE 010
#define GET_SHA1_TREEISH 020
#define GET_SHA1_BLOB 040
#define GET_SHA1_ONLY_TO_DIE 04000
#define GET_SHA1_QUIETLY 01
#define GET_SHA1_COMMIT 02
#define GET_SHA1_COMMITTISH 04
#define GET_SHA1_TREE 010
#define GET_SHA1_TREEISH 020
#define GET_SHA1_BLOB 040
#define GET_SHA1_FOLLOW_SYMLINKS 0100
#define GET_SHA1_ONLY_TO_DIE 04000

extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_commit(const char *str, unsigned char *sha1);
Expand Down
20 changes: 15 additions & 5 deletions sha1_name.c
Original file line number Diff line number Diff line change
Expand Up @@ -1433,11 +1433,19 @@ static int get_sha1_with_context_1(const char *name,
new_filename = resolve_relative_path(filename);
if (new_filename)
filename = new_filename;
ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
if (ret && only_to_die) {
diagnose_invalid_sha1_path(prefix, filename,
tree_sha1,
name, len);
if (flags & GET_SHA1_FOLLOW_SYMLINKS) {
ret = get_tree_entry_follow_symlinks(tree_sha1,
filename, sha1, &oc->symlink_path,
&oc->mode);
} else {
ret = get_tree_entry(tree_sha1, filename,
sha1, &oc->mode);
if (ret && only_to_die) {
diagnose_invalid_sha1_path(prefix,
filename,
tree_sha1,
name, len);
}
}
hashcpy(oc->tree, tree_sha1);
strlcpy(oc->path, filename, sizeof(oc->path));
Expand Down Expand Up @@ -1468,5 +1476,7 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)

int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
{
if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)
die("BUG: incompatible flags for get_sha1_with_context");
return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
}
Loading

0 comments on commit 67f0b6f

Please sign in to comment.