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