Merge branch 'jt/doc-submitting-rerolled-series'
[git/gitster.git] / builtin / bugreport.c
blob25f860a0d973caca44593cb28919e47afb13089d
1 #include "builtin.h"
2 #include "abspath.h"
3 #include "editor.h"
4 #include "gettext.h"
5 #include "parse-options.h"
6 #include "strbuf.h"
7 #include "help.h"
8 #include "compat/compiler.h"
9 #include "hook.h"
10 #include "hook-list.h"
11 #include "diagnose.h"
12 #include "object-file.h"
13 #include "setup.h"
15 static void get_system_info(struct strbuf *sys_info)
17 struct utsname uname_info;
18 char *shell = NULL;
20 /* get git version from native cmd */
21 strbuf_addstr(sys_info, _("git version:\n"));
22 get_version_info(sys_info, 1);
24 /* system call for other version info */
25 strbuf_addstr(sys_info, "uname: ");
26 if (uname(&uname_info))
27 strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
28 strerror(errno),
29 errno);
30 else
31 strbuf_addf(sys_info, "%s %s %s %s\n",
32 uname_info.sysname,
33 uname_info.release,
34 uname_info.version,
35 uname_info.machine);
37 strbuf_addstr(sys_info, _("compiler info: "));
38 get_compiler_info(sys_info);
40 strbuf_addstr(sys_info, _("libc info: "));
41 get_libc_info(sys_info);
43 shell = getenv("SHELL");
44 strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n",
45 shell ? shell : "<unset>");
48 static void get_populated_hooks(struct strbuf *hook_info, int nongit)
50 const char **p;
52 if (nongit) {
53 strbuf_addstr(hook_info,
54 _("not run from a git repository - no hooks to show\n"));
55 return;
58 for (p = hook_name_list; *p; p++) {
59 const char *hook = *p;
61 if (hook_exists(hook))
62 strbuf_addf(hook_info, "%s\n", hook);
66 static const char * const bugreport_usage[] = {
67 N_("git bugreport [(-o | --output-directory) <path>]\n"
68 " [(-s | --suffix) <format> | --no-suffix]\n"
69 " [--diagnose[=<mode>]]"),
70 NULL
73 static int get_bug_template(struct strbuf *template)
75 const char template_text[] = N_(
76 "Thank you for filling out a Git bug report!\n"
77 "Please answer the following questions to help us understand your issue.\n"
78 "\n"
79 "What did you do before the bug happened? (Steps to reproduce your issue)\n"
80 "\n"
81 "What did you expect to happen? (Expected behavior)\n"
82 "\n"
83 "What happened instead? (Actual behavior)\n"
84 "\n"
85 "What's different between what you expected and what actually happened?\n"
86 "\n"
87 "Anything else you want to add:\n"
88 "\n"
89 "Please review the rest of the bug report below.\n"
90 "You can delete any lines you don't wish to share.\n");
92 strbuf_addstr(template, _(template_text));
93 return 0;
96 static void get_header(struct strbuf *buf, const char *title)
98 strbuf_addf(buf, "\n\n[%s]\n", title);
101 int cmd_bugreport(int argc, const char **argv, const char *prefix)
103 struct strbuf buffer = STRBUF_INIT;
104 struct strbuf report_path = STRBUF_INIT;
105 int report = -1;
106 time_t now = time(NULL);
107 struct tm tm;
108 enum diagnose_mode diagnose = DIAGNOSE_NONE;
109 char *option_output = NULL;
110 char *option_suffix = "%Y-%m-%d-%H%M";
111 const char *user_relative_path = NULL;
112 char *prefixed_filename;
113 size_t output_path_len;
114 int ret;
116 const struct option bugreport_options[] = {
117 OPT_CALLBACK_F(0, "diagnose", &diagnose, N_("mode"),
118 N_("create an additional zip archive of detailed diagnostics (default 'stats')"),
119 PARSE_OPT_OPTARG, option_parse_diagnose),
120 OPT_STRING('o', "output-directory", &option_output, N_("path"),
121 N_("specify a destination for the bugreport file(s)")),
122 OPT_STRING('s', "suffix", &option_suffix, N_("format"),
123 N_("specify a strftime format suffix for the filename(s)")),
124 OPT_END()
127 argc = parse_options(argc, argv, prefix, bugreport_options,
128 bugreport_usage, 0);
130 if (argc) {
131 error(_("unknown argument `%s'"), argv[0]);
132 usage(bugreport_usage[0]);
135 /* Prepare the path to put the result */
136 prefixed_filename = prefix_filename(prefix,
137 option_output ? option_output : "");
138 strbuf_addstr(&report_path, prefixed_filename);
139 strbuf_complete(&report_path, '/');
140 output_path_len = report_path.len;
142 strbuf_addstr(&report_path, "git-bugreport");
143 if (option_suffix) {
144 strbuf_addch(&report_path, '-');
145 strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0);
147 strbuf_addstr(&report_path, ".txt");
149 switch (safe_create_leading_directories(report_path.buf)) {
150 case SCLD_OK:
151 case SCLD_EXISTS:
152 break;
153 default:
154 die(_("could not create leading directories for '%s'"),
155 report_path.buf);
158 /* Prepare diagnostics, if requested */
159 if (diagnose != DIAGNOSE_NONE) {
160 struct strbuf zip_path = STRBUF_INIT;
161 strbuf_add(&zip_path, report_path.buf, output_path_len);
162 strbuf_addstr(&zip_path, "git-diagnostics-");
163 strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0);
164 strbuf_addstr(&zip_path, ".zip");
166 if (create_diagnostics_archive(&zip_path, diagnose))
167 die_errno(_("unable to create diagnostics archive %s"), zip_path.buf);
169 strbuf_release(&zip_path);
172 /* Prepare the report contents */
173 get_bug_template(&buffer);
175 get_header(&buffer, _("System Info"));
176 get_system_info(&buffer);
178 get_header(&buffer, _("Enabled Hooks"));
179 get_populated_hooks(&buffer, !startup_info->have_repository);
181 /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
182 report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
184 if (write_in_full(report, buffer.buf, buffer.len) < 0)
185 die_errno(_("unable to write to %s"), report_path.buf);
187 close(report);
190 * We want to print the path relative to the user, but we still need the
191 * path relative to us to give to the editor.
193 if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
194 user_relative_path = report_path.buf;
195 fprintf(stderr, _("Created new report at '%s'.\n"),
196 user_relative_path);
198 free(prefixed_filename);
199 strbuf_release(&buffer);
201 ret = !!launch_editor(report_path.buf, NULL, NULL);
202 strbuf_release(&report_path);
203 return ret;