git-merge-tree.txt: replace spurious HTML entity
[git.git] / builtin / bugreport.c
blob96052541cbfe74cda5aff77396c94f4fb6397ae7
1 #include "builtin.h"
2 #include "parse-options.h"
3 #include "strbuf.h"
4 #include "help.h"
5 #include "compat/compiler.h"
6 #include "hook.h"
7 #include "hook-list.h"
8 #include "diagnose.h"
11 static void get_system_info(struct strbuf *sys_info)
13 struct utsname uname_info;
14 char *shell = NULL;
16 /* get git version from native cmd */
17 strbuf_addstr(sys_info, _("git version:\n"));
18 get_version_info(sys_info, 1);
20 /* system call for other version info */
21 strbuf_addstr(sys_info, "uname: ");
22 if (uname(&uname_info))
23 strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
24 strerror(errno),
25 errno);
26 else
27 strbuf_addf(sys_info, "%s %s %s %s\n",
28 uname_info.sysname,
29 uname_info.release,
30 uname_info.version,
31 uname_info.machine);
33 strbuf_addstr(sys_info, _("compiler info: "));
34 get_compiler_info(sys_info);
36 strbuf_addstr(sys_info, _("libc info: "));
37 get_libc_info(sys_info);
39 shell = getenv("SHELL");
40 strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n",
41 shell ? shell : "<unset>");
44 static void get_populated_hooks(struct strbuf *hook_info, int nongit)
46 const char **p;
48 if (nongit) {
49 strbuf_addstr(hook_info,
50 _("not run from a git repository - no hooks to show\n"));
51 return;
54 for (p = hook_name_list; *p; p++) {
55 const char *hook = *p;
57 if (hook_exists(hook))
58 strbuf_addf(hook_info, "%s\n", hook);
62 static const char * const bugreport_usage[] = {
63 N_("git bugreport [(-o | --output-directory) <path>] [(-s | --suffix) <format>]\n"
64 " [--diagnose[=<mode>]]"),
65 NULL
68 static int get_bug_template(struct strbuf *template)
70 const char template_text[] = N_(
71 "Thank you for filling out a Git bug report!\n"
72 "Please answer the following questions to help us understand your issue.\n"
73 "\n"
74 "What did you do before the bug happened? (Steps to reproduce your issue)\n"
75 "\n"
76 "What did you expect to happen? (Expected behavior)\n"
77 "\n"
78 "What happened instead? (Actual behavior)\n"
79 "\n"
80 "What's different between what you expected and what actually happened?\n"
81 "\n"
82 "Anything else you want to add:\n"
83 "\n"
84 "Please review the rest of the bug report below.\n"
85 "You can delete any lines you don't wish to share.\n");
87 strbuf_addstr(template, _(template_text));
88 return 0;
91 static void get_header(struct strbuf *buf, const char *title)
93 strbuf_addf(buf, "\n\n[%s]\n", title);
96 int cmd_bugreport(int argc, const char **argv, const char *prefix)
98 struct strbuf buffer = STRBUF_INIT;
99 struct strbuf report_path = STRBUF_INIT;
100 int report = -1;
101 time_t now = time(NULL);
102 struct tm tm;
103 enum diagnose_mode diagnose = DIAGNOSE_NONE;
104 char *option_output = NULL;
105 char *option_suffix = "%Y-%m-%d-%H%M";
106 const char *user_relative_path = NULL;
107 char *prefixed_filename;
108 size_t output_path_len;
110 const struct option bugreport_options[] = {
111 OPT_CALLBACK_F(0, "diagnose", &diagnose, N_("mode"),
112 N_("create an additional zip archive of detailed diagnostics (default 'stats')"),
113 PARSE_OPT_OPTARG, option_parse_diagnose),
114 OPT_STRING('o', "output-directory", &option_output, N_("path"),
115 N_("specify a destination for the bugreport file(s)")),
116 OPT_STRING('s', "suffix", &option_suffix, N_("format"),
117 N_("specify a strftime format suffix for the filename(s)")),
118 OPT_END()
121 argc = parse_options(argc, argv, prefix, bugreport_options,
122 bugreport_usage, 0);
124 /* Prepare the path to put the result */
125 prefixed_filename = prefix_filename(prefix,
126 option_output ? option_output : "");
127 strbuf_addstr(&report_path, prefixed_filename);
128 strbuf_complete(&report_path, '/');
129 output_path_len = report_path.len;
131 strbuf_addstr(&report_path, "git-bugreport-");
132 strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0);
133 strbuf_addstr(&report_path, ".txt");
135 switch (safe_create_leading_directories(report_path.buf)) {
136 case SCLD_OK:
137 case SCLD_EXISTS:
138 break;
139 default:
140 die(_("could not create leading directories for '%s'"),
141 report_path.buf);
144 /* Prepare diagnostics, if requested */
145 if (diagnose != DIAGNOSE_NONE) {
146 struct strbuf zip_path = STRBUF_INIT;
147 strbuf_add(&zip_path, report_path.buf, output_path_len);
148 strbuf_addstr(&zip_path, "git-diagnostics-");
149 strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0);
150 strbuf_addstr(&zip_path, ".zip");
152 if (create_diagnostics_archive(&zip_path, diagnose))
153 die_errno(_("unable to create diagnostics archive %s"), zip_path.buf);
155 strbuf_release(&zip_path);
158 /* Prepare the report contents */
159 get_bug_template(&buffer);
161 get_header(&buffer, _("System Info"));
162 get_system_info(&buffer);
164 get_header(&buffer, _("Enabled Hooks"));
165 get_populated_hooks(&buffer, !startup_info->have_repository);
167 /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
168 report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
170 if (write_in_full(report, buffer.buf, buffer.len) < 0)
171 die_errno(_("unable to write to %s"), report_path.buf);
173 close(report);
176 * We want to print the path relative to the user, but we still need the
177 * path relative to us to give to the editor.
179 if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
180 user_relative_path = report_path.buf;
181 fprintf(stderr, _("Created new report at '%s'.\n"),
182 user_relative_path);
184 free(prefixed_filename);
185 UNLEAK(buffer);
186 UNLEAK(report_path);
187 return !!launch_editor(report_path.buf, NULL, NULL);