From 7099ba353ed5faef37f44c9edf57e5c76a9902f1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 28 Nov 2021 10:32:03 -0500 Subject: cli: add `cat-file` command Introduce a simple command that emulates `git cat-file`. --- src/cli/cmd.h | 1 + src/cli/cmd_cat_file.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/main.c | 3 +- 3 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 src/cli/cmd_cat_file.c diff --git a/src/cli/cmd.h b/src/cli/cmd.h index 816614efc..cc1743ed2 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -25,6 +25,7 @@ extern const cli_cmd_spec cli_cmds[]; extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); /* Commands */ +extern int cmd_cat_file(int argc, char **argv); extern int cmd_help(int argc, char **argv); #endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c new file mode 100644 index 000000000..4ee5647ba --- /dev/null +++ b/src/cli/cmd_cat_file.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include "cli.h" +#include "cmd.h" + +typedef enum { + DISPLAY_CONTENT = 0, + DISPLAY_EXISTS, + DISPLAY_PRETTY, + DISPLAY_SIZE, + DISPLAY_TYPE +} display_t; + +typedef int (raw_cb)(const unsigned char *data, size_t size); + +static int show_help; +static int display = DISPLAY_CONTENT; +static char *type_name, *object_spec; + +static const cli_opt_spec opts[] = { + { CLI_OPT_SWITCH, "help", 0, &show_help, 1, NULL, "display help about the cat-file command", CLI_OPT_USAGE_HIDDEN }, + { CLI_OPT_SWITCH, NULL, 't', &display, DISPLAY_TYPE, NULL, "display the type of the object", CLI_OPT_USAGE_REQUIRED }, + { CLI_OPT_SWITCH, NULL, 's', &display, DISPLAY_SIZE, NULL, "display the size of the object", CLI_OPT_USAGE_REQUIRED | CLI_OPT_USAGE_CHOICE }, + { CLI_OPT_SWITCH, NULL, 'e', &display, DISPLAY_EXISTS, NULL, "displays nothing unless the object is corrupt", CLI_OPT_USAGE_REQUIRED | CLI_OPT_USAGE_CHOICE }, + { CLI_OPT_SWITCH, NULL, 'p', &display, DISPLAY_PRETTY, NULL, "pretty-print the object", CLI_OPT_USAGE_REQUIRED | CLI_OPT_USAGE_CHOICE }, + { CLI_OPT_ARG, "type", 0, &type_name, 0, "type", "the type of object to display", CLI_OPT_USAGE_REQUIRED | CLI_OPT_USAGE_CHOICE }, + { CLI_OPT_ARG, "object", 0, &object_spec, 0, "object", "the object to display", CLI_OPT_USAGE_REQUIRED }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, "cat-file", opts); + printf("\n"); + + printf("Display the content for the given object in the repository.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int print_odb(git_object *object, display_t display) +{ + git_odb *odb = NULL; + git_odb_object *odb_object = NULL; + const unsigned char *content; + git_object_size_t size; + int error = 0; + + /* + * Our parsed blobs retain the raw content; all other objects are + * parsed into a working representation. To get the raw content, + * we need to do an ODB lookup. (Thankfully, this should be cached + * in-memory from our last call.) + */ + if (git_object_type(object) == GIT_OBJECT_BLOB) { + content = git_blob_rawcontent((git_blob *)object); + size = git_blob_rawsize((git_blob *)object); + } else { + if ((error = git_repository_odb(&odb, git_object_owner(object))) < 0 || + (error = git_odb_read(&odb_object, odb, git_object_id(object))) < 0) { + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, git_error_last()->message); + error = 1; + goto done; + } + + content = git_odb_object_data(odb_object); + size = git_odb_object_size(odb_object); + } + + switch (display) { + case DISPLAY_SIZE: + error = printf("%" PRIu64 "\n", size); + break; + case DISPLAY_CONTENT: + error = p_write(fileno(stdout), content, (size_t)size); + break; + default: + abort(); + } + + if (error < 0) + perror(PROGRAM_NAME); + +done: + git_odb_object_free(odb_object); + git_odb_free(odb); + return error < 0 ? 1 : 0; +} + +static int print_type(git_object *object) +{ + int error; + + if ((error = printf("%s\n", git_object_type2string(git_object_type(object)))) < 0) + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, strerror(errno)); + + return error < 0 ? 1 : 0; +} + +static int print_pretty(git_object *object) +{ + const git_tree_entry *entry; + size_t i, count; + + /* + * Only trees are stored in an unreadable format and benefit from + * pretty-printing. + */ + if (git_object_type(object) != GIT_OBJECT_TREE) + return print_odb(object, DISPLAY_CONTENT); + + for (i = 0, count = git_tree_entrycount((git_tree *)object); i < count; i++) { + entry = git_tree_entry_byindex((git_tree *)object, i); + + printf("%06o %s %s\t%s\n", + git_tree_entry_filemode_raw(entry), + git_object_type2string(git_tree_entry_type(entry)), + git_oid_tostr_s(git_tree_entry_id(entry)), + git_tree_entry_name(entry)); + } + + return 0; +} + +int cmd_cat_file(int argc, char **argv) +{ + git_repository *repo = NULL; + git_object *object = NULL; + git_object_t type; + cli_opt invalid_opt; + int error, ret = 1; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1)) { + cli_opt_status_fprint(stderr, &invalid_opt); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, "cat-file", opts); + return 129; + } + + if (show_help) { + print_help(); + return 0; + } + + /* + * Either the type may be explicitly specified, or a flag that + * indicates that the object's actual type is used, but not both. + */ + if (display != DISPLAY_CONTENT && type_name && object_spec) { + fprintf(stderr, "unknown option: %s\n", object_spec); + cli_opt_usage_fprint(stdout, PROGRAM_NAME, "cat-file", opts); + return 129; + } + + if (display != DISPLAY_CONTENT) { + /* What we parsed as the type is the object. */ + object_spec = type_name; + type_name = NULL; + } + if ((display == DISPLAY_CONTENT && !type_name) || !object_spec) { + cli_opt_usage_fprint(stdout, PROGRAM_NAME, "cat-file", opts); + return 129; + } + + if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) { + printf("%s: %s\n", PROGRAM_NAME, git_error_last()->message); + return 128; + } + + if ((error = git_revparse_single(&object, repo, object_spec)) < 0) { + if (display == DISPLAY_EXISTS && error == GIT_ENOTFOUND) { + ret = 1; + } else { + printf("%s: %s\n", PROGRAM_NAME, git_error_last()->message); + ret = 128; + } + + goto done; + } + + if (type_name) { + git_object *peeled; + + if ((type = git_object_string2type(type_name)) == GIT_OBJECT_INVALID) { + fprintf(stderr, "%s: invalid object type '%s'\n", PROGRAM_NAME, type_name); + return 129; + } + + if (git_object_peel(&peeled, object, type) < 0) { + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, git_error_last()->message); + goto done; + } + + git_object_free(object); + object = peeled; + } + + switch (display) { + case DISPLAY_EXISTS: + ret = 0; + break; + case DISPLAY_TYPE: + ret = print_type(object); + break; + case DISPLAY_PRETTY: + ret = print_pretty(object); + break; + default: + ret = print_odb(object, display); + break; + } + +done: + git_object_free(object); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/main.c b/src/cli/main.c index 690ef74f1..84491effe 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -24,7 +24,8 @@ const cli_opt_spec cli_common_opts[] = { }; const cli_cmd_spec cli_cmds[] = { - { "help", cmd_help, "Display help information" }, + { "cat-file", cmd_cat_file, "Display an object in the repository" }, + { "help", cmd_help, "Display help information" }, { NULL } }; -- cgit v1.2.1