]> git.ipfire.org Git - thirdparty/git.git/blob - builtin/bugreport.c
Merge branch 'jk/clone-allow-bare-and-o-together'
[thirdparty/git.git] / builtin / bugreport.c
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"
9
10
11 static void get_system_info(struct strbuf *sys_info)
12 {
13 struct utsname uname_info;
14 char *shell = NULL;
15
16 /* get git version from native cmd */
17 strbuf_addstr(sys_info, _("git version:\n"));
18 get_version_info(sys_info, 1);
19
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);
32
33 strbuf_addstr(sys_info, _("compiler info: "));
34 get_compiler_info(sys_info);
35
36 strbuf_addstr(sys_info, _("libc info: "));
37 get_libc_info(sys_info);
38
39 shell = getenv("SHELL");
40 strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n",
41 shell ? shell : "<unset>");
42 }
43
44 static void get_populated_hooks(struct strbuf *hook_info, int nongit)
45 {
46 const char **p;
47
48 if (nongit) {
49 strbuf_addstr(hook_info,
50 _("not run from a git repository - no hooks to show\n"));
51 return;
52 }
53
54 for (p = hook_name_list; *p; p++) {
55 const char *hook = *p;
56
57 if (hook_exists(hook))
58 strbuf_addf(hook_info, "%s\n", hook);
59 }
60 }
61
62 static const char * const bugreport_usage[] = {
63 N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>] [--diagnose[=<mode>]"),
64 NULL
65 };
66
67 static int get_bug_template(struct strbuf *template)
68 {
69 const char template_text[] = N_(
70 "Thank you for filling out a Git bug report!\n"
71 "Please answer the following questions to help us understand your issue.\n"
72 "\n"
73 "What did you do before the bug happened? (Steps to reproduce your issue)\n"
74 "\n"
75 "What did you expect to happen? (Expected behavior)\n"
76 "\n"
77 "What happened instead? (Actual behavior)\n"
78 "\n"
79 "What's different between what you expected and what actually happened?\n"
80 "\n"
81 "Anything else you want to add:\n"
82 "\n"
83 "Please review the rest of the bug report below.\n"
84 "You can delete any lines you don't wish to share.\n");
85
86 strbuf_addstr(template, _(template_text));
87 return 0;
88 }
89
90 static void get_header(struct strbuf *buf, const char *title)
91 {
92 strbuf_addf(buf, "\n\n[%s]\n", title);
93 }
94
95 int cmd_bugreport(int argc, const char **argv, const char *prefix)
96 {
97 struct strbuf buffer = STRBUF_INIT;
98 struct strbuf report_path = STRBUF_INIT;
99 int report = -1;
100 time_t now = time(NULL);
101 struct tm tm;
102 enum diagnose_mode diagnose = DIAGNOSE_NONE;
103 char *option_output = NULL;
104 char *option_suffix = "%Y-%m-%d-%H%M";
105 const char *user_relative_path = NULL;
106 char *prefixed_filename;
107 size_t output_path_len;
108
109 const struct option bugreport_options[] = {
110 OPT_CALLBACK_F(0, "diagnose", &diagnose, N_("mode"),
111 N_("create an additional zip archive of detailed diagnostics (default 'stats')"),
112 PARSE_OPT_OPTARG, option_parse_diagnose),
113 OPT_STRING('o', "output-directory", &option_output, N_("path"),
114 N_("specify a destination for the bugreport file(s)")),
115 OPT_STRING('s', "suffix", &option_suffix, N_("format"),
116 N_("specify a strftime format suffix for the filename(s)")),
117 OPT_END()
118 };
119
120 argc = parse_options(argc, argv, prefix, bugreport_options,
121 bugreport_usage, 0);
122
123 /* Prepare the path to put the result */
124 prefixed_filename = prefix_filename(prefix,
125 option_output ? option_output : "");
126 strbuf_addstr(&report_path, prefixed_filename);
127 strbuf_complete(&report_path, '/');
128 output_path_len = report_path.len;
129
130 strbuf_addstr(&report_path, "git-bugreport-");
131 strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0);
132 strbuf_addstr(&report_path, ".txt");
133
134 switch (safe_create_leading_directories(report_path.buf)) {
135 case SCLD_OK:
136 case SCLD_EXISTS:
137 break;
138 default:
139 die(_("could not create leading directories for '%s'"),
140 report_path.buf);
141 }
142
143 /* Prepare diagnostics, if requested */
144 if (diagnose != DIAGNOSE_NONE) {
145 struct strbuf zip_path = STRBUF_INIT;
146 strbuf_add(&zip_path, report_path.buf, output_path_len);
147 strbuf_addstr(&zip_path, "git-diagnostics-");
148 strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0);
149 strbuf_addstr(&zip_path, ".zip");
150
151 if (create_diagnostics_archive(&zip_path, diagnose))
152 die_errno(_("unable to create diagnostics archive %s"), zip_path.buf);
153
154 strbuf_release(&zip_path);
155 }
156
157 /* Prepare the report contents */
158 get_bug_template(&buffer);
159
160 get_header(&buffer, _("System Info"));
161 get_system_info(&buffer);
162
163 get_header(&buffer, _("Enabled Hooks"));
164 get_populated_hooks(&buffer, !startup_info->have_repository);
165
166 /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
167 report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
168
169 if (write_in_full(report, buffer.buf, buffer.len) < 0)
170 die_errno(_("unable to write to %s"), report_path.buf);
171
172 close(report);
173
174 /*
175 * We want to print the path relative to the user, but we still need the
176 * path relative to us to give to the editor.
177 */
178 if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
179 user_relative_path = report_path.buf;
180 fprintf(stderr, _("Created new report at '%s'.\n"),
181 user_relative_path);
182
183 free(prefixed_filename);
184 UNLEAK(buffer);
185 UNLEAK(report_path);
186 return !!launch_editor(report_path.buf, NULL, NULL);
187 }