bugreport: add tool to generate debugging info
Teach Git how to prompt the user for a good bug report: reproduction steps, expected behavior, and actual behavior. Later, Git can learn how to collect some diagnostic information from the repository. If users can send us a well-written bug report which contains diagnostic information we would otherwise need to ask the user for, we can reduce the number of question-and-answer round trips between the reporter and the Git contributor. Users may also wish to send a report like this to their local "Git expert" if they have put their repository into a state they are confused by. Signed-off-by: Emily Shaffer <emilyshaffer@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Emily Shaffer committed
Apr 16, 2020 at 14:18 UTC
238b439d69890980dafc5154895d425cb4cf4a5e
8 files changed
+224
.gitignore
+1
@@ -25,6 +25,7 @@
25
/git-bisect--helper
26
/git-blame
27
/git-branch
28
+/git-bugreport
29
/git-bundle
30
/git-cat-file
31
/git-check-attr
Documentation/git-bugreport.txt
new
+46
@@ -0,0 +1,46 @@
1
+git-bugreport(1)
2
+================
3
+
4
+NAME
5
+----
6
+git-bugreport - Collect information for user to file a bug report
7
+
8
+SYNOPSIS
9
+--------
10
+[verse]
11
+'git bugreport' [(-o | --output-directory) <path>] [(-s | --suffix) <format>]
12
+
13
+DESCRIPTION
14
+-----------
15
+Captures information about the user's machine, Git client, and repository state,
16
+as well as a form requesting information about the behavior the user observed,
17
+into a single text file which the user can then share, for example to the Git
18
+mailing list, in order to report an observed bug.
19
+
20
+The following information is requested from the user:
21
+
22
+ - Reproduction steps
23
+ - Expected behavior
24
+ - Actual behavior
25
+
26
+This tool is invoked via the typical Git setup process, which means that in some
27
+cases, it might not be able to launch - for example, if a relevant config file
28
+is unreadable. In this kind of scenario, it may be helpful to manually gather
29
+the kind of information listed above when manually asking for help.
30
+
31
+OPTIONS
32
+-------
33
+-o <path>::
34
+--output-directory <path>::
35
+ Place the resulting bug report file in `<path>` instead of the root of
36
+ the Git repository.
37
+
38
+-s <format>::
39
+--suffix <format>::
40
+ Specify an alternate suffix for the bugreport name, to create a file
41
+ named 'git-bugreport-<formatted suffix>'. This should take the form of a
42
+ link:strftime[3] format string; the current local time will be used.
43
+
44
+GIT
45
+---
46
+Part of the linkgit:git[1] suite
Makefile
+5
@@ -681,6 +681,7 @@ EXTRA_PROGRAMS =
681
# ... and all the rest that could be moved out of bindir to gitexecdir
682
PROGRAMS += $(EXTRA_PROGRAMS)
683
684
+PROGRAM_OBJS += bugreport.o
685
PROGRAM_OBJS += credential-store.o
686
PROGRAM_OBJS += daemon.o
687
PROGRAM_OBJS += fast-import.o
@@ -2457,6 +2458,10 @@ endif
2458
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
2459
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
2460
2461
+git-bugreport$X: bugreport.o GIT-LDFLAGS $(GITLIBS)
2462
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
2463
+ $(LIBS)
2464
+
2465
git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
2466
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
2467
$(IMAP_SEND_LDFLAGS) $(LIBS)
bugreport.c
new
+105
@@ -0,0 +1,105 @@
1
+#include "builtin.h"
2
+#include "parse-options.h"
3
+#include "stdio.h"
4
+#include "strbuf.h"
5
+#include "time.h"
6
+
7
+static const char * const bugreport_usage[] = {
8
+ N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"),
9
+ NULL
10
+};
11
+
12
+static int get_bug_template(struct strbuf *template)
13
+{
14
+ const char template_text[] = N_(
15
+"Thank you for filling out a Git bug report!\n"
16
+"Please answer the following questions to help us understand your issue.\n"
17
+"\n"
18
+"What did you do before the bug happened? (Steps to reproduce your issue)\n"
19
+"\n"
20
+"What did you expect to happen? (Expected behavior)\n"
21
+"\n"
22
+"What happened instead? (Actual behavior)\n"
23
+"\n"
24
+"What's different between what you expected and what actually happened?\n"
25
+"\n"
26
+"Anything else you want to add:\n"
27
+"\n"
28
+"Please review the rest of the bug report below.\n"
29
+"You can delete any lines you don't wish to share.\n");
30
+
31
+ strbuf_addstr(template, _(template_text));
32
+ return 0;
33
+}
34
+
35
+int cmd_main(int argc, const char **argv)
36
+{
37
+ struct strbuf buffer = STRBUF_INIT;
38
+ struct strbuf report_path = STRBUF_INIT;
39
+ int report = -1;
40
+ time_t now = time(NULL);
41
+ char *option_output = NULL;
42
+ char *option_suffix = "%Y-%m-%d-%H%M";
43
+ int nongit_ok = 0;
44
+ const char *prefix = NULL;
45
+ const char *user_relative_path = NULL;
46
+
47
+ const struct option bugreport_options[] = {
48
+ OPT_STRING('o', "output-directory", &option_output, N_("path"),
49
+ N_("specify a destination for the bugreport file")),
50
+ OPT_STRING('s', "suffix", &option_suffix, N_("format"),
51
+ N_("specify a strftime format suffix for the filename")),
52
+ OPT_END()
53
+ };
54
+
55
+ prefix = setup_git_directory_gently(&nongit_ok);
56
+
57
+ argc = parse_options(argc, argv, prefix, bugreport_options,
58
+ bugreport_usage, 0);
59
+
60
+ /* Prepare the path to put the result */
61
+ strbuf_addstr(&report_path,
62
+ prefix_filename(prefix,
63
+ option_output ? option_output : ""));
64
+ strbuf_complete(&report_path, '/');
65
+
66
+ strbuf_addstr(&report_path, "git-bugreport-");
67
+ strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0);
68
+ strbuf_addstr(&report_path, ".txt");
69
+
70
+ switch (safe_create_leading_directories(report_path.buf)) {
71
+ case SCLD_OK:
72
+ case SCLD_EXISTS:
73
+ break;
74
+ default:
75
+ die(_("could not create leading directories for '%s'"),
76
+ report_path.buf);
77
+ }
78
+
79
+ /* Prepare the report contents */
80
+ get_bug_template(&buffer);
81
+
82
+ /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
83
+ report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
84
+
85
+ if (report < 0) {
86
+ UNLEAK(report_path);
87
+ die(_("couldn't create a new file at '%s'"), report_path.buf);
88
+ }
89
+
90
+ strbuf_write_fd(&buffer, report);
91
+ close(report);
92
+
93
+ /*
94
+ * We want to print the path relative to the user, but we still need the
95
+ * path relative to us to give to the editor.
96
+ */
97
+ if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
98
+ user_relative_path = report_path.buf;
99
+ fprintf(stderr, _("Created new report at '%s'.\n"),
100
+ user_relative_path);
101
+
102
+ UNLEAK(buffer);
103
+ UNLEAK(report_path);
104
+ return !!launch_editor(report_path.buf, NULL, NULL);
105
+}
command-list.txt
+1
@@ -54,6 +54,7 @@ git-archive mainporcelain
54
git-bisect mainporcelain info
55
git-blame ancillaryinterrogators complete
56
git-branch mainporcelain history
57
+git-bugreport ancillaryinterrogators
58
git-bundle mainporcelain
59
git-cat-file plumbinginterrogators
60
git-check-attr purehelpers
strbuf.c
+4
@@ -539,6 +539,10 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
539
return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
540
}
541
542
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd)
543
+{
544
+ return sb->len ? write(fd, sb->buf, sb->len) : 0;
545
+}
546
547
#define STRBUF_MAXLINK (2*PATH_MAX)
548
strbuf.h
+1
@@ -450,6 +450,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
450
* NUL bytes.
451
*/
452
ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
453
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd);
454
455
/**
456
* Read a line from a FILE *, overwriting the existing contents of
t/t0091-bugreport.sh
new
+61
@@ -0,0 +1,61 @@
1
+#!/bin/sh
2
+
3
+test_description='git bugreport'
4
+
5
+. ./test-lib.sh
6
+
7
+# Headers "[System Info]" will be followed by a non-empty line if we put some
8
+# information there; we can make sure all our headers were followed by some
9
+# information to check if the command was successful.
10
+HEADER_PATTERN="^\[.*\]$"
11
+
12
+check_all_headers_populated () {
13
+ while read -r line
14
+ do
15
+ if test "$(grep "$HEADER_PATTERN" "$line")"
16
+ then
17
+ echo "$line"
18
+ read -r nextline
19
+ if test -z "$nextline"; then
20
+ return 1;
21
+ fi
22
+ fi
23
+ done
24
+}
25
+
26
+test_expect_success 'creates a report with content in the right places' '
27
+ test_when_finished rm git-bugreport-check-headers.txt &&
28
+ git bugreport -s check-headers &&
29
+ check_all_headers_populated <git-bugreport-check-headers.txt
30
+'
31
+
32
+test_expect_success 'dies if file with same name as report already exists' '
33
+ test_when_finished rm git-bugreport-duplicate.txt &&
34
+ >>git-bugreport-duplicate.txt &&
35
+ test_must_fail git bugreport --suffix duplicate
36
+'
37
+
38
+test_expect_success '--output-directory puts the report in the provided dir' '
39
+ test_when_finished rm -fr foo/ &&
40
+ git bugreport -o foo/ &&
41
+ test_path_is_file foo/git-bugreport-*
42
+'
43
+
44
+test_expect_success 'incorrect arguments abort with usage' '
45
+ test_must_fail git bugreport --false 2>output &&
46
+ test_i18ngrep usage output &&
47
+ test_path_is_missing git-bugreport-*
48
+'
49
+
50
+test_expect_success 'runs outside of a git dir' '
51
+ test_when_finished rm non-repo/git-bugreport-* &&
52
+ nongit git bugreport
53
+'
54
+
55
+test_expect_success 'can create leading directories outside of a git dir' '
56
+ test_when_finished rm -fr foo/bar/baz &&
57
+ nongit git bugreport -o foo/bar/baz
58
+'
59
+
60
+
61
+test_done