builtin/diagnose.c: create 'git diagnose' builtin

Create a 'git diagnose' builtin to generate a standalone zip archive of repository diagnostics. The "diagnose" functionality was originally implemented for Scalar in aa5c79a331 (scalar: implement `scalar diagnose`, 2022-05-28). However, the diagnostics gathered are not specific to Scalar-cloned repositories and can be useful when diagnosing issues in any Git repository. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Victoria Dye committed Aug 12, 2022 at 20:10 UTC 6783fd3cef0d6625e8a6d9d42d76042447078401
7 files changed +143
.gitignore
+1
@@ -53,6 +53,7 @@
53 /git-cvsimport
54 /git-cvsserver
55 /git-daemon
56 +/git-diagnose
57 /git-diff
58 /git-diff-files
59 /git-diff-index
Documentation/git-diagnose.txt new
+50
@@ -0,0 +1,50 @@
1 +git-diagnose(1)
2 +================
3 +
4 +NAME
5 +----
6 +git-diagnose - Generate a zip archive of diagnostic information
7 +
8 +SYNOPSIS
9 +--------
10 +[verse]
11 +'git diagnose' [(-o | --output-directory) <path>] [(-s | --suffix) <format>]
12 +
13 +DESCRIPTION
14 +-----------
15 +Collects detailed information about the user's machine, Git client, and
16 +repository state and packages that information into a zip archive. The
17 +generated archive can then, for example, be shared with the Git mailing list to
18 +help debug an issue or serve as a reference for independent debugging.
19 +
20 +The following information is captured in the archive:
21 +
22 + * 'git version --build-options'
23 + * The path to the repository root
24 + * The available disk space on the filesystem
25 + * The name and size of each packfile, including those in alternate object
26 + stores
27 + * The total count of loose objects, as well as counts broken down by
28 + `.git/objects` subdirectory
29 +
30 +This tool differs from linkgit:git-bugreport[1] in that it collects much more
31 +detailed information with a greater focus on reporting the size and data shape
32 +of repository contents.
33 +
34 +OPTIONS
35 +-------
36 +-o <path>::
37 +--output-directory <path>::
38 + Place the resulting diagnostics archive in `<path>` instead of the
39 + current directory.
40 +
41 +-s <format>::
42 +--suffix <format>::
43 + Specify an alternate suffix for the diagnostics archive name, to create
44 + a file named 'git-diagnostics-<formatted suffix>'. This should take the
45 + form of a strftime(3) format string; the current local time will be
46 + used.
47 +
48 +GIT
49 +---
50 +Part of the linkgit:git[1] suite
Makefile
+1
@@ -1154,6 +1154,7 @@ BUILTIN_OBJS += builtin/credential-cache.o
1154 BUILTIN_OBJS += builtin/credential-store.o
1155 BUILTIN_OBJS += builtin/credential.o
1156 BUILTIN_OBJS += builtin/describe.o
1157 +BUILTIN_OBJS += builtin/diagnose.o
1158 BUILTIN_OBJS += builtin/diff-files.o
1159 BUILTIN_OBJS += builtin/diff-index.o
1160 BUILTIN_OBJS += builtin/diff-tree.o
builtin.h
+1
@@ -144,6 +144,7 @@ int cmd_credential_cache(int argc, const char **argv, const char *prefix);
144 int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix);
145 int cmd_credential_store(int argc, const char **argv, const char *prefix);
146 int cmd_describe(int argc, const char **argv, const char *prefix);
147 +int cmd_diagnose(int argc, const char **argv, const char *prefix);
148 int cmd_diff_files(int argc, const char **argv, const char *prefix);
149 int cmd_diff_index(int argc, const char **argv, const char *prefix);
150 int cmd_diff(int argc, const char **argv, const char *prefix);
builtin/diagnose.c new
+57
@@ -0,0 +1,57 @@
1 +#include "builtin.h"
2 +#include "parse-options.h"
3 +#include "diagnose.h"
4 +
5 +static const char * const diagnose_usage[] = {
6 + N_("git diagnose [-o|--output-directory <path>] [-s|--suffix <format>]"),
7 + NULL
8 +};
9 +
10 +int cmd_diagnose(int argc, const char **argv, const char *prefix)
11 +{
12 + struct strbuf zip_path = STRBUF_INIT;
13 + time_t now = time(NULL);
14 + struct tm tm;
15 + char *option_output = NULL;
16 + char *option_suffix = "%Y-%m-%d-%H%M";
17 + char *prefixed_filename;
18 +
19 + const struct option diagnose_options[] = {
20 + OPT_STRING('o', "output-directory", &option_output, N_("path"),
21 + N_("specify a destination for the diagnostics archive")),
22 + OPT_STRING('s', "suffix", &option_suffix, N_("format"),
23 + N_("specify a strftime format suffix for the filename")),
24 + OPT_END()
25 + };
26 +
27 + argc = parse_options(argc, argv, prefix, diagnose_options,
28 + diagnose_usage, 0);
29 +
30 + /* Prepare the path to put the result */
31 + prefixed_filename = prefix_filename(prefix,
32 + option_output ? option_output : "");
33 + strbuf_addstr(&zip_path, prefixed_filename);
34 + strbuf_complete(&zip_path, '/');
35 +
36 + strbuf_addstr(&zip_path, "git-diagnostics-");
37 + strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0);
38 + strbuf_addstr(&zip_path, ".zip");
39 +
40 + switch (safe_create_leading_directories(zip_path.buf)) {
41 + case SCLD_OK:
42 + case SCLD_EXISTS:
43 + break;
44 + default:
45 + die_errno(_("could not create leading directories for '%s'"),
46 + zip_path.buf);
47 + }
48 +
49 + /* Prepare diagnostics */
50 + if (create_diagnostics_archive(&zip_path, DIAGNOSE_STATS))
51 + die_errno(_("unable to create diagnostics archive %s"),
52 + zip_path.buf);
53 +
54 + free(prefixed_filename);
55 + strbuf_release(&zip_path);
56 + return 0;
57 +}
git.c
+1
@@ -522,6 +522,7 @@ static struct cmd_struct commands[] = {
522 { "credential-cache--daemon", cmd_credential_cache_daemon },
523 { "credential-store", cmd_credential_store },
524 { "describe", cmd_describe, RUN_SETUP },
525 + { "diagnose", cmd_diagnose, RUN_SETUP_GENTLY },
526 { "diff", cmd_diff, NO_PARSEOPT },
527 { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
528 { "diff-index", cmd_diff_index, RUN_SETUP | NO_PARSEOPT },
t/t0092-diagnose.sh new
+32
@@ -0,0 +1,32 @@
1 +#!/bin/sh
2 +
3 +test_description='git diagnose'
4 +
5 +TEST_PASSES_SANITIZE_LEAK=true
6 +. ./test-lib.sh
7 +
8 +test_expect_success UNZIP 'creates diagnostics zip archive' '
9 + test_when_finished rm -rf report &&
10 +
11 + git diagnose -o report -s test >out &&
12 + grep "Available space" out &&
13 +
14 + zip_path=report/git-diagnostics-test.zip &&
15 + test_path_is_file "$zip_path" &&
16 +
17 + # Check zipped archive content
18 + "$GIT_UNZIP" -p "$zip_path" diagnostics.log >out &&
19 + test_file_not_empty out &&
20 +
21 + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
22 + grep ".git/objects" out &&
23 +
24 + "$GIT_UNZIP" -p "$zip_path" objects-local.txt >out &&
25 + grep "^Total: [0-9][0-9]*" out &&
26 +
27 + # Should not include .git directory contents by default
28 + ! "$GIT_UNZIP" -l "$zip_path" | grep ".git/"
29 + grep "^Total: [0-9][0-9]*" out
30 +'
31 +
32 +test_done