diff --git a/.gitignore b/.gitignore index aebe7c0908f168..bd2f49b99620f2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ /git-bisect--helper /git-blame /git-branch +/git-bugreport /git-bundle /git-cat-file /git-check-attr @@ -189,7 +190,9 @@ /gitweb/gitweb.cgi /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* +/config-list.h /command-list.h +/bugreport-config-safelist.h *.tar.gz *.dsc *.deb diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 8fc4b67081a649..663e06481fc5db 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -6,9 +6,14 @@ # # Show Git link as: (
); if section is defined, else just show # the command. +# +# The annotate macro does nothing as far as rendering is +# concerned -- we just grep for it in the sources to populate +# things like the bugreport safelist. [macros] (?su)[\\]?(?Plinkgit):(?P\S*?)\[(?P.*?)\]= +(?su)[\\]?(?Pannotate):(?P\S*?)\[(?P.*?)\]= [attributes] asterisk=* @@ -28,6 +33,8 @@ ifdef::backend-docbook[] {0#} {0#{target}{0}} {0#} +[annotate-inlinemacro] +{0#} endif::backend-docbook[] ifdef::backend-docbook[] @@ -94,4 +101,6 @@ ifdef::backend-xhtml11[] git-relative-html-prefix= [linkgit-inlinemacro] {target}{0?({0})} +[annotate-inlinemacro] + endif::backend-xhtml11[] diff --git a/Documentation/asciidoctor-extensions.rb b/Documentation/asciidoctor-extensions.rb index d906a008039cf5..03c80af0e5463b 100644 --- a/Documentation/asciidoctor-extensions.rb +++ b/Documentation/asciidoctor-extensions.rb @@ -39,10 +39,17 @@ def process document, output output end end + + class AnnotateProcessor < Asciidoctor::Extensions::InlineMacroProcessor + def process(parent, target, attrs) + "" + end + end end end Asciidoctor::Extensions.register do inline_macro Git::Documentation::LinkGitProcessor, :linkgit postprocessor Git::Documentation::DocumentPostProcessor + inline_macro Git::Documentation::AnnotateProcessor, :annotate end diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt index 0006faf800ae69..fe27473e44948e 100644 --- a/Documentation/config/sendemail.txt +++ b/Documentation/config/sendemail.txt @@ -4,7 +4,7 @@ sendemail.identity:: values in the 'sendemail' section. The default identity is the value of `sendemail.identity`. -sendemail.smtpEncryption:: +sendemail.smtpEncryption annotate:bugreport[include] :: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism. @@ -15,7 +15,7 @@ sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification. -sendemail..*:: +sendemail..* annotate:bugreport[exclude] :: Identity-specific versions of the 'sendemail.*' parameters found below, taking precedence over those when this identity is selected, through either the command-line or @@ -23,41 +23,41 @@ sendemail..*:: sendemail.aliasesFile:: sendemail.aliasFileType:: -sendemail.annotate:: -sendemail.bcc:: -sendemail.cc:: -sendemail.ccCmd:: -sendemail.chainReplyTo:: -sendemail.confirm:: -sendemail.envelopeSender:: -sendemail.from:: -sendemail.multiEdit:: -sendemail.signedoffbycc:: -sendemail.smtpPass:: -sendemail.suppresscc:: -sendemail.suppressFrom:: -sendemail.to:: -sendemail.tocmd:: -sendemail.smtpDomain:: -sendemail.smtpServer:: -sendemail.smtpServerPort:: -sendemail.smtpServerOption:: -sendemail.smtpUser:: -sendemail.thread:: -sendemail.transferEncoding:: -sendemail.validate:: -sendemail.xmailer:: +sendemail.annotate annotate:bugreport[include] :: +sendemail.bcc annotate:bugreport[include] :: +sendemail.cc annotate:bugreport[include] :: +sendemail.ccCmd annotate:bugreport[include] :: +sendemail.chainReplyTo annotate:bugreport[include] :: +sendemail.confirm annotate:bugreport[include] :: +sendemail.envelopeSender annotate:bugreport[include] :: +sendemail.from annotate:bugreport[include] :: +sendemail.multiEdit annotate:bugreport[include] :: +sendemail.signedoffbycc annotate:bugreport[include] :: +sendemail.smtpPass annotate:bugreport[exclude] :: +sendemail.suppresscc annotate:bugreport[include] :: +sendemail.suppressFrom annotate:bugreport[include] :: +sendemail.to annotate:bugreport[include] :: +sendemail.tocmd annotate:bugreport[include] :: +sendemail.smtpDomain annotate:bugreport[include] :: +sendemail.smtpServer annotate:bugreport[include] :: +sendemail.smtpServerPort annotate:bugreport[include] :: +sendemail.smtpServerOption annotate:bugreport[include] :: +sendemail.smtpUser annotate:bugreport[exclude] :: +sendemail.thread annotate:bugreport[include] :: +sendemail.transferEncoding annotate:bugreport[include] :: +sendemail.validate annotate:bugreport[include] :: +sendemail.xmailer annotate:bugreport[include] :: See linkgit:git-send-email[1] for description. sendemail.signedoffcc (deprecated):: Deprecated alias for `sendemail.signedoffbycc`. -sendemail.smtpBatchSize:: +sendemail.smtpBatchSize annotate:bugreport[include] :: Number of messages to be sent per connection, after that a relogin will happen. If the value is 0 or undefined, send all messages in one connection. See also the `--batch-size` option of linkgit:git-send-email[1]. -sendemail.smtpReloginDelay:: +sendemail.smtpReloginDelay annotate:bugreport[include] :: Seconds wait before reconnecting to smtp server. See also the `--relogin-delay` option of linkgit:git-send-email[1]. diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt new file mode 100644 index 00000000000000..1f60fb9456e60a --- /dev/null +++ b/Documentation/git-bugreport.txt @@ -0,0 +1,60 @@ +git-bugreport(1) +================ + +NAME +---- +git-bugreport - Collect information for user to file a bug report + +SYNOPSIS +-------- +[verse] +'git bugreport' [(-o | --output-directory) ] [(-s | --suffix) ] + +DESCRIPTION +----------- +Captures information about the user's machine, Git client, and repository state, +as well as a form requesting information about the behavior the user observed, +into a single text file which the user can then share, for example to the Git +mailing list, in order to report an observed bug. + +The following information is requested from the user: + + - Reproduction steps + - Expected behavior + - Actual behavior + +The following information is captured automatically: + + - 'git version --build-options' + - uname sysname, release, version, and machine strings + - Compiler-specific info string + - 'git remote-https --build-info' + - $SHELL + - Selected config values + - A list of enabled hooks + - The number of loose objects in the repository + - The number of packs and packed objects in the repository + - A list of the contents of .git/objects/info (or equivalent) + - The number of valid and invalid alternates + +This tool is invoked via the typical Git setup process, which means that in some +cases, it might not be able to launch - for example, if a relevant config file +is unreadable. In this kind of scenario, it may be helpful to manually gather +the kind of information listed above when manually asking for help. + +OPTIONS +------- +-o :: +--output-directory :: + Place the resulting bug report file in `` instead of the root of + the Git repository. + +-s :: +--suffix :: + Specify an alternate suffix for the bugreport name, to create a file + named 'git-bugreport-'. This should take the form of a + link:strftime[3] format string; the current local time will be used. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index 6134104ae65b71..bf4f3ede2aae14 100644 --- a/Makefile +++ b/Makefile @@ -681,6 +681,7 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) +PROGRAM_OBJS += bugreport.o PROGRAM_OBJS += credential-store.o PROGRAM_OBJS += daemon.o PROGRAM_OBJS += fast-import.o @@ -815,7 +816,9 @@ LIB_FILE = libgit.a XDIFF_LIB = xdiff/lib.a VCSSVN_LIB = vcs-svn/lib.a +GENERATED_H += config-list.h GENERATED_H += command-list.h +GENERATED_H += bugreport-config-safelist.h LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \ $(FIND) . \ @@ -2132,7 +2135,9 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) help.sp help.s help.o: command-list.h -builtin/help.sp builtin/help.s builtin/help.o: command-list.h GIT-PREFIX +bugreport.sp bugreport.s bugreport.o: bugreport-config-safelist.h + +builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ @@ -2152,6 +2157,12 @@ $(BUILT_INS): git$X ln -s $< $@ 2>/dev/null || \ cp $< $@ +config-list.h: generate-configlist.sh + +config-list.h: + $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \ + >$@+ && mv $@+ $@ + command-list.h: generate-cmdlist.sh command-list.txt command-list.h: $(wildcard Documentation/git*.txt) Documentation/*config.txt Documentation/config/*.txt @@ -2159,6 +2170,12 @@ command-list.h: $(wildcard Documentation/git*.txt) Documentation/*config.txt Doc $(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \ command-list.txt >$@+ && mv $@+ $@ +bugreport-config-safelist.h: generate-bugreport-config-safelist.sh + +bugreport-config-safelist.h: Documentation/config/*.txt + $(QUIET_GEN)$(SHELL_PATH) ./generate-bugreport-config-safelist.sh \ + >$@+ && mv $@+ $@ + SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\ $(gitwebdir_SQ):$(PERL_PATH_SQ):$(SANE_TEXT_GREP):$(PAGER_ENV):\ @@ -2454,6 +2471,10 @@ endif git-%$X: %.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) +git-bugreport$X: bugreport.o GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) + git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(IMAP_SEND_LDFLAGS) $(LIBS) @@ -2785,7 +2806,7 @@ $(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE .PHONY: sparse $(SP_OBJ) sparse: $(SP_OBJ) -EXCEPT_HDRS := command-list.h unicode-width.h compat/% xdiff/% +EXCEPT_HDRS := command-list.h config-list.h unicode-width.h compat/% xdiff/% ifndef GCRYPT_SHA256 EXCEPT_HDRS += sha256/gcrypt.h endif @@ -2807,7 +2828,7 @@ hdr-check: $(HCO) style: git clang-format --style file --diff --extensions c,h -check: command-list.h +check: config-list.h command-list.h @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \ diff --git a/bugreport.c b/bugreport.c new file mode 100644 index 00000000000000..5687a952a91ad2 --- /dev/null +++ b/bugreport.c @@ -0,0 +1,416 @@ +#include "cache.h" +#include "parse-options.h" +#include "stdio.h" +#include "strbuf.h" +#include "time.h" +#include "help.h" +#include "compat/compiler.h" +#include "run-command.h" +#include "config.h" +#include "bugreport-config-safelist.h" +#include "khash.h" +#include "run-command.h" +#include "object-store.h" + +static void get_git_remote_https_version_info(struct strbuf *version_info) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "remote-https"); + argv_array_push(&cp.args, "--build-info"); + if (capture_command(&cp, version_info, 0)) + strbuf_addstr(version_info, "'git-remote-https --build-info' not supported\n"); +} + +static void get_system_info(struct strbuf *sys_info) +{ + struct utsname uname_info; + char *shell = NULL; + + /* get git version from native cmd */ + strbuf_addstr(sys_info, "git version:\n"); + get_version_info(sys_info, 1); + strbuf_complete_line(sys_info); + + /* system call for other version info */ + strbuf_addstr(sys_info, "uname: "); + if (uname(&uname_info)) + strbuf_addf(sys_info, "uname() failed with error '%s' (%d)\n", + strerror(errno), + errno); + else + strbuf_addf(sys_info, "%s %s %s %s\n", + uname_info.sysname, + uname_info.release, + uname_info.version, + uname_info.machine); + + strbuf_addstr(sys_info, "compiler info: "); + get_compiler_info(sys_info); + strbuf_complete_line(sys_info); + + shell = getenv("SHELL"); + strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n", + shell ? shell : ""); + + strbuf_addstr(sys_info, "git-remote-https --build-info:\n"); + get_git_remote_https_version_info(sys_info); + strbuf_complete_line(sys_info); +} + +static void get_safelisted_config(struct strbuf *config_info) +{ + size_t idx; + struct string_list_item *it = NULL; + struct key_value_info *kv_info = NULL; + + for (idx = 0; idx < ARRAY_SIZE(bugreport_config_safelist); idx++) { + const struct string_list *list = + git_config_get_value_multi(bugreport_config_safelist[idx]); + + if (!list) + continue; + + strbuf_addf(config_info, "%s:\n", bugreport_config_safelist[idx]); + for_each_string_list_item(it, list) { + kv_info = it->util; + strbuf_addf(config_info, " %s (%s)\n", it->string, + kv_info ? config_scope_name(kv_info->scope) + : "source unknown"); + } + } +} + +static void get_populated_hooks(struct strbuf *hook_info, int nongit) +{ + /* + * Doesn't look like there is a list of all possible hooks; so below is + * a transcription of `git help hooks`. + */ + const char *hooks = "applypatch-msg," + "pre-applypatch," + "post-applypatch," + "pre-commit," + "pre-merge-commit," + "prepare-commit-msg," + "commit-msg," + "post-commit," + "pre-rebase," + "post-checkout," + "post-merge," + "pre-push," + "pre-receive," + "update," + "post-receive," + "post-update," + "push-to-checkout," + "pre-auto-gc," + "post-rewrite," + "sendemail-validate," + "fsmonitor-watchman," + "p4-pre-submit," + "post-index-changex"; + struct string_list hooks_list = STRING_LIST_INIT_DUP; + struct string_list_item *iter = NULL; + + + if (nongit) { + strbuf_addstr(hook_info, + "not run from a git repository - no hooks to show\n"); + return; + } + + string_list_split(&hooks_list, hooks, ',', -1); + + for_each_string_list_item(iter, &hooks_list) { + if (find_hook(iter->string)) { + strbuf_addstr(hook_info, iter->string); + strbuf_complete_line(hook_info); + } + } +} + +static int loose_object_cb(const struct object_id *oid, const char *path, + void *data) { + int *loose_object_count = data; + + if (loose_object_count) { + (*loose_object_count)++; + return 0; + } + + return 1; +} + +static void get_loose_object_summary(struct strbuf *obj_info, int nongit) { + + int local_loose_object_count = 0, total_loose_object_count = 0; + int local_count_questionable = 0, total_count_questionable = 0; + + if (nongit) { + strbuf_addstr(obj_info, + "not run from a git repository - no objects to show\n"); + return; + } + + local_count_questionable = for_each_loose_object( + loose_object_cb, + &local_loose_object_count, + FOR_EACH_OBJECT_LOCAL_ONLY); + + total_count_questionable = for_each_loose_object( + loose_object_cb, + &total_loose_object_count, + 0); + + strbuf_addf(obj_info, "%d local loose objects%s\n", + local_loose_object_count, + local_count_questionable ? " (problem during count)" : ""); + + strbuf_addf(obj_info, "%d alternate loose objects%s\n", + total_loose_object_count - local_loose_object_count, + (local_count_questionable || total_count_questionable) + ? " (problem during count)" + : ""); + + strbuf_addf(obj_info, "%d total loose objects%s\n", + total_loose_object_count, + total_count_questionable ? " (problem during count)" : ""); +} + +static void get_packed_object_summary(struct strbuf *obj_info, int nongit) +{ + struct packed_git *pack = NULL; + int pack_count = 0; + int object_count = 0; + + if (nongit) { + strbuf_addstr(obj_info, + "not run from a git repository - no objects to show\n"); + return; + } + + for_each_pack(the_repository, pack) { + pack_count++; + /* + * To accurately count how many objects are packed, look inside + * the packfile's index. + */ + open_pack_index(pack); + object_count += pack->num_objects; + } + + strbuf_addf(obj_info, "%d total packs (%d objects)\n", pack_count, + object_count); + +} + +static void list_contents_of_dir_recursively(struct strbuf *contents, + struct strbuf *dirpath) +{ + struct dirent *d; + DIR *dir; + size_t path_len; + + dir = opendir(dirpath->buf); + if (!dir) + return; + + strbuf_complete(dirpath, '/'); + path_len = dirpath->len; + + while ((d = readdir(dir))) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + strbuf_addbuf(contents, dirpath); + strbuf_addstr(contents, d->d_name); + strbuf_complete_line(contents); + + if (d->d_type == DT_DIR) { + strbuf_addstr(dirpath, d->d_name); + list_contents_of_dir_recursively(contents, dirpath); + } + strbuf_setlen(dirpath, path_len); + } + + closedir(dir); +} + +static void get_object_info_summary(struct strbuf *obj_info, int nongit) +{ + struct strbuf dirpath = STRBUF_INIT; + + if (nongit) { + strbuf_addstr(obj_info, + "not run from a git repository - object info unavailable\n"); + return; + } + + strbuf_addstr(&dirpath, get_object_directory()); + strbuf_complete(&dirpath, '/'); + strbuf_addstr(&dirpath, "info/"); + + list_contents_of_dir_recursively(obj_info, &dirpath); + + strbuf_release(&dirpath); +} + +static void get_alternates_summary(struct strbuf *alternates_info, int nongit) +{ + struct strbuf alternates_path = STRBUF_INIT; + struct strbuf alternate = STRBUF_INIT; + FILE *file; + size_t exists = 0, broken = 0; + + if (nongit) { + strbuf_addstr(alternates_info, + "not run from a git repository - alternates unavailable\n"); + return; + } + + strbuf_addstr(&alternates_path, get_object_directory()); + strbuf_complete(&alternates_path, '/'); + strbuf_addstr(&alternates_path, "info/alternates"); + + file = fopen(alternates_path.buf, "r"); + if (!file) { + strbuf_addstr(alternates_info, "No alternates file found.\n"); + strbuf_release(&alternates_path); + return; + } + + while (strbuf_getline(&alternate, file) != EOF) { + if (!access(alternate.buf, F_OK)) + exists++; + else + broken++; + } + + strbuf_addf(alternates_info, + "%zd alternates found (%zd working, %zd broken)\n", + exists + broken, + exists, + broken); + + fclose(file); + strbuf_release(&alternate); + strbuf_release(&alternates_path); +} + +static const char * const bugreport_usage[] = { + N_("git bugreport [-o|--output-directory ] [-s|--suffix ]"), + NULL +}; + +static int get_bug_template(struct strbuf *template) +{ + const char template_text[] = N_( +"Thank you for filling out a Git bug report!\n" +"Please answer the following questions to help us understand your issue.\n" +"\n" +"What did you do before the bug happened? (Steps to reproduce your issue)\n" +"\n" +"What did you expect to happen? (Expected behavior)\n" +"\n" +"What happened instead? (Actual behavior)\n" +"\n" +"What's different between what you expected and what actually happened?\n" +"\n" +"Anything else you want to add:\n" +"\n" +"Please review the rest of the bug report below.\n" +"You can delete any lines you don't wish to share.\n"); + + strbuf_addstr(template, template_text); + return 0; +} + +static void get_header(struct strbuf *buf, const char *title) +{ + strbuf_addf(buf, "\n\n[%s]\n", title); +} + +int cmd_main(int argc, const char **argv) +{ + struct strbuf buffer = STRBUF_INIT; + struct strbuf report_path = STRBUF_INIT; + int report = -1; + time_t now = time(NULL); + char *option_output = NULL; + char *option_suffix = "%F-%H%M"; + int nongit_ok = 0; + + const struct option bugreport_options[] = { + OPT_STRING('o', "output-directory", &option_output, N_("path"), + N_("specify a destination for the bugreport file")), + OPT_STRING('s', "suffix", &option_suffix, N_("format"), + N_("specify a strftime format suffix for the filename")), + OPT_END() + }; + + /* Prerequisite for hooks and config checks */ + setup_git_directory_gently(&nongit_ok); + + argc = parse_options(argc, argv, NULL, bugreport_options, + bugreport_usage, 0); + + if (option_output) { + strbuf_addstr(&report_path, option_output); + strbuf_complete(&report_path, '/'); + } + + strbuf_addstr(&report_path, "git-bugreport-"); + strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0); + strbuf_addstr(&report_path, ".txt"); + + switch (safe_create_leading_directories(report_path.buf)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die(_("could not create leading directories for '%s'"), + report_path.buf); + } + + get_bug_template(&buffer); + + get_header(&buffer, "System Info"); + get_system_info(&buffer); + + get_header(&buffer, "Safelisted Config Info"); + get_safelisted_config(&buffer); + + get_header(&buffer, "Enabled Hooks"); + get_populated_hooks(&buffer, nongit_ok); + + get_header(&buffer, "Loose Object Counts"); + get_loose_object_summary(&buffer, nongit_ok); + + get_header(&buffer, "Packed Object Summary"); + get_packed_object_summary(&buffer, nongit_ok); + + get_header(&buffer, "Object Info Summary"); + get_object_info_summary(&buffer, nongit_ok); + + get_header(&buffer, "Alternates"); + get_alternates_summary(&buffer, nongit_ok); + + /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */ + report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); + + if (report < 0) { + UNLEAK(report_path); + die(_("couldn't create a new file at '%s'"), report_path.buf); + } + + strbuf_write_fd(&buffer, report); + close(report); + + fprintf(stderr, _("Created new report at '%s'.\n"), report_path.buf); + + UNLEAK(buffer); + UNLEAK(report_path); + return !!launch_editor(report_path.buf, NULL, NULL); +} diff --git a/builtin/help.c b/builtin/help.c index e5590d7787c50a..1c5f2b9255fc2e 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -8,6 +8,7 @@ #include "parse-options.h" #include "run-command.h" #include "column.h" +#include "config-list.h" #include "help.h" #include "alias.h" @@ -62,6 +63,91 @@ static const char * const builtin_help_usage[] = { NULL }; +struct slot_expansion { + const char *prefix; + const char *placeholder; + void (*fn)(struct string_list *list, const char *prefix); + int found; +}; + +static void list_config_help(int for_human) +{ + struct slot_expansion slot_expansions[] = { + { "advice", "*", list_config_advices }, + { "color.branch", "", list_config_color_branch_slots }, + { "color.decorate", "", list_config_color_decorate_slots }, + { "color.diff", "", list_config_color_diff_slots }, + { "color.grep", "", list_config_color_grep_slots }, + { "color.interactive", "", list_config_color_interactive_slots }, + { "color.remote", "", list_config_color_sideband_slots }, + { "color.status", "", list_config_color_status_slots }, + { "fsck", "", list_config_fsck_msg_ids }, + { "receive.fsck", "", list_config_fsck_msg_ids }, + { NULL, NULL, NULL } + }; + const char **p; + struct slot_expansion *e; + struct string_list keys = STRING_LIST_INIT_DUP; + int i; + + for (p = config_name_list; *p; p++) { + const char *var = *p; + struct strbuf sb = STRBUF_INIT; + + for (e = slot_expansions; e->prefix; e++) { + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); + if (!strcasecmp(var, sb.buf)) { + e->fn(&keys, e->prefix); + e->found++; + break; + } + } + strbuf_release(&sb); + if (!e->prefix) + string_list_append(&keys, var); + } + + for (e = slot_expansions; e->prefix; e++) + if (!e->found) + BUG("slot_expansion %s.%s is not used", + e->prefix, e->placeholder); + + string_list_sort(&keys); + for (i = 0; i < keys.nr; i++) { + const char *var = keys.items[i].string; + const char *wildcard, *tag, *cut; + + if (for_human) { + puts(var); + continue; + } + + wildcard = strchr(var, '*'); + tag = strchr(var, '<'); + + if (!wildcard && !tag) { + puts(var); + continue; + } + + if (wildcard && !tag) + cut = wildcard; + else if (!wildcard && tag) + cut = tag; + else + cut = wildcard < tag ? wildcard : tag; + + /* + * We may produce duplicates, but that's up to + * git-completion.bash to handle + */ + printf("%.*s\n", (int)(cut - var), var); + } + string_list_clear(&keys, 0); +} + static enum help_format parse_help_format(const char *format) { if (!strcmp(format, "man")) diff --git a/command-list.txt b/command-list.txt index 20878946558847..185e5e3f05fbc4 100644 --- a/command-list.txt +++ b/command-list.txt @@ -54,6 +54,7 @@ git-archive mainporcelain git-bisect mainporcelain info git-blame ancillaryinterrogators complete git-branch mainporcelain history +git-bugreport ancillaryinterrogators git-bundle mainporcelain git-cat-file plumbinginterrogators git-check-attr purehelpers diff --git a/compat/compiler.h b/compat/compiler.h new file mode 100644 index 00000000000000..309cd3830bcce0 --- /dev/null +++ b/compat/compiler.h @@ -0,0 +1,22 @@ +#ifndef COMPILER_H +#define COMPILER_H + +#include "git-compat-util.h" +#include "strbuf.h" + +#ifdef __GLIBC__ +#include +#endif + +static inline void get_compiler_info(struct strbuf *info) +{ +#ifdef __GLIBC__ + strbuf_addf(info, "glibc: %s\n", gnu_get_libc_version()); +#endif + +#ifdef __GNUC__ + strbuf_addf(info, "gnuc: %d.%d\n", __GNUC__, __GNUC_MINOR__); +#endif +} + +#endif /* COMPILER_H */ diff --git a/generate-bugreport-config-safelist.sh b/generate-bugreport-config-safelist.sh new file mode 100755 index 00000000000000..57ebf3bb57ab94 --- /dev/null +++ b/generate-bugreport-config-safelist.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +cat <<"EOF" +/* Automatically generated by bugreport-generate-config-safelist.sh */ + + +static const char *bugreport_config_safelist[] = { +EOF + +# cat all regular files in Documentation/config +find Documentation/config -type f -exec cat {} \; | +# print the command name which matches the annotate-bugreport macro +sed -n 's/^\([^ ]*\) *annotate:bugreport\[include\].* ::$/ "\1",/p' | + sort + +cat <", list_config_color_branch_slots }, - { "color.decorate", "", list_config_color_decorate_slots }, - { "color.diff", "", list_config_color_diff_slots }, - { "color.grep", "", list_config_color_grep_slots }, - { "color.interactive", "", list_config_color_interactive_slots }, - { "color.remote", "", list_config_color_sideband_slots }, - { "color.status", "", list_config_color_status_slots }, - { "fsck", "", list_config_fsck_msg_ids }, - { "receive.fsck", "", list_config_fsck_msg_ids }, - { NULL, NULL, NULL } - }; - const char **p; - struct slot_expansion *e; - struct string_list keys = STRING_LIST_INIT_DUP; - int i; - - for (p = config_name_list; *p; p++) { - const char *var = *p; - struct strbuf sb = STRBUF_INIT; - - for (e = slot_expansions; e->prefix; e++) { - - strbuf_reset(&sb); - strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); - if (!strcasecmp(var, sb.buf)) { - e->fn(&keys, e->prefix); - e->found++; - break; - } - } - strbuf_release(&sb); - if (!e->prefix) - string_list_append(&keys, var); - } - - for (e = slot_expansions; e->prefix; e++) - if (!e->found) - BUG("slot_expansion %s.%s is not used", - e->prefix, e->placeholder); - - string_list_sort(&keys); - for (i = 0; i < keys.nr; i++) { - const char *var = keys.items[i].string; - const char *wildcard, *tag, *cut; - - if (for_human) { - puts(var); - continue; - } - - wildcard = strchr(var, '*'); - tag = strchr(var, '<'); - - if (!wildcard && !tag) { - puts(var); - continue; - } - - if (wildcard && !tag) - cut = wildcard; - else if (!wildcard && tag) - cut = tag; - else - cut = wildcard < tag ? wildcard : tag; - - /* - * We may produce duplicates, but that's up to - * git-completion.bash to handle - */ - printf("%.*s\n", (int)(cut - var), var); - } - string_list_clear(&keys, 0); -} - static int get_alias(const char *var, const char *value, void *data) { struct string_list *list = data; @@ -707,8 +622,33 @@ const char *help_unknown_cmd(const char *cmd) exit(1); } +void get_version_info(struct strbuf *buf, int show_build_options) +{ + /* + * The format of this string should be kept stable for compatibility + * with external projects that rely on the output of "git version". + * + * Always show the version, even if other options are given. + */ + strbuf_addf(buf, "git version %s\n", git_version_string); + + if (show_build_options) { + strbuf_addf(buf, "cpu: %s\n", GIT_HOST_CPU); + if (git_built_from_commit_string[0]) + strbuf_addf(buf, "built from commit: %s\n", + git_built_from_commit_string); + else + strbuf_addstr(buf, "no commit associated with this build\n"); + strbuf_addf(buf, "sizeof-long: %d\n", (int)sizeof(long)); + strbuf_addf(buf, "sizeof-size_t: %d\n", (int)sizeof(size_t)); + strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH); + /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ + } +} + int cmd_version(int argc, const char **argv, const char *prefix) { + struct strbuf buf = STRBUF_INIT; int build_options = 0; const char * const usage[] = { N_("git version []"), @@ -722,25 +662,11 @@ int cmd_version(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, usage, 0); - /* - * The format of this string should be kept stable for compatibility - * with external projects that rely on the output of "git version". - * - * Always show the version, even if other options are given. - */ - printf("git version %s\n", git_version_string); + get_version_info(&buf, build_options); + printf("%s", buf.buf); + + strbuf_release(&buf); - if (build_options) { - printf("cpu: %s\n", GIT_HOST_CPU); - if (git_built_from_commit_string[0]) - printf("built from commit: %s\n", - git_built_from_commit_string); - else - printf("no commit associated with this build\n"); - printf("sizeof-long: %d\n", (int)sizeof(long)); - printf("sizeof-size_t: %d\n", (int)sizeof(size_t)); - /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ - } return 0; } diff --git a/help.h b/help.h index 7a455beeb725e2..500521b9081c37 100644 --- a/help.h +++ b/help.h @@ -22,7 +22,6 @@ static inline void mput_char(char c, unsigned int num) void list_common_cmds_help(void); void list_all_cmds_help(void); void list_common_guides_help(void); -void list_config_help(int for_human); void list_all_main_cmds(struct string_list *list); void list_all_other_cmds(struct string_list *list); @@ -38,6 +37,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len); void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); int is_in_cmdlist(struct cmdnames *cmds, const char *name); void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds); +void get_version_info(struct strbuf *buf, int show_build_options); /* * call this to die(), when it is suspected that the user mistyped a diff --git a/object-store.h b/object-store.h index 5b047637e3e02e..f881a1756ed0da 100644 --- a/object-store.h +++ b/object-store.h @@ -7,6 +7,7 @@ #include "sha1-array.h" #include "strbuf.h" #include "thread-utils.h" +#include "packfile.h" struct object_directory { struct object_directory *next; @@ -447,4 +448,9 @@ int for_each_object_in_pack(struct packed_git *p, int for_each_packed_object(each_packed_object_fn, void *, enum for_each_object_flags flags); +#define for_each_pack(repo, pack) \ + for (pack = get_all_packs(repo);\ + pack; \ + pack = pack->next) + #endif /* OBJECT_STORE_H */ diff --git a/packfile.c b/packfile.c index 99dd1a7d094fe8..95afcc1187bc81 100644 --- a/packfile.c +++ b/packfile.c @@ -2095,8 +2095,7 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, int r = 0; int pack_errors = 0; - prepare_packed_git(the_repository); - for (p = get_all_packs(the_repository); p; p = p->next) { + for_each_pack(the_repository, p) { if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) continue; if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) && diff --git a/remote-curl.c b/remote-curl.c index 8eb96152f51b64..73e52175c05c3e 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -17,6 +17,7 @@ #include "protocol.h" #include "quote.h" #include "transport.h" +#include "version.h" static struct remote *remote; /* always ends with a trailing slash */ @@ -1375,6 +1376,13 @@ int cmd_main(int argc, const char **argv) string_list_init(&options.deepen_not, 1); string_list_init(&options.push_options, 1); + if (!strcmp("--build-info", argv[1])) { + printf("git-http-fetch version: %s\n", git_version_string); + printf("built from commit: %s\n", git_built_from_commit_string); + printf("curl version: %s\n", curl_version()); + return 0; + } + /* * Just report "remote-curl" here (folding all the various aliases * ("git-remote-http", "git-remote-https", and etc.) here since they diff --git a/strbuf.c b/strbuf.c index f19da55b0783dc..f1d66c7848e4dc 100644 --- a/strbuf.c +++ b/strbuf.c @@ -539,6 +539,10 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f) return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0; } +ssize_t strbuf_write_fd(struct strbuf *sb, int fd) +{ + return sb->len ? write(fd, sb->buf, sb->len) : 0; +} #define STRBUF_MAXLINK (2*PATH_MAX) diff --git a/strbuf.h b/strbuf.h index aae7ac3a82da1a..bbf6204de726cb 100644 --- a/strbuf.h +++ b/strbuf.h @@ -462,6 +462,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); * NUL bytes. */ ssize_t strbuf_write(struct strbuf *sb, FILE *stream); +ssize_t strbuf_write_fd(struct strbuf *sb, int fd); /** * Read a line from a FILE *, overwriting the existing contents of diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh new file mode 100755 index 00000000000000..65f664fdac25a2 --- /dev/null +++ b/t/t0091-bugreport.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='git bugreport' + +. ./test-lib.sh + +# Headers "[System Info]" will be followed by a non-empty line if we put some +# information there; we can make sure all our headers were followed by some +# information to check if the command was successful. +HEADER_PATTERN="^\[.*\]$" + +check_all_headers_populated () { + while read -r line + do + if test "$(grep "$HEADER_PATTERN" "$line")" + then + echo "$line" + read -r nextline + if test -z "$nextline"; then + return 1; + fi + fi + done +} + +test_expect_success 'creates a report with content in the right places' ' + git bugreport -s check-headers && + check_all_headers_populated >git-bugreport-duplicate.txt && + test_must_fail git bugreport --suffix duplicate && + test_when_finished rm git-bugreport-duplicate.txt +' + +test_expect_success '--output-directory puts the report in the provided dir' ' + git bugreport -o foo/ && + test_path_is_file foo/git-bugreport-* && + test_when_finished rm -fr foo/ +' + +test_expect_success 'incorrect arguments abort with usage' ' + test_must_fail git bugreport --false 2>output && + test_i18ngrep usage output && + test_path_is_missing git-bugreport-* +' + +test_expect_success 'runs outside of a git dir' ' + nongit git bugreport && + test_when_finished rm non-repo/git-bugreport-* +' + +test_expect_success 'can create leading directories outside of a git dir' ' + nongit git bugreport -o foo/bar/baz && + test_when_finished rm -fr foo/bar/baz +' + + +test_done