]> git.ipfire.org Git - thirdparty/git.git/commitdiff
bugreport: add tool to generate debugging info
authorEmily Shaffer <emilyshaffer@google.com>
Thu, 16 Apr 2020 21:18:04 +0000 (14:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 16 Apr 2020 22:23:42 +0000 (15:23 -0700)
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>
.gitignore
Documentation/git-bugreport.txt [new file with mode: 0644]
Makefile
bugreport.c [new file with mode: 0644]
command-list.txt
strbuf.c
strbuf.h
t/t0091-bugreport.sh [new file with mode: 0755]

index ea97de83f395008bc5099279a3f03d0a224aad57..d89bf9e11eec6c2264457bc670e94d112d1f37d7 100644 (file)
@@ -25,6 +25,7 @@
 /git-bisect--helper
 /git-blame
 /git-branch
+/git-bugreport
 /git-bundle
 /git-cat-file
 /git-check-attr
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
new file mode 100644 (file)
index 0000000..1f9fde5
--- /dev/null
@@ -0,0 +1,46 @@
+git-bugreport(1)
+================
+
+NAME
+----
+git-bugreport - Collect information for user to file a bug report
+
+SYNOPSIS
+--------
+[verse]
+'git bugreport' [(-o | --output-directory) <path>] [(-s | --suffix) <format>]
+
+DESCRIPTION
+-----------
+Captures information about the user's machine, Git client, and repository state,
+as well as a form requesting information about the behavior the user observed,
+into a single text file which the user can then share, for example to the Git
+mailing list, in order to report an observed bug.
+
+The following information is requested from the user:
+
+ - Reproduction steps
+ - Expected behavior
+ - Actual behavior
+
+This tool is invoked via the typical Git setup process, which means that in some
+cases, it might not be able to launch - for example, if a relevant config file
+is unreadable. In this kind of scenario, it may be helpful to manually gather
+the kind of information listed above when manually asking for help.
+
+OPTIONS
+-------
+-o <path>::
+--output-directory <path>::
+       Place the resulting bug report file in `<path>` instead of the root of
+       the Git repository.
+
+-s <format>::
+--suffix <format>::
+       Specify an alternate suffix for the bugreport name, to create a file
+       named 'git-bugreport-<formatted suffix>'. This should take the form of a
+       link:strftime[3] format string; the current local time will be used.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 5a022367d41faa52d77d2ae292a9a852488be169..a01a050aa3ea817787d248a981a7d405d4cd33bc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -681,6 +681,7 @@ EXTRA_PROGRAMS =
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
 
+PROGRAM_OBJS += bugreport.o
 PROGRAM_OBJS += credential-store.o
 PROGRAM_OBJS += daemon.o
 PROGRAM_OBJS += fast-import.o
@@ -2457,6 +2458,10 @@ endif
 git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
+git-bugreport$X: bugreport.o GIT-LDFLAGS $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS)
+
 git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(IMAP_SEND_LDFLAGS) $(LIBS)
diff --git a/bugreport.c b/bugreport.c
new file mode 100644 (file)
index 0000000..f6f53a5
--- /dev/null
@@ -0,0 +1,105 @@
+#include "builtin.h"
+#include "parse-options.h"
+#include "stdio.h"
+#include "strbuf.h"
+#include "time.h"
+
+static const char * const bugreport_usage[] = {
+       N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"),
+       NULL
+};
+
+static int get_bug_template(struct strbuf *template)
+{
+       const char template_text[] = N_(
+"Thank you for filling out a Git bug report!\n"
+"Please answer the following questions to help us understand your issue.\n"
+"\n"
+"What did you do before the bug happened? (Steps to reproduce your issue)\n"
+"\n"
+"What did you expect to happen? (Expected behavior)\n"
+"\n"
+"What happened instead? (Actual behavior)\n"
+"\n"
+"What's different between what you expected and what actually happened?\n"
+"\n"
+"Anything else you want to add:\n"
+"\n"
+"Please review the rest of the bug report below.\n"
+"You can delete any lines you don't wish to share.\n");
+
+       strbuf_addstr(template, _(template_text));
+       return 0;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+       struct strbuf buffer = STRBUF_INIT;
+       struct strbuf report_path = STRBUF_INIT;
+       int report = -1;
+       time_t now = time(NULL);
+       char *option_output = NULL;
+       char *option_suffix = "%Y-%m-%d-%H%M";
+       int nongit_ok = 0;
+       const char *prefix = NULL;
+       const char *user_relative_path = NULL;
+
+       const struct option bugreport_options[] = {
+               OPT_STRING('o', "output-directory", &option_output, N_("path"),
+                          N_("specify a destination for the bugreport file")),
+               OPT_STRING('s', "suffix", &option_suffix, N_("format"),
+                          N_("specify a strftime format suffix for the filename")),
+               OPT_END()
+       };
+
+       prefix = setup_git_directory_gently(&nongit_ok);
+
+       argc = parse_options(argc, argv, prefix, bugreport_options,
+                            bugreport_usage, 0);
+
+       /* Prepare the path to put the result */
+       strbuf_addstr(&report_path,
+                     prefix_filename(prefix,
+                                     option_output ? option_output : ""));
+       strbuf_complete(&report_path, '/');
+
+       strbuf_addstr(&report_path, "git-bugreport-");
+       strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0);
+       strbuf_addstr(&report_path, ".txt");
+
+       switch (safe_create_leading_directories(report_path.buf)) {
+       case SCLD_OK:
+       case SCLD_EXISTS:
+               break;
+       default:
+               die(_("could not create leading directories for '%s'"),
+                   report_path.buf);
+       }
+
+       /* Prepare the report contents */
+       get_bug_template(&buffer);
+
+       /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
+       report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
+
+       if (report < 0) {
+               UNLEAK(report_path);
+               die(_("couldn't create a new file at '%s'"), report_path.buf);
+       }
+
+       strbuf_write_fd(&buffer, report);
+       close(report);
+
+       /*
+        * We want to print the path relative to the user, but we still need the
+        * path relative to us to give to the editor.
+        */
+       if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
+               user_relative_path = report_path.buf;
+       fprintf(stderr, _("Created new report at '%s'.\n"),
+               user_relative_path);
+
+       UNLEAK(buffer);
+       UNLEAK(report_path);
+       return !!launch_editor(report_path.buf, NULL, NULL);
+}
index 20878946558847e35261a7f99a22405e4d9707d0..185e5e3f05fbc492b7d3849bbe0015c2eff9a195 100644 (file)
@@ -54,6 +54,7 @@ git-archive                             mainporcelain
 git-bisect                              mainporcelain           info
 git-blame                               ancillaryinterrogators          complete
 git-branch                              mainporcelain           history
+git-bugreport                           ancillaryinterrogators
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
index f19da55b0783dc2d1bf4cab0e0ce76be5711cc55..f1d66c7848e4dc68af0b89d05893cf77890a9930 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -539,6 +539,10 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
        return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
 }
 
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd)
+{
+       return sb->len ? write(fd, sb->buf, sb->len) : 0;
+}
 
 #define STRBUF_MAXLINK (2*PATH_MAX)
 
index bfa66569a4bffd063578477e68010a5ccc14f5a3..1eaa944a4f0ad3546a8384908bd693bd873ceba7 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -450,6 +450,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
  * NUL bytes.
  */
 ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd);
 
 /**
  * Read a line from a FILE *, overwriting the existing contents of
diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh
new file mode 100755 (executable)
index 0000000..2e73658
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='git bugreport'
+
+. ./test-lib.sh
+
+# Headers "[System Info]" will be followed by a non-empty line if we put some
+# information there; we can make sure all our headers were followed by some
+# information to check if the command was successful.
+HEADER_PATTERN="^\[.*\]$"
+
+check_all_headers_populated () {
+       while read -r line
+       do
+               if test "$(grep "$HEADER_PATTERN" "$line")"
+               then
+                       echo "$line"
+                       read -r nextline
+                       if test -z "$nextline"; then
+                               return 1;
+                       fi
+               fi
+       done
+}
+
+test_expect_success 'creates a report with content in the right places' '
+       test_when_finished rm git-bugreport-check-headers.txt &&
+       git bugreport -s check-headers &&
+       check_all_headers_populated <git-bugreport-check-headers.txt
+'
+
+test_expect_success 'dies if file with same name as report already exists' '
+       test_when_finished rm git-bugreport-duplicate.txt &&
+       >>git-bugreport-duplicate.txt &&
+       test_must_fail git bugreport --suffix duplicate
+'
+
+test_expect_success '--output-directory puts the report in the provided dir' '
+       test_when_finished rm -fr foo/ &&
+       git bugreport -o foo/ &&
+       test_path_is_file foo/git-bugreport-*
+'
+
+test_expect_success 'incorrect arguments abort with usage' '
+       test_must_fail git bugreport --false 2>output &&
+       test_i18ngrep usage output &&
+       test_path_is_missing git-bugreport-*
+'
+
+test_expect_success 'runs outside of a git dir' '
+       test_when_finished rm non-repo/git-bugreport-* &&
+       nongit git bugreport
+'
+
+test_expect_success 'can create leading directories outside of a git dir' '
+       test_when_finished rm -fr foo/bar/baz &&
+       nongit git bugreport -o foo/bar/baz
+'
+
+
+test_done