Junio C Hamano | fae22ac | 2005-06-22 02:30:47 -0700 | [diff] [blame] | 1 | 6af1f0192ff8740fe77db7cf02c739ccfbdf119c (from 2bc2564145835996734d6ed5d1880f85b17233d6) |
| 2 | diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt |
| 3 | --- a/Documentation/git-ls-tree.txt |
| 4 | +++ b/Documentation/git-ls-tree.txt |
| 5 | @@ -4,23 +4,26 @@ v0.1, May 2005 |
| 6 | |
| 7 | NAME |
| 8 | ---- |
| 9 | -git-ls-tree - Displays a tree object in human readable form |
| 10 | +git-ls-tree - Lists the contents of a tree object. |
| 11 | |
| 12 | |
| 13 | SYNOPSIS |
| 14 | -------- |
| 15 | -'git-ls-tree' [-r] [-z] <tree-ish> [paths...] |
| 16 | +'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...] |
| 17 | |
| 18 | DESCRIPTION |
| 19 | ----------- |
| 20 | -Converts the tree object to a human readable (and script processable) |
| 21 | -form. |
| 22 | +Lists the contents of a tree object, like what "/bin/ls -a" does |
| 23 | +in the current working directory. |
| 24 | |
| 25 | OPTIONS |
| 26 | ------- |
| 27 | <tree-ish>:: |
| 28 | Id of a tree. |
| 29 | |
| 30 | +-d:: |
| 31 | + show only the named tree entry itself, not its children |
| 32 | + |
| 33 | -r:: |
| 34 | recurse into sub-trees |
| 35 | |
| 36 | @@ -28,18 +31,19 @@ OPTIONS |
| 37 | \0 line termination on output |
| 38 | |
| 39 | paths:: |
| 40 | - Optionally, restrict the output of git-ls-tree to specific |
| 41 | - paths. Directories will only list their tree blob ids. |
| 42 | - Implies -r. |
| 43 | + When paths are given, shows them. Otherwise implicitly |
| 44 | + uses the root level of the tree as the sole path argument. |
| 45 | + |
| 46 | |
| 47 | Output Format |
| 48 | ------------- |
| 49 | - <mode>\t <type>\t <object>\t <file> |
| 50 | + <mode> SP <type> SP <object> TAB <file> |
| 51 | |
| 52 | |
| 53 | Author |
| 54 | ------ |
| 55 | Written by Linus Torvalds <torvalds@osdl.org> |
| 56 | +Completely rewritten from scratch by Junio C Hamano <junkio@cox.net> |
| 57 | |
| 58 | Documentation |
| 59 | -------------- |
| 60 | diff --git a/ls-tree.c b/ls-tree.c |
| 61 | dissimilarity index 82% |
| 62 | --- ls-tree.c |
| 63 | +++ ls-tree.c |
| 64 | @@ -1,212 +1,247 @@ |
| 65 | -/* |
| 66 | - * GIT - The information manager from hell |
| 67 | - * |
| 68 | - * Copyright (C) Linus Torvalds, 2005 |
| 69 | - */ |
| 70 | -#include "cache.h" |
| 71 | - |
| 72 | -static int line_termination = '\n'; |
| 73 | -static int recursive = 0; |
| 74 | - |
| 75 | -struct path_prefix { |
| 76 | - struct path_prefix *prev; |
| 77 | - const char *name; |
| 78 | -}; |
| 79 | - |
| 80 | -#define DEBUG(fmt, ...) |
| 81 | - |
| 82 | -static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix) |
| 83 | -{ |
| 84 | - int len = 0; |
| 85 | - if (prefix) { |
| 86 | - if (prefix->prev) { |
| 87 | - len = string_path_prefix(buff,blen,prefix->prev); |
| 88 | - buff += len; |
| 89 | - blen -= len; |
| 90 | - if (blen > 0) { |
| 91 | - *buff = '/'; |
| 92 | - len++; |
| 93 | - buff++; |
| 94 | - blen--; |
| 95 | - } |
| 96 | - } |
| 97 | - strncpy(buff,prefix->name,blen); |
| 98 | - return len + strlen(prefix->name); |
| 99 | - } |
| 100 | - |
| 101 | - return 0; |
| 102 | -} |
| 103 | - |
| 104 | -static void print_path_prefix(struct path_prefix *prefix) |
| 105 | -{ |
| 106 | - if (prefix) { |
| 107 | - if (prefix->prev) { |
| 108 | - print_path_prefix(prefix->prev); |
| 109 | - putchar('/'); |
| 110 | - } |
| 111 | - fputs(prefix->name, stdout); |
| 112 | - } |
| 113 | -} |
| 114 | - |
| 115 | -/* |
| 116 | - * return: |
| 117 | - * -1 if prefix is *not* a subset of path |
| 118 | - * 0 if prefix == path |
| 119 | - * 1 if prefix is a subset of path |
| 120 | - */ |
| 121 | -static int pathcmp(const char *path, struct path_prefix *prefix) |
| 122 | -{ |
| 123 | - char buff[PATH_MAX]; |
| 124 | - int len,slen; |
| 125 | - |
| 126 | - if (prefix == NULL) |
| 127 | - return 1; |
| 128 | - |
| 129 | - len = string_path_prefix(buff, sizeof buff, prefix); |
| 130 | - slen = strlen(path); |
| 131 | - |
| 132 | - if (slen < len) |
| 133 | - return -1; |
| 134 | - |
| 135 | - if (strncmp(path,buff,len) == 0) { |
| 136 | - if (slen == len) |
| 137 | - return 0; |
| 138 | - else |
| 139 | - return 1; |
| 140 | - } |
| 141 | - |
| 142 | - return -1; |
| 143 | -} |
| 144 | - |
| 145 | -/* |
| 146 | - * match may be NULL, or a *sorted* list of paths |
| 147 | - */ |
| 148 | -static void list_recursive(void *buffer, |
| 149 | - const char *type, |
| 150 | - unsigned long size, |
| 151 | - struct path_prefix *prefix, |
| 152 | - char **match, int matches) |
| 153 | -{ |
| 154 | - struct path_prefix this_prefix; |
| 155 | - this_prefix.prev = prefix; |
| 156 | - |
| 157 | - if (strcmp(type, "tree")) |
| 158 | - die("expected a 'tree' node"); |
| 159 | - |
| 160 | - if (matches) |
| 161 | - recursive = 1; |
| 162 | - |
| 163 | - while (size) { |
| 164 | - int namelen = strlen(buffer)+1; |
| 165 | - void *eltbuf = NULL; |
| 166 | - char elttype[20]; |
| 167 | - unsigned long eltsize; |
| 168 | - unsigned char *sha1 = buffer + namelen; |
| 169 | - char *path = strchr(buffer, ' ') + 1; |
| 170 | - unsigned int mode; |
| 171 | - const char *matched = NULL; |
| 172 | - int mtype = -1; |
| 173 | - int mindex; |
| 174 | - |
| 175 | - if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1) |
| 176 | - die("corrupt 'tree' file"); |
| 177 | - buffer = sha1 + 20; |
| 178 | - size -= namelen + 20; |
| 179 | - |
| 180 | - this_prefix.name = path; |
| 181 | - for ( mindex = 0; mindex < matches; mindex++) { |
| 182 | - mtype = pathcmp(match[mindex],&this_prefix); |
| 183 | - if (mtype >= 0) { |
| 184 | - matched = match[mindex]; |
| 185 | - break; |
| 186 | - } |
| 187 | - } |
| 188 | - |
| 189 | - /* |
| 190 | - * If we're not matching, or if this is an exact match, |
| 191 | - * print out the info |
| 192 | - */ |
| 193 | - if (!matches || (matched != NULL && mtype == 0)) { |
| 194 | - printf("%06o %s %s\t", mode, |
| 195 | - S_ISDIR(mode) ? "tree" : "blob", |
| 196 | - sha1_to_hex(sha1)); |
| 197 | - print_path_prefix(&this_prefix); |
| 198 | - putchar(line_termination); |
| 199 | - } |
| 200 | - |
| 201 | - if (! recursive || ! S_ISDIR(mode)) |
| 202 | - continue; |
| 203 | - |
| 204 | - if (matches && ! matched) |
| 205 | - continue; |
| 206 | - |
| 207 | - if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) { |
| 208 | - error("cannot read %s", sha1_to_hex(sha1)); |
| 209 | - continue; |
| 210 | - } |
| 211 | - |
| 212 | - /* If this is an exact directory match, we may have |
| 213 | - * directory files following this path. Match on them. |
Elijah Newren | 7a40cf1 | 2019-11-05 17:07:24 +0000 | [diff] [blame] | 214 | - * Otherwise, we're at a patch subcomponent, and we need |
Junio C Hamano | fae22ac | 2005-06-22 02:30:47 -0700 | [diff] [blame] | 215 | - * to try to match again. |
| 216 | - */ |
| 217 | - if (mtype == 0) |
| 218 | - mindex++; |
| 219 | - |
| 220 | - list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex); |
| 221 | - free(eltbuf); |
| 222 | - } |
| 223 | -} |
| 224 | - |
| 225 | -static int qcmp(const void *a, const void *b) |
| 226 | -{ |
| 227 | - return strcmp(*(char **)a, *(char **)b); |
| 228 | -} |
| 229 | - |
| 230 | -static int list(unsigned char *sha1,char **path) |
| 231 | -{ |
| 232 | - void *buffer; |
| 233 | - unsigned long size; |
| 234 | - int npaths; |
| 235 | - |
| 236 | - for (npaths = 0; path[npaths] != NULL; npaths++) |
| 237 | - ; |
| 238 | - |
| 239 | - qsort(path,npaths,sizeof(char *),qcmp); |
| 240 | - |
| 241 | - buffer = read_object_with_reference(sha1, "tree", &size, NULL); |
| 242 | - if (!buffer) |
| 243 | - die("unable to read sha1 file"); |
| 244 | - list_recursive(buffer, "tree", size, NULL, path, npaths); |
| 245 | - free(buffer); |
| 246 | - return 0; |
| 247 | -} |
| 248 | - |
| 249 | -static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]"; |
| 250 | - |
| 251 | -int main(int argc, char **argv) |
| 252 | -{ |
| 253 | - unsigned char sha1[20]; |
| 254 | - |
| 255 | - while (1 < argc && argv[1][0] == '-') { |
| 256 | - switch (argv[1][1]) { |
| 257 | - case 'z': |
| 258 | - line_termination = 0; |
| 259 | - break; |
| 260 | - case 'r': |
| 261 | - recursive = 1; |
| 262 | - break; |
| 263 | - default: |
| 264 | - usage(ls_tree_usage); |
| 265 | - } |
| 266 | - argc--; argv++; |
| 267 | - } |
| 268 | - |
| 269 | - if (argc < 2) |
| 270 | - usage(ls_tree_usage); |
| 271 | - if (get_sha1(argv[1], sha1) < 0) |
| 272 | - usage(ls_tree_usage); |
| 273 | - if (list(sha1, &argv[2]) < 0) |
| 274 | - die("list failed"); |
| 275 | - return 0; |
| 276 | -} |
| 277 | +/* |
| 278 | + * GIT - The information manager from hell |
| 279 | + * |
| 280 | + * Copyright (C) Linus Torvalds, 2005 |
| 281 | + */ |
| 282 | +#include "cache.h" |
| 283 | +#include "blob.h" |
| 284 | +#include "tree.h" |
| 285 | + |
| 286 | +static int line_termination = '\n'; |
| 287 | +#define LS_RECURSIVE 1 |
| 288 | +#define LS_TREE_ONLY 2 |
| 289 | +static int ls_options = 0; |
| 290 | + |
| 291 | +static struct tree_entry_list root_entry; |
| 292 | + |
| 293 | +static void prepare_root(unsigned char *sha1) |
| 294 | +{ |
| 295 | + unsigned char rsha[20]; |
| 296 | + unsigned long size; |
| 297 | + void *buf; |
| 298 | + struct tree *root_tree; |
| 299 | + |
| 300 | + buf = read_object_with_reference(sha1, "tree", &size, rsha); |
| 301 | + free(buf); |
| 302 | + if (!buf) |
| 303 | + die("Could not read %s", sha1_to_hex(sha1)); |
| 304 | + |
| 305 | + root_tree = lookup_tree(rsha); |
| 306 | + if (!root_tree) |
| 307 | + die("Could not read %s", sha1_to_hex(sha1)); |
| 308 | + |
| 309 | + /* Prepare a fake entry */ |
| 310 | + root_entry.directory = 1; |
| 311 | + root_entry.executable = root_entry.symlink = 0; |
| 312 | + root_entry.mode = S_IFDIR; |
| 313 | + root_entry.name = ""; |
| 314 | + root_entry.item.tree = root_tree; |
| 315 | + root_entry.parent = NULL; |
| 316 | +} |
| 317 | + |
| 318 | +static int prepare_children(struct tree_entry_list *elem) |
| 319 | +{ |
| 320 | + if (!elem->directory) |
| 321 | + return -1; |
| 322 | + if (!elem->item.tree->object.parsed) { |
| 323 | + struct tree_entry_list *e; |
| 324 | + if (parse_tree(elem->item.tree)) |
| 325 | + return -1; |
| 326 | + /* Set up the parent link */ |
| 327 | + for (e = elem->item.tree->entries; e; e = e->next) |
| 328 | + e->parent = elem; |
| 329 | + } |
| 330 | + return 0; |
| 331 | +} |
| 332 | + |
| 333 | +static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem, |
| 334 | + const char *path, |
| 335 | + const char *path_end) |
| 336 | +{ |
| 337 | + const char *ep; |
| 338 | + int len; |
| 339 | + |
| 340 | + while (path < path_end) { |
| 341 | + if (prepare_children(elem)) |
| 342 | + return NULL; |
| 343 | + |
| 344 | + /* In elem->tree->entries, find the one that has name |
| 345 | + * that matches what is between path and ep. |
| 346 | + */ |
| 347 | + elem = elem->item.tree->entries; |
| 348 | + |
| 349 | + ep = strchr(path, '/'); |
| 350 | + if (!ep || path_end <= ep) |
| 351 | + ep = path_end; |
| 352 | + len = ep - path; |
| 353 | + |
| 354 | + while (elem) { |
| 355 | + if ((strlen(elem->name) == len) && |
| 356 | + !strncmp(elem->name, path, len)) |
| 357 | + break; |
| 358 | + elem = elem->next; |
| 359 | + } |
| 360 | + if (path_end <= ep || !elem) |
| 361 | + return elem; |
| 362 | + while (*ep == '/' && ep < path_end) |
| 363 | + ep++; |
| 364 | + path = ep; |
| 365 | + } |
| 366 | + return NULL; |
| 367 | +} |
| 368 | + |
| 369 | +static struct tree_entry_list *find_entry(const char *path, |
| 370 | + const char *path_end) |
| 371 | +{ |
| 372 | + /* Find tree element, descending from root, that |
| 373 | + * corresponds to the named path, lazily expanding |
| 374 | + * the tree if possible. |
| 375 | + */ |
| 376 | + if (path == path_end) { |
| 377 | + /* Special. This is the root level */ |
| 378 | + return &root_entry; |
| 379 | + } |
| 380 | + return find_entry_0(&root_entry, path, path_end); |
| 381 | +} |
| 382 | + |
| 383 | +static void show_entry_name(struct tree_entry_list *e) |
| 384 | +{ |
| 385 | + /* This is yucky. The root level is there for |
| 386 | + * our convenience but we really want to do a |
| 387 | + * forest. |
| 388 | + */ |
| 389 | + if (e->parent && e->parent != &root_entry) { |
| 390 | + show_entry_name(e->parent); |
| 391 | + putchar('/'); |
| 392 | + } |
| 393 | + printf("%s", e->name); |
| 394 | +} |
| 395 | + |
| 396 | +static const char *entry_type(struct tree_entry_list *e) |
| 397 | +{ |
| 398 | + return (e->directory ? "tree" : "blob"); |
| 399 | +} |
| 400 | + |
| 401 | +static const char *entry_hex(struct tree_entry_list *e) |
| 402 | +{ |
| 403 | + return sha1_to_hex(e->directory |
| 404 | + ? e->item.tree->object.sha1 |
| 405 | + : e->item.blob->object.sha1); |
| 406 | +} |
| 407 | + |
| 408 | +/* forward declaration for mutually recursive routines */ |
| 409 | +static int show_entry(struct tree_entry_list *, int); |
| 410 | + |
| 411 | +static int show_children(struct tree_entry_list *e, int level) |
| 412 | +{ |
| 413 | + if (prepare_children(e)) |
| 414 | + die("internal error: ls-tree show_children called with non tree"); |
| 415 | + e = e->item.tree->entries; |
| 416 | + while (e) { |
| 417 | + show_entry(e, level); |
| 418 | + e = e->next; |
| 419 | + } |
| 420 | + return 0; |
| 421 | +} |
| 422 | + |
| 423 | +static int show_entry(struct tree_entry_list *e, int level) |
| 424 | +{ |
| 425 | + int err = 0; |
| 426 | + |
| 427 | + if (e != &root_entry) { |
| 428 | + printf("%06o %s %s ", e->mode, entry_type(e), |
| 429 | + entry_hex(e)); |
| 430 | + show_entry_name(e); |
| 431 | + putchar(line_termination); |
| 432 | + } |
| 433 | + |
| 434 | + if (e->directory) { |
| 435 | + /* If this is a directory, we have the following cases: |
| 436 | + * (1) This is the top-level request (explicit path from the |
| 437 | + * command line, or "root" if there is no command line). |
| 438 | + * a. Without any flag. We show direct children. We do not |
| 439 | + * recurse into them. |
| 440 | + * b. With -r. We do recurse into children. |
| 441 | + * c. With -d. We do not recurse into children. |
| 442 | + * (2) We came here because our caller is either (1-a) or |
| 443 | + * (1-b). |
| 444 | + * a. Without any flag. We do not show our children (which |
| 445 | + * are grandchildren for the original request). |
| 446 | + * b. With -r. We continue to recurse into our children. |
| 447 | + * c. With -d. We should not have come here to begin with. |
| 448 | + */ |
| 449 | + if (level == 0 && !(ls_options & LS_TREE_ONLY)) |
| 450 | + /* case (1)-a and (1)-b */ |
| 451 | + err = err | show_children(e, level+1); |
| 452 | + else if (level && ls_options & LS_RECURSIVE) |
| 453 | + /* case (2)-b */ |
| 454 | + err = err | show_children(e, level+1); |
| 455 | + } |
| 456 | + return err; |
| 457 | +} |
| 458 | + |
| 459 | +static int list_one(const char *path, const char *path_end) |
| 460 | +{ |
| 461 | + int err = 0; |
| 462 | + struct tree_entry_list *e = find_entry(path, path_end); |
| 463 | + if (!e) { |
| 464 | + /* traditionally ls-tree does not complain about |
| 465 | + * missing path. We may change this later to match |
| 466 | + * what "/bin/ls -a" does, which is to complain. |
| 467 | + */ |
| 468 | + return err; |
| 469 | + } |
| 470 | + err = err | show_entry(e, 0); |
| 471 | + return err; |
| 472 | +} |
| 473 | + |
| 474 | +static int list(char **path) |
| 475 | +{ |
| 476 | + int i; |
| 477 | + int err = 0; |
| 478 | + for (i = 0; path[i]; i++) { |
| 479 | + int len = strlen(path[i]); |
| 480 | + while (0 <= len && path[i][len] == '/') |
| 481 | + len--; |
| 482 | + err = err | list_one(path[i], path[i] + len); |
| 483 | + } |
| 484 | + return err; |
| 485 | +} |
| 486 | + |
| 487 | +static const char *ls_tree_usage = |
| 488 | + "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]"; |
| 489 | + |
| 490 | +int main(int argc, char **argv) |
| 491 | +{ |
| 492 | + static char *path0[] = { "", NULL }; |
| 493 | + char **path; |
| 494 | + unsigned char sha1[20]; |
| 495 | + |
| 496 | + while (1 < argc && argv[1][0] == '-') { |
| 497 | + switch (argv[1][1]) { |
| 498 | + case 'z': |
| 499 | + line_termination = 0; |
| 500 | + break; |
| 501 | + case 'r': |
| 502 | + ls_options |= LS_RECURSIVE; |
| 503 | + break; |
| 504 | + case 'd': |
| 505 | + ls_options |= LS_TREE_ONLY; |
| 506 | + break; |
| 507 | + default: |
| 508 | + usage(ls_tree_usage); |
| 509 | + } |
| 510 | + argc--; argv++; |
| 511 | + } |
| 512 | + |
| 513 | + if (argc < 2) |
| 514 | + usage(ls_tree_usage); |
| 515 | + if (get_sha1(argv[1], sha1) < 0) |
| 516 | + usage(ls_tree_usage); |
| 517 | + |
| 518 | + path = (argc == 2) ? path0 : (argv + 2); |
| 519 | + prepare_root(sha1); |
| 520 | + if (list(path) < 0) |
| 521 | + die("list failed"); |
| 522 | + return 0; |
| 523 | +} |
| 524 | diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh |
| 525 | --- a/t/t3100-ls-tree-restrict.sh |
| 526 | +++ b/t/t3100-ls-tree-restrict.sh |
| 527 | @@ -74,8 +74,8 @@ test_expect_success \ |
| 528 | 'ls-tree filtered' \ |
| 529 | 'git-ls-tree $tree path1 path0 >current && |
| 530 | cat >expected <<\EOF && |
| 531 | -100644 blob X path0 |
| 532 | 120000 blob X path1 |
| 533 | +100644 blob X path0 |
| 534 | EOF |
| 535 | test_output' |
| 536 | |
| 537 | @@ -85,7 +85,6 @@ test_expect_success \ |
| 538 | cat >expected <<\EOF && |
| 539 | 040000 tree X path2 |
| 540 | 040000 tree X path2/baz |
| 541 | -100644 blob X path2/baz/b |
| 542 | 120000 blob X path2/bazbo |
| 543 | 100644 blob X path2/foo |
| 544 | EOF |
| 545 | diff --git a/tree.c b/tree.c |
| 546 | --- a/tree.c |
| 547 | +++ b/tree.c |
| 548 | @@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item, |
| 549 | } |
| 550 | if (obj) |
| 551 | add_ref(&item->object, obj); |
| 552 | - |
| 553 | + entry->parent = NULL; /* needs to be filled by the user */ |
| 554 | *list_p = entry; |
| 555 | list_p = &entry->next; |
| 556 | } |
| 557 | diff --git a/tree.h b/tree.h |
| 558 | --- a/tree.h |
| 559 | +++ b/tree.h |
| 560 | @@ -16,6 +16,7 @@ struct tree_entry_list { |
| 561 | struct tree *tree; |
| 562 | struct blob *blob; |
| 563 | } item; |
| 564 | + struct tree_entry_list *parent; |
| 565 | }; |
| 566 | |
| 567 | struct tree { |