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