commit 20662ea0d15900417adcea3b296822f88d2a38d4 from: Stefan Sperling date: Sat Apr 10 13:31:30 2021 UTC introduce 'gotadmin info' commit - 762d73f46b73795f4f1defc7cbadd0a3c3f17604 commit + 20662ea0d15900417adcea3b296822f88d2a38d4 blob - 5040513451048db2045e1f377e6bbcacf001a587 blob + 5e96ac3cb604c6c6ffb943a5f7620395583905f4 --- Makefile +++ Makefile @@ -1,4 +1,4 @@ -SUBDIR = libexec got tog +SUBDIR = libexec got tog gotadmin .PHONY: release dist blob - /dev/null blob + 920c70117d0c5d2d028f95b8082678537cbf4f45 (mode 644) --- /dev/null +++ gotadmin/Makefile @@ -0,0 +1,34 @@ +.PATH:${.CURDIR}/../lib + +.include "../got-version.mk" + +PROG= gotadmin +SRCS= gotadmin.c \ + deflate.c delta.c delta_cache.c deltify.c error.c gotconfig.c \ + inflate.c lockfile.c object.c object_cache.c object_create.c \ + object_idset.c object_parse.c opentemp.c pack.c \ + path.c privsep.c reference.c repository.c sha1.c +MAN = ${PROG}.1 + +CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib + +.if defined(PROFILE) +LDADD = -lutil_p -lz_p -lc_p +.else +LDADD = -lutil -lz +.endif +DPADD = ${LIBZ} ${LIBUTIL} + +.if ${GOT_RELEASE} != "Yes" +NOMAN = Yes +.endif + +realinstall: + ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \ + -m ${BINMODE} ${PROG} ${BINDIR}/${PROG} + +dist: + mkdir ../got-${GOT_VERSION}/${PROG} + cp ${SRCS} ${MAN} ../got-${GOT_VERSION}/${PROG} + +.include blob - /dev/null blob + 61c214f5d923cc2ae9198a585bfa364076f830eb (mode 644) --- /dev/null +++ gotadmin/gotadmin.1 @@ -0,0 +1,81 @@ +.\" +.\" Copyright (c) 2021 Stefan Sperling +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate$ +.Dt GOTADMIN 1 +.Os +.Sh NAME +.Nm gotadmin +.Nd Game of Trees repository administration +.Sh SYNOPSIS +.Nm +.Ar command +.Op Fl h +.Op Ar arg ... +.Sh DESCRIPTION +.Nm +is the repository maintenance tool for the +.Xr got 1 +version control system. +.Pp +.Xr got 1 +stores the history of tracked files in a Git repository, as used +by the Git version control system. +.Nm +provides commands for inspecting and manipulating the on-disk state of +Git repositories. +The repository format is described in +.Xr git-repository 5 . +.Pp +.Nm +provides global and command-specific options. +Global options must precede the command name, and are as follows: +.Bl -tag -width tenletters +.It Fl h +Display usage information and exit immediately. +.It Fl V , -version +Display program version and exit immediately. +.El +.Pp +The commands for +.Nm +are as follows: +.Bl -tag -width checkout +.It Cm info Oo Fl r Ar repository-path Oc +Display information about a repository. +This includes some configuration settings from +.Xr got.conf 5 , +and the number of objects stored in the repository, in packed or +loose form, as well as the current on-disk size of these objects. +.Pp +The options for +.Cm gotadmin info +are as follows: +.Bl -tag -width Ds +.It Fl r Ar repository-path +Use the repository at the specified path. +If not specified, assume the repository is located at or above the current +working directory. +.El +.El +.Sh EXIT STATUS +.Ex -std gotadmin +.Sh SEE ALSO +.Xr got 1 , +.Xr tog 1 , +.Xr git-repository 5 , +.Xr got.conf 5 +.Sh AUTHORS +.An Stefan Sperling Aq Mt stsp@openbsd.org blob - /dev/null blob + 87e781ed58930ff07563b90ffc8f130eda1d0cfc (mode 644) --- /dev/null +++ gotadmin/gotadmin.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2021 Stefan Sperling + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_version.h" +#include "got_error.h" +#include "got_object.h" +#include "got_reference.h" +#include "got_repository.h" +#include "got_gotconfig.h" +#include "got_path.h" +#include "got_cancel.h" +#include "got_privsep.h" +#include "got_opentemp.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +static volatile sig_atomic_t sigint_received; +static volatile sig_atomic_t sigpipe_received; + +static void +catch_sigint(int signo) +{ + sigint_received = 1; +} + +static void +catch_sigpipe(int signo) +{ + sigpipe_received = 1; +} + + +struct gotadmin_cmd { + const char *cmd_name; + const struct got_error *(*cmd_main)(int, char *[]); + void (*cmd_usage)(void); + const char *cmd_alias; +}; + +__dead static void usage(int, int); +__dead static void usage_info(void); + +static const struct got_error* cmd_info(int, char *[]); + +static struct gotadmin_cmd gotadmin_commands[] = { + { "info", cmd_info, usage_info, "" }, +}; + +static void +list_commands(FILE *fp) +{ + size_t i; + + fprintf(fp, "commands:"); + for (i = 0; i < nitems(gotadmin_commands); i++) { + struct gotadmin_cmd *cmd = &gotadmin_commands[i]; + fprintf(fp, " %s", cmd->cmd_name); + } + fputc('\n', fp); +} + +int +main(int argc, char *argv[]) +{ + struct gotadmin_cmd *cmd; + size_t i; + int ch; + int hflag = 0, Vflag = 0; + static struct option longopts[] = { + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_CTYPE, ""); + + while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) { + switch (ch) { + case 'h': + hflag = 1; + break; + case 'V': + Vflag = 1; + break; + default: + usage(hflag, 1); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + optind = 1; + optreset = 1; + + if (Vflag) { + got_version_print_str(); + return 0; + } + + if (argc <= 0) + usage(hflag, hflag ? 0 : 1); + + signal(SIGINT, catch_sigint); + signal(SIGPIPE, catch_sigpipe); + + for (i = 0; i < nitems(gotadmin_commands); i++) { + const struct got_error *error; + + cmd = &gotadmin_commands[i]; + + if (strcmp(cmd->cmd_name, argv[0]) != 0 && + strcmp(cmd->cmd_alias, argv[0]) != 0) + continue; + + if (hflag) + gotadmin_commands[i].cmd_usage(); + + error = gotadmin_commands[i].cmd_main(argc, argv); + if (error && error->code != GOT_ERR_CANCELLED && + error->code != GOT_ERR_PRIVSEP_EXIT && + !(sigpipe_received && + error->code == GOT_ERR_ERRNO && errno == EPIPE) && + !(sigint_received && + error->code == GOT_ERR_ERRNO && errno == EINTR)) { + fprintf(stderr, "%s: %s\n", getprogname(), error->msg); + return 1; + } + + return 0; + } + + fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]); + list_commands(stderr); + return 1; +} + +__dead static void +usage(int hflag, int status) +{ + FILE *fp = (status == 0) ? stdout : stderr; + + fprintf(fp, "usage: %s [-h] [-V | --version] command [arg ...]\n", + getprogname()); + if (hflag) + list_commands(fp); + exit(status); +} + +static const struct got_error * +apply_unveil(const char *repo_path, int repo_read_only) +{ + const struct got_error *err; + +#ifdef PROFILE + if (unveil("gmon.out", "rwc") != 0) + return got_error_from_errno2("unveil", "gmon.out"); +#endif + if (repo_path && unveil(repo_path, repo_read_only ? "r" : "rwc") != 0) + return got_error_from_errno2("unveil", repo_path); + + if (unveil(GOT_TMPDIR_STR, "rwc") != 0) + return got_error_from_errno2("unveil", GOT_TMPDIR_STR); + + err = got_privsep_unveil_exec_helpers(); + if (err != NULL) + return err; + + if (unveil(NULL, NULL) != 0) + return got_error_from_errno("unveil"); + + return NULL; +} + +__dead static void +usage_info(void) +{ + fprintf(stderr, "usage: %s info [-r repository-path]\n", + getprogname()); + exit(1); +} + +static const struct got_error * +cmd_info(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + char *cwd = NULL, *repo_path = NULL; + struct got_repository *repo = NULL; + const struct got_gotconfig *gotconfig = NULL; + int ch, npackfiles, npackedobj, nobj; + off_t packsize, loose_size; + char scaled[FMT_SCALED_STRSIZE]; + + while ((ch = getopt(argc, argv, "r:")) != -1) { + switch (ch) { + case 'r': + repo_path = realpath(optarg, NULL); + if (repo_path == NULL) + return got_error_from_errno2("realpath", + optarg); + got_path_strip_trailing_slashes(repo_path); + break; + default: + usage_info(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath flock proc exec sendfd unveil", + NULL) == -1) + err(1, "pledge"); +#endif + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_repo_open(&repo, repo_path ? repo_path : cwd, NULL); + if (error) + goto done; + + error = apply_unveil(got_repo_get_path_git_dir(repo), 1); + if (error) + goto done; + + printf("repository: %s\n", got_repo_get_path_git_dir(repo)); + + gotconfig = got_repo_get_gotconfig(repo); + if (gotconfig) { + const struct got_remote_repo *remotes; + int i, nremotes; + if (got_gotconfig_get_author(gotconfig)) { + printf("default author: %s\n", + got_gotconfig_get_author(gotconfig)); + } + got_gotconfig_get_remotes(&nremotes, &remotes, gotconfig); + for (i = 0; i < nremotes; i++) { + printf("remote \"%s\": %s\n", remotes[i].name, + remotes[i].url); + } + } + + error = got_repo_get_packfile_info(&npackfiles, &npackedobj, + &packsize, repo); + if (error) + goto done; + printf("pack files: %d\n", npackfiles); + if (npackfiles > 0) { + if (fmt_scaled(packsize, scaled) == -1) { + error = got_error_from_errno("fmt_scaled"); + goto done; + } + printf("packed objects: %d\n", npackedobj); + printf("packed total size: %s\n", scaled); + } + + error = got_repo_get_loose_object_info(&nobj, &loose_size, repo); + if (error) + goto done; + printf("loose objects: %d\n", nobj); + if (nobj > 0) { + if (fmt_scaled(loose_size, scaled) == -1) { + error = got_error_from_errno("fmt_scaled"); + goto done; + } + printf("loose total size: %s\n", scaled); + } +done: + if (repo) + got_repo_close(repo); + free(cwd); + return error; +} blob - 56d5bf0fafeca5450bd82577d1eb163c53c74a9e blob + 2584e7cad8d98fb4009517d5add53e9b00f9d55b --- include/got_repository.h +++ include/got_repository.h @@ -160,3 +160,11 @@ typedef const struct got_error *(*got_repo_import_cb)( const struct got_error *got_repo_import(struct got_object_id **, const char *, const char *, const char *, struct got_pathlist_head *, struct got_repository *, got_repo_import_cb, void *); + +/* Obtain the number and size of loose objects in the repository. */ +const struct got_error *got_repo_get_loose_object_info(int *nobjects, + off_t *ondisk_size, struct got_repository *); + +/* Obtain the number and size of packed objects in the repository. */ +const struct got_error *got_repo_get_packfile_info(int *npackfiles, + int *nobjects, off_t *total_packsize, struct got_repository *); blob - 56e526ad2a7c56e76b3d397516bbac2fb58a0910 blob + 6e2791cfc992633e50cd32c7bf6039b72faaf2a5 --- lib/repository.c +++ lib/repository.c @@ -1812,5 +1812,183 @@ got_repo_import(struct got_object_id **new_commit_id, err = got_object_commit_create(new_commit_id, new_tree_id, NULL, 0, author, time(NULL), author, time(NULL), logmsg, repo); free(new_tree_id); + return err; +} + +const struct got_error * +got_repo_get_loose_object_info(int *nobjects, off_t *ondisk_size, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *path_objects = NULL, *path = NULL; + DIR *dir = NULL; + struct got_object_id id; + int i; + + *nobjects = 0; + *ondisk_size = 0; + + path_objects = got_repo_get_path_objects(repo); + if (path_objects == NULL) + return got_error_from_errno("got_repo_get_path_objects"); + + for (i = 0; i <= 0xff; i++) { + struct dirent *dent; + + if (asprintf(&path, "%s/%.2x", path_objects, i) == -1) { + err = got_error_from_errno("asprintf"); + break; + } + + dir = opendir(path); + if (dir == NULL) { + if (errno == ENOENT) { + err = NULL; + continue; + } + err = got_error_from_errno2("opendir", path); + break; + } + + while ((dent = readdir(dir)) != NULL) { + char *id_str; + int fd; + struct stat sb; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + if (asprintf(&id_str, "%.2x%s", i, dent->d_name) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + if (!got_parse_sha1_digest(id.sha1, id_str)) { + free(id_str); + continue; + } + free(id_str); + + err = got_object_open_loose_fd(&fd, &id, repo); + if (err) + goto done; + + if (fstat(fd, &sb) == -1) { + err = got_error_from_errno("fstat"); + close(fd); + goto done; + } + (*nobjects)++; + (*ondisk_size) += sb.st_size; + + if (close(fd) == -1) { + err = got_error_from_errno("close"); + goto done; + } + } + + if (closedir(dir) != 0) { + err = got_error_from_errno("closedir"); + goto done; + } + dir = NULL; + + free(path); + path = NULL; + } +done: + if (dir && closedir(dir) != 0 && err == NULL) + err = got_error_from_errno("closedir"); + + if (err) { + *nobjects = 0; + *ondisk_size = 0; + } + free(path_objects); + free(path); return err; } + +const struct got_error * +got_repo_get_packfile_info(int *npackfiles, int *nobjects, + off_t *total_packsize, struct got_repository *repo) +{ + const struct got_error *err; + DIR *packdir = NULL; + struct dirent *dent; + struct got_packidx *packidx = NULL; + char *path_packidx; + char *path_packfile; + int packdir_fd; + struct stat sb; + + *npackfiles = 0; + *nobjects = 0; + *total_packsize = 0; + + packdir_fd = openat(got_repo_get_fd(repo), + GOT_OBJECTS_PACK_DIR, O_DIRECTORY); + if (packdir_fd == -1) { + return got_error_from_errno_fmt("openat: %s/%s", + got_repo_get_path_git_dir(repo), + GOT_OBJECTS_PACK_DIR); + } + + packdir = fdopendir(packdir_fd); + if (packdir == NULL) { + err = got_error_from_errno("fdopendir"); + goto done; + } + + while ((dent = readdir(packdir)) != NULL) { + if (!is_packidx_filename(dent->d_name, dent->d_namlen)) + continue; + + if (asprintf(&path_packidx, "%s/%s", GOT_OBJECTS_PACK_DIR, + dent->d_name) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + err = got_packidx_open(&packidx, got_repo_get_fd(repo), + path_packidx, 0); + free(path_packidx); + if (err) + goto done; + + if (fstat(packidx->fd, &sb) == -1) + goto done; + *total_packsize += sb.st_size; + + err = got_packidx_get_packfile_path(&path_packfile, packidx); + if (err) + goto done; + + if (fstatat(got_repo_get_fd(repo), path_packfile, &sb, + 0) == -1) { + free(path_packfile); + goto done; + } + free(path_packfile); + *total_packsize += sb.st_size; + + *nobjects += be32toh(packidx->hdr.fanout_table[0xff]); + + (*npackfiles)++; + + got_packidx_close(packidx); + packidx = NULL; + } +done: + if (packidx) + got_packidx_close(packidx); + if (packdir && closedir(packdir) != 0 && err == NULL) + err = got_error_from_errno("closedir"); + if (err) { + *npackfiles = 0; + *nobjects = 0; + *total_packsize = 0; + } + return err; +}