You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
5.7 KiB
250 lines
5.7 KiB
/* |
|
* GIT - The information manager from hell |
|
* |
|
* Copyright (C) Linus Torvalds, 2005 |
|
*/ |
|
#include "cache.h" |
|
#include "blob.h" |
|
#include "tree.h" |
|
|
|
static int line_termination = '\n'; |
|
#define LS_RECURSIVE 1 |
|
#define LS_TREE_ONLY 2 |
|
static int ls_options = 0; |
|
|
|
static struct tree_entry_list root_entry; |
|
|
|
static void prepare_root(unsigned char *sha1) |
|
{ |
|
unsigned char rsha[20]; |
|
unsigned long size; |
|
void *buf; |
|
struct tree *root_tree; |
|
|
|
buf = read_object_with_reference(sha1, "tree", &size, rsha); |
|
free(buf); |
|
if (!buf) |
|
die("Could not read %s", sha1_to_hex(sha1)); |
|
|
|
root_tree = lookup_tree(rsha); |
|
if (!root_tree) |
|
die("Could not read %s", sha1_to_hex(sha1)); |
|
|
|
/* Prepare a fake entry */ |
|
root_entry.directory = 1; |
|
root_entry.executable = root_entry.symlink = 0; |
|
root_entry.mode = S_IFDIR; |
|
root_entry.name = ""; |
|
root_entry.item.tree = root_tree; |
|
root_entry.parent = NULL; |
|
} |
|
|
|
static int prepare_children(struct tree_entry_list *elem) |
|
{ |
|
if (!elem->directory) |
|
return -1; |
|
if (!elem->item.tree->object.parsed) { |
|
struct tree_entry_list *e; |
|
if (parse_tree(elem->item.tree)) |
|
return -1; |
|
/* Set up the parent link */ |
|
for (e = elem->item.tree->entries; e; e = e->next) |
|
e->parent = elem; |
|
} |
|
return 0; |
|
} |
|
|
|
static struct tree_entry_list *find_entry(const char *path, char *pathbuf) |
|
{ |
|
const char *next, *slash; |
|
int len; |
|
struct tree_entry_list *elem = &root_entry, *oldelem = NULL; |
|
|
|
*(pathbuf) = '\0'; |
|
|
|
/* Find tree element, descending from root, that |
|
* corresponds to the named path, lazily expanding |
|
* the tree if possible. |
|
*/ |
|
|
|
while (path) { |
|
/* The fact we still have path means that the caller |
|
* wants us to make sure that elem at this point is a |
|
* directory, and possibly descend into it. Even what |
|
* is left is just trailing slashes, we loop back to |
|
* here, and this call to prepare_children() will |
|
* catch elem not being a tree. Nice. |
|
*/ |
|
if (prepare_children(elem)) |
|
return NULL; |
|
|
|
slash = strchr(path, '/'); |
|
if (!slash) { |
|
len = strlen(path); |
|
next = NULL; |
|
} |
|
else { |
|
next = slash + 1; |
|
len = slash - path; |
|
} |
|
if (len) { |
|
if (oldelem) { |
|
pathbuf += sprintf(pathbuf, "%s/", oldelem->name); |
|
} |
|
|
|
/* (len == 0) if the original path was "drivers/char/" |
|
* and we have run already two rounds, having elem |
|
* pointing at the drivers/char directory. |
|
*/ |
|
elem = elem->item.tree->entries; |
|
while (elem) { |
|
if ((strlen(elem->name) == len) && |
|
!strncmp(elem->name, path, len)) { |
|
/* found */ |
|
break; |
|
} |
|
elem = elem->next; |
|
} |
|
if (!elem) |
|
return NULL; |
|
|
|
oldelem = elem; |
|
} |
|
path = next; |
|
} |
|
|
|
return elem; |
|
} |
|
|
|
static const char *entry_type(struct tree_entry_list *e) |
|
{ |
|
return (e->directory ? "tree" : "blob"); |
|
} |
|
|
|
static const char *entry_hex(struct tree_entry_list *e) |
|
{ |
|
return sha1_to_hex(e->directory |
|
? e->item.tree->object.sha1 |
|
: e->item.blob->object.sha1); |
|
} |
|
|
|
/* forward declaration for mutually recursive routines */ |
|
static int show_entry(struct tree_entry_list *, int, char *pathbuf); |
|
|
|
static int show_children(struct tree_entry_list *e, int level, char *pathbuf) |
|
{ |
|
int oldlen = strlen(pathbuf); |
|
|
|
if (e != &root_entry) |
|
sprintf(pathbuf + oldlen, "%s/", e->name); |
|
|
|
if (prepare_children(e)) |
|
die("internal error: ls-tree show_children called with non tree"); |
|
e = e->item.tree->entries; |
|
while (e) { |
|
show_entry(e, level, pathbuf); |
|
e = e->next; |
|
} |
|
|
|
pathbuf[oldlen] = '\0'; |
|
|
|
return 0; |
|
} |
|
|
|
static int show_entry(struct tree_entry_list *e, int level, char *pathbuf) |
|
{ |
|
int err = 0; |
|
|
|
if (e != &root_entry) { |
|
printf("%06o %s %s %s%s", e->mode, entry_type(e), |
|
entry_hex(e), pathbuf, e->name); |
|
putchar(line_termination); |
|
} |
|
|
|
if (e->directory) { |
|
/* If this is a directory, we have the following cases: |
|
* (1) This is the top-level request (explicit path from the |
|
* command line, or "root" if there is no command line). |
|
* a. Without any flag. We show direct children. We do not |
|
* recurse into them. |
|
* b. With -r. We do recurse into children. |
|
* c. With -d. We do not recurse into children. |
|
* (2) We came here because our caller is either (1-a) or |
|
* (1-b). |
|
* a. Without any flag. We do not show our children (which |
|
* are grandchildren for the original request). |
|
* b. With -r. We continue to recurse into our children. |
|
* c. With -d. We should not have come here to begin with. |
|
*/ |
|
if (level == 0 && !(ls_options & LS_TREE_ONLY)) |
|
/* case (1)-a and (1)-b */ |
|
err = err | show_children(e, level+1, pathbuf); |
|
else if (level && ls_options & LS_RECURSIVE) |
|
/* case (2)-b */ |
|
err = err | show_children(e, level+1, pathbuf); |
|
} |
|
return err; |
|
} |
|
|
|
static int list_one(const char *path) |
|
{ |
|
int err = 0; |
|
char pathbuf[MAXPATHLEN + 1]; |
|
struct tree_entry_list *e = find_entry(path, pathbuf); |
|
if (!e) { |
|
/* traditionally ls-tree does not complain about |
|
* missing path. We may change this later to match |
|
* what "/bin/ls -a" does, which is to complain. |
|
*/ |
|
return err; |
|
} |
|
err = err | show_entry(e, 0, pathbuf); |
|
return err; |
|
} |
|
|
|
static int list(char **path) |
|
{ |
|
int i; |
|
int err = 0; |
|
for (i = 0; path[i]; i++) |
|
err = err | list_one(path[i]); |
|
return err; |
|
} |
|
|
|
static const char ls_tree_usage[] = |
|
"git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]"; |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
static char *path0[] = { "", NULL }; |
|
char **path; |
|
unsigned char sha1[20]; |
|
|
|
while (1 < argc && argv[1][0] == '-') { |
|
switch (argv[1][1]) { |
|
case 'z': |
|
line_termination = 0; |
|
break; |
|
case 'r': |
|
ls_options |= LS_RECURSIVE; |
|
break; |
|
case 'd': |
|
ls_options |= LS_TREE_ONLY; |
|
break; |
|
default: |
|
usage(ls_tree_usage); |
|
} |
|
argc--; argv++; |
|
} |
|
|
|
if (argc < 2) |
|
usage(ls_tree_usage); |
|
if (get_sha1(argv[1], sha1) < 0) |
|
usage(ls_tree_usage); |
|
|
|
path = (argc == 2) ? path0 : (argv + 2); |
|
prepare_root(sha1); |
|
if (list(path) < 0) |
|
die("list failed"); |
|
return 0; |
|
}
|
|
|