diff --git a/.editorconfig b/.editorconfig index 6e4eaa8e955341..82e121a41754b5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,11 @@ insert_final_newline = true # The settings for C (*.c and *.h) files are mirrored in .clang-format. Keep # them in sync. -[{*.{c,h,sh,bash,perl,pl,pm,txt,adoc},config.mak.*,Makefile,templates/hooks/*.sample}] +[{*.{c,h,sh,bash,perl,pl,pm,txt,adoc},config.mak.*,Makefile}] +indent_style = tab +tab_width = 8 + +[templates/hooks/*.sample] indent_style = tab tab_width = 8 diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 359f5fb74e6991..d570184ec84998 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -56,6 +56,18 @@ feedback before sending a new version, rather than sending an updated series immediately after receiving a review. This helps collect broader input and avoids unnecessary churn from many rapid iterations. +. These early update iterations are expected to be full replacements, + not incremental updates on top of what you posted already. If you + are correcting mistakes you made in the previous iteration that a + reviewer noticed and pointed out in their review, you _fix_ that + mistake by rewriting your history (e.g., by using "git rebase -i") + to pretend that you never made the mistake in the first place. In + other words, this is a chance to pretend to be a perfect developer, + and you are expected to take advantage of that. In the larger + picture, nobody is interested in your earlier mistakes. Just + present a logical progression made by a perfect developer who makes + no mistakes while working on the topic. + . Polish, refine, and re-send your patches to the list and to the people who spent their time to improve your patch. Go back to step (2). diff --git a/Documentation/config.adoc b/Documentation/config.adoc index 62eebe7c54501c..dcea3c0c15e2a9 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -523,6 +523,8 @@ include::config/sequencer.adoc[] include::config/showbranch.adoc[] +include::config/sideband.adoc[] + include::config/sparse.adoc[] include::config/splitindex.adoc[] diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc index ab0710e86a3e2c..ea5ec5df7a2aee 100644 --- a/Documentation/config/format.adoc +++ b/Documentation/config/format.adoc @@ -101,6 +101,11 @@ format.coverLetter:: generate a cover-letter only when there's more than one patch. Default is false. +format.commitListFormat:: + When the `--cover-letter-format` option is not given, `format-patch` + uses the value of this variable to decide how to format the title of + each commit. Default to `shortlog`. + format.outputDirectory:: Set a custom directory to store the resulting files instead of the current working directory. All directory components will be created. diff --git a/Documentation/config/sideband.adoc b/Documentation/config/sideband.adoc new file mode 100644 index 00000000000000..96fade7f5fee39 --- /dev/null +++ b/Documentation/config/sideband.adoc @@ -0,0 +1,27 @@ +sideband.allowControlCharacters:: + By default, control characters that are delivered via the sideband + are masked, except ANSI color sequences. This prevents potentially + unwanted ANSI escape sequences from being sent to the terminal. Use + this config setting to override this behavior (the value can be + a comma-separated list of the following keywords): ++ +-- + `color`:: + Allow ANSI color sequences, line feeds and horizontal tabs, + but mask all other control characters. This is the default. + `cursor:`: + Allow control sequences that move the cursor. This is + disabled by default. + `erase`:: + Allow control sequences that erase charactrs. This is + disabled by default. + `false`:: + Mask all control characters other than line feeds and + horizontal tabs. + `true`:: + Allow all control characters to be sent to the terminal. +-- + +sideband..*:: + Apply the `sideband.*` option selectively to specific URLs. The + same URL matching logic applies as for `http..*` settings. diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index 36146006fa9855..31fa4923354b38 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -24,6 +24,7 @@ SYNOPSIS [(--reroll-count|-v) ] [--to=] [--cc=] [--[no-]cover-letter] [--quiet] + [--cover-letter-format=] [--[no-]encode-email-headers] [--no-notes | --notes[=]] [--interdiff=] @@ -322,6 +323,17 @@ feeding the result to `git send-email`. containing the branch description, shortlog and the overall diffstat. You can fill in a description in the file before sending it out. +--cover-letter-format=:: + Specify the format in which to generate the commit list of the + patch series. This option is available if the user wants to use + an alternative to the default `shortlog` format. The accepted + values for format-spec are "shortlog" or a format string + prefixed with `log:`. + e.g. `log: %s (%an)` + If defined, defaults to the `format.commitListFormat` configuration + variable. + This option is relevant only if a cover letter is generated. + --encode-email-headers:: --no-encode-email-headers:: Encode email headers that have non-ASCII characters with @@ -453,6 +465,7 @@ with configuration variables. signOff = true outputDirectory = coverLetter = auto + commitListFormat = shortlog coverFromDescription = auto ------------ diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc index e177808004fa81..f6c22d1598978a 100644 --- a/Documentation/git-rebase.adoc +++ b/Documentation/git-rebase.adoc @@ -497,6 +497,13 @@ See also INCOMPATIBLE OPTIONS below. + See also INCOMPATIBLE OPTIONS below. +--trailer=:: + Append the given trailer to every rebased commit message, processed + via linkgit:git-interpret-trailers[1]. This option implies + `--force-rebase`. ++ +See also INCOMPATIBLE OPTIONS below. + -i:: --interactive:: Make a list of the commits which are about to be rebased. Let the @@ -653,6 +660,7 @@ are incompatible with the following options: * --[no-]reapply-cherry-picks when used without --keep-base * --update-refs * --root when used without --onto + * --trailer In addition, the following pairs of options are incompatible: diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 5e2968b707eacd..42262c198347e5 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -64,6 +64,7 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest reachable objects in the repository by type + The output format can be chosen through the flag `--format`. Three formats are supported: diff --git a/Documentation/git.adoc b/Documentation/git.adoc index 2b3d1d2d0c599d..8a5cdd3b3d22c5 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -235,7 +235,10 @@ GIT COMMANDS ------------ We divide Git into high level ("porcelain") commands and low level -("plumbing") commands. +("plumbing") commands. For defining command aliases, see +linkgit:git-config[1] and look for descriptions of `alias.*`. +For installing custom "git" subcommands, see the description for +the 'PATH' environment variable in this manual. High-level commands (porcelain) ------------------------------- diff --git a/Makefile b/Makefile index 58fb895f4e2f23..c619b5405b5300 100644 --- a/Makefile +++ b/Makefile @@ -1605,6 +1605,7 @@ BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS endif ifneq ($(filter leak,$(SANITIZERS)),) BASIC_CFLAGS += -O0 +NO_MMAP = CatchMapLeaks SANITIZE_LEAK = YesCompiledWithIt endif ifneq ($(filter address,$(SANITIZERS)),) diff --git a/bloom.c b/bloom.c index 77a6fddf720912..a805ac0c296b37 100644 --- a/bloom.c +++ b/bloom.c @@ -501,7 +501,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, struct hashmap_iter iter; for (i = 0; i < diff_queued_diff.nr; i++) { - const char *path = diff_queued_diff.queue[i]->two->path; + char *path = diff_queued_diff.queue[i]->two->path; /* * Add each leading directory of the changed file, i.e. for @@ -523,7 +523,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, free(e); if (!last_slash) - last_slash = (char*)path; + last_slash = path; *last_slash = '\0'; } while (*path); diff --git a/builtin/am.c b/builtin/am.c index e0c767e223dbce..9d0b51c651924a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1937,7 +1937,7 @@ static void am_run(struct am_state *state, int resume) */ if (!state->rebasing) { am_destroy(state); - run_auto_maintenance(state->quiet); + run_auto_maintenance(the_repository, state->quiet); } } diff --git a/builtin/commit.c b/builtin/commit.c index 844bdcc72803b9..02a04f68be0209 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1720,7 +1720,8 @@ int cmd_commit(int argc, OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG), + OPT_STRVEC(0, "trailer", &trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), @@ -1820,6 +1821,9 @@ int cmd_commit(int argc, argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); + if (trailer_args.nr) + trailer_config_init(); + if (verbose == -1) verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; @@ -1958,7 +1962,7 @@ int cmd_commit(int argc, git_test_write_commit_graph_or_die(the_repository->objects->sources); repo_rerere(the_repository, 0); - run_auto_maintenance(quiet); + run_auto_maintenance(the_repository, quiet); run_commit_hook(use_editor, repo_get_index_file(the_repository), NULL, "post-commit", NULL); if (amend && !no_post_rewrite) { diff --git a/builtin/fetch.c b/builtin/fetch.c index 8a36cf67b5f140..4795b2a13c30e3 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -2873,7 +2873,7 @@ int cmd_fetch(int argc, if (opt_val != 0) git_config_push_parameter("maintenance.incremental-repack.auto=-1"); } - run_auto_maintenance(verbosity < 0); + run_auto_maintenance(the_repository, verbosity < 0); } cleanup: diff --git a/builtin/gc.c b/builtin/gc.c index fb329c2cffab80..5d8d358f7a511d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1030,7 +1030,7 @@ int cmd_gc(int argc, struct child_process repack_cmd = CHILD_PROCESS_INIT; repack_cmd.git_cmd = 1; - repack_cmd.close_object_store = 1; + repack_cmd.odb_to_close = the_repository->objects; strvec_pushv(&repack_cmd.args, repack_args.v); if (run_command(&repack_cmd)) die(FAILED_RUN, repack_args.v[0]); @@ -1199,7 +1199,8 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "commit-graph", "write", "--split", "--reachable", NULL); @@ -1268,7 +1269,8 @@ static int maintenance_task_gc_background(struct maintenance_run_opts *opts, { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_push(&child.args, "gc"); if (opts->auto_flag) @@ -1484,7 +1486,8 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "multi-pack-index", "expire", NULL); if (opts->quiet) @@ -1542,7 +1545,8 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "multi-pack-index", "repack", NULL); if (opts->quiet) diff --git a/builtin/history.c b/builtin/history.c index 1cf6c668cfd814..88822a184fa5bc 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -425,7 +425,7 @@ static int cmd_history_reword(int argc, }; struct strbuf reflog_msg = STRBUF_INIT; struct commit *original, *rewritten; - struct rev_info revs; + struct rev_info revs = { 0 }; int ret; argc = parse_options(argc, argv, prefix, options, usage, 0); diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 41b0750e5af324..acaf42b2d9333b 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -93,37 +93,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg, return 0; } -static struct tempfile *trailers_tempfile; - -static FILE *create_in_place_tempfile(const char *file) -{ - struct stat st; - struct strbuf filename_template = STRBUF_INIT; - const char *tail; - FILE *outfile; - - if (stat(file, &st)) - die_errno(_("could not stat %s"), file); - if (!S_ISREG(st.st_mode)) - die(_("file %s is not a regular file"), file); - if (!(st.st_mode & S_IWUSR)) - die(_("file %s is not writable by user"), file); - - /* Create temporary file in the same directory as the original */ - tail = strrchr(file, '/'); - if (tail) - strbuf_add(&filename_template, file, tail - file + 1); - strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); - - trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode); - strbuf_release(&filename_template); - outfile = fdopen_tempfile(trailers_tempfile, "w"); - if (!outfile) - die_errno(_("could not open temporary file")); - - return outfile; -} - static void read_input_file(struct strbuf *sb, const char *file) { if (file) { @@ -140,55 +109,31 @@ static void interpret_trailers(const struct process_trailer_options *opts, struct list_head *new_trailer_head, const char *file) { - LIST_HEAD(head); - struct strbuf sb = STRBUF_INIT; - struct strbuf trailer_block_sb = STRBUF_INIT; - struct trailer_block *trailer_block; - FILE *outfile = stdout; + struct strbuf input = STRBUF_INIT; + struct strbuf out = STRBUF_INIT; + struct tempfile *tempfile = NULL; + int fd = 1; trailer_config_init(); - read_input_file(&sb, file); - - if (opts->in_place) - outfile = create_in_place_tempfile(file); - - trailer_block = parse_trailers(opts, sb.buf, &head); - - /* Print the lines before the trailer block */ - if (!opts->only_trailers) - fwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile); - - if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block)) - fprintf(outfile, "\n"); + read_input_file(&input, file); - - if (!opts->only_input) { - LIST_HEAD(config_head); - LIST_HEAD(arg_head); - parse_trailers_from_config(&config_head); - parse_trailers_from_command_line_args(&arg_head, new_trailer_head); - list_splice(&config_head, &arg_head); - process_trailers_lists(&head, &arg_head); + if (opts->in_place) { + tempfile = trailer_create_in_place_tempfile(file); + if (!tempfile) + die(NULL); + fd = tempfile->fd; } + process_trailers(opts, new_trailer_head, &input, &out); - /* Print trailer block. */ - format_trailers(opts, &head, &trailer_block_sb); - free_trailers(&head); - fwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile); - strbuf_release(&trailer_block_sb); - - /* Print the lines after the trailer block as is. */ - if (!opts->only_trailers) - fwrite(sb.buf + trailer_block_end(trailer_block), 1, - sb.len - trailer_block_end(trailer_block), outfile); - trailer_block_release(trailer_block); - + if (write_in_full(fd, out.buf, out.len) < 0) + die_errno(_("could not write to temporary file '%s'"), file); if (opts->in_place) - if (rename_tempfile(&trailers_tempfile, file)) + if (rename_tempfile(&tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); - strbuf_release(&sb); + strbuf_release(&input); + strbuf_release(&out); } int cmd_interpret_trailers(int argc, diff --git a/builtin/log.c b/builtin/log.c index 89e8b8f0115665..716ebc2701e495 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -886,6 +886,7 @@ struct format_config { char *signature; char *signature_file; enum cover_setting config_cover_letter; + char *fmt_cover_letter_commit_list; char *config_output_directory; enum cover_from_description cover_from_description_mode; int show_notes; @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg) string_list_clear(&cfg->extra_cc, 0); strbuf_release(&cfg->sprefix); free(cfg->fmt_patch_suffix); + free(cfg->fmt_cover_letter_commit_list); } static enum cover_from_description parse_cover_from_description(const char *arg) @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value, cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; return 0; } + if (!strcmp(var, "format.commitlistformat")) { + struct strbuf tmp = STRBUF_INIT; + strbuf_init(&tmp, 0); + if (value) + strbuf_addstr(&tmp, value); + else + strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s"); + + FREE_AND_NULL(cfg->fmt_cover_letter_commit_list); + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf); + strbuf_release(&tmp); + return 0; + } if (!strcmp(var, "format.outputdirectory")) { FREE_AND_NULL(cfg->config_output_directory); return git_config_string(&cfg->config_output_directory, var, value); @@ -1335,13 +1350,55 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev) } } +static void generate_shortlog_cover_letter(struct shortlog *log, + struct rev_info *rev, + struct commit **list, + int nr) +{ + shortlog_init(log); + log->wrap_lines = 1; + log->wrap = MAIL_DEFAULT_WRAP; + log->in1 = 2; + log->in2 = 4; + log->file = rev->diffopt.file; + log->groups = SHORTLOG_GROUP_AUTHOR; + shortlog_finish_setup(log); + for (int i = 0; i < nr; i++) + shortlog_add_commit(log, list[i]); + + shortlog_output(log); +} + +static void generate_commit_list_cover(FILE *cover_file, const char *format, + struct commit **list, int n) +{ + struct strbuf commit_line = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + struct rev_info rev = REV_INFO_INIT; + + strbuf_init(&commit_line, 0); + rev.total = n; + ctx.rev = &rev; + for (int i = n - 1; i >= 0; i--) { + rev.nr = n - i; + repo_format_commit_message(the_repository, list[i], format, + &commit_line, &ctx); + fprintf(cover_file, "%s\n", commit_line.buf); + strbuf_reset(&commit_line); + } + fprintf(cover_file, "\n"); + + strbuf_release(&commit_line); +} + static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct commit *origin, int nr, struct commit **list, const char *description_file, const char *branch_name, int quiet, - const struct format_config *cfg) + const struct format_config *cfg, + const char *format) { const char *from; struct shortlog log; @@ -1388,18 +1445,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, free(pp.after_subject); strbuf_release(&sb); - shortlog_init(&log); - log.wrap_lines = 1; - log.wrap = MAIL_DEFAULT_WRAP; - log.in1 = 2; - log.in2 = 4; - log.file = rev->diffopt.file; - log.groups = SHORTLOG_GROUP_AUTHOR; - shortlog_finish_setup(&log); - for (i = 0; i < nr; i++) - shortlog_add_commit(&log, list[i]); - - shortlog_output(&log); + if (skip_prefix(format, "log:", &format)) + generate_commit_list_cover(rev->diffopt.file, format, list, nr); + else if (!strcmp(format, "shortlog")) + generate_shortlog_cover_letter(&log, rev, list, nr); + else + die(_("'%s' is not a valid format string"), format); /* We can only do diffstat with a unique reference point */ if (origin) @@ -1917,6 +1968,7 @@ int cmd_format_patch(int argc, int just_numbers = 0; int ignore_if_in_upstream = 0; int cover_letter = -1; + const char *cover_letter_fmt = NULL; int boundary_count = 0; int no_binary_diff = 0; int zero_commit = 0; @@ -1963,6 +2015,8 @@ int cmd_format_patch(int argc, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, N_("generate a cover letter")), + OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"), + N_("format spec used for the commit list in the cover letter")), OPT_BOOL(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), @@ -2300,6 +2354,13 @@ int cmd_format_patch(int argc, /* nothing to do */ goto done; total = list.nr; + + if (!cover_letter_fmt) { + cover_letter_fmt = cfg.fmt_cover_letter_commit_list; + if (!cover_letter_fmt) + cover_letter_fmt = "shortlog"; + } + if (cover_letter == -1) { if (cfg.config_cover_letter == COVER_AUTO) cover_letter = (total > 1); @@ -2386,12 +2447,14 @@ int cmd_format_patch(int argc, } rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; + if (cover_letter) { if (cfg.thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, !!output_directory, origin, list.nr, list.items, - description_file, branch_name, quiet, &cfg); + description_file, branch_name, quiet, &cfg, + cover_letter_fmt); print_bases(&bases, rev.diffopt.file); print_signature(signature, rev.diffopt.file); total++; diff --git a/builtin/merge.c b/builtin/merge.c index 4e456a381c192d..2cbce56f8da9f7 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -506,7 +506,7 @@ static void finish(struct commit *head_commit, * We ignore errors in 'gc --auto', since the * user should see them. */ - run_auto_maintenance(verbosity < 0); + run_auto_maintenance(the_repository, verbosity < 0); } } if (new_head && show_diffstat) { diff --git a/builtin/pull.c b/builtin/pull.c index 6ad420ce6f9b41..7e67fdce97fd1d 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -454,7 +454,7 @@ static int run_fetch(const char *repo, const char **refspecs) } else if (*refspecs) BUG("refspecs without repo?"); cmd.git_cmd = 1; - cmd.close_object_store = 1; + cmd.odb_to_close = the_repository->objects; return run_command(&cmd); } diff --git a/builtin/rebase.c b/builtin/rebase.c index c487e1090779c2..a1c7d78196750e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -36,6 +36,7 @@ #include "reset.h" #include "trace2.h" #include "hook.h" +#include "trailer.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] " @@ -113,6 +114,7 @@ struct rebase_options { enum action action; char *reflog_action; int signoff; + struct strvec trailer_args; int allow_rerere_autoupdate; int keep_empty; int autosquash; @@ -143,6 +145,7 @@ struct rebase_options { .flags = REBASE_NO_QUIET, \ .git_am_opts = STRVEC_INIT, \ .exec = STRING_LIST_INIT_NODUP, \ + .trailer_args = STRVEC_INIT, \ .git_format_patch_opt = STRBUF_INIT, \ .fork_point = -1, \ .reapply_cherry_picks = -1, \ @@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts) free(opts->strategy); string_list_clear(&opts->strategy_opts, 0); strbuf_release(&opts->git_format_patch_opt); + strvec_clear(&opts->trailer_args); } static struct replay_opts get_replay_opts(const struct rebase_options *opts) @@ -177,6 +181,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) sequencer_init_config(&replay); replay.signoff = opts->signoff; + + for (size_t i = 0; i < opts->trailer_args.nr; i++) + strvec_push(&replay.trailer_args, opts->trailer_args.v[i]); + replay.allow_ff = !(opts->flags & REBASE_FORCE); if (opts->allow_rerere_autoupdate) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; @@ -562,7 +570,9 @@ static int finish_rebase(struct rebase_options *opts) * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + run_auto_maintenance(the_repository, + !(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; @@ -1132,6 +1142,8 @@ int cmd_rebase(int argc, .flags = PARSE_OPT_NOARG, .defval = REBASE_DIFFSTAT, }, + OPT_STRVEC(0, "trailer", &options.trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by trailer to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -1285,6 +1297,12 @@ int cmd_rebase(int argc, builtin_rebase_options, builtin_rebase_usage, 0); + if (options.trailer_args.nr) { + if (validate_trailer_args(&options.trailer_args)) + die(NULL); + options.flags |= REBASE_FORCE; + } + if (preserve_merges_selected) die(_("--preserve-merges was replaced by --rebase-merges\n" "Note: Your `pull.rebase` configuration may also be set to 'preserve',\n" @@ -1542,6 +1560,9 @@ int cmd_rebase(int argc, if (options.root && !options.onto_name) imply_merge(&options, "--root without --onto"); + if (options.trailer_args.nr) + imply_merge(&options, "--trailer"); + if (isatty(2) && options.flags & REBASE_NO_QUIET) strbuf_addstr(&options.git_format_patch_opt, " --progress"); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d6225df890d888..e34edff406959a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -2727,7 +2727,7 @@ int cmd_receive_pack(int argc, if (auto_gc) { struct child_process proc = CHILD_PROCESS_INIT; - if (prepare_auto_maintenance(1, &proc)) { + if (prepare_auto_maintenance(the_repository, 1, &proc)) { proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; diff --git a/builtin/repo.c b/builtin/repo.c index fae1141cff2e31..55f9b9095c1f92 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -1,7 +1,9 @@ #define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "commit.h" #include "environment.h" +#include "hash.h" #include "hex.h" #include "odb.h" #include "parse-options.h" @@ -14,6 +16,8 @@ #include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "tree.h" +#include "tree-walk.h" #include "utf8.h" static const char *const repo_usage[] = { @@ -230,6 +234,21 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, return print_fields(argc, argv, repo, format); } +struct object_data { + struct object_id oid; + size_t value; +}; + +struct largest_objects { + struct object_data tag_size; + struct object_data commit_size; + struct object_data tree_size; + struct object_data blob_size; + + struct object_data parent_count; + struct object_data tree_entries; +}; + struct ref_stats { size_t branches; size_t remotes; @@ -248,6 +267,7 @@ struct object_stats { struct object_values type_counts; struct object_values inflated_sizes; struct object_values disk_sizes; + struct largest_objects largest; }; struct repo_structure { @@ -257,6 +277,7 @@ struct repo_structure { struct stats_table { struct string_list rows; + struct string_list annotations; int name_col_width; int value_col_width; @@ -269,6 +290,8 @@ struct stats_table { struct stats_table_entry { char *value; const char *unit; + size_t index; + struct object_id *oid; }; static void stats_table_vaddf(struct stats_table *table, @@ -291,6 +314,12 @@ static void stats_table_vaddf(struct stats_table *table, table->name_col_width = name_width; if (!entry) return; + if (entry->oid) { + entry->index = table->annotations.nr + 1; + strbuf_addf(&buf, "[%" PRIuMAX "] %s", (uintmax_t)entry->index, + oid_to_hex(entry->oid)); + string_list_append_nodup(&table->annotations, strbuf_detach(&buf, NULL)); + } if (entry->value) { int value_width = utf8_strwidth(entry->value); if (value_width > table->value_col_width) @@ -301,6 +330,8 @@ static void stats_table_vaddf(struct stats_table *table, if (unit_width > table->unit_col_width) table->unit_col_width = unit_width; } + + strbuf_release(&buf); } static void stats_table_addf(struct stats_table *table, const char *format, ...) @@ -326,6 +357,27 @@ static void stats_table_count_addf(struct stats_table *table, size_t value, va_end(ap); } +static void stats_table_object_count_addf(struct stats_table *table, + struct object_id *oid, size_t value, + const char *format, ...) +{ + struct stats_table_entry *entry; + va_list ap; + + CALLOC_ARRAY(entry, 1); + humanise_count(value, &entry->value, &entry->unit); + + /* + * A NULL OID should not have a table annotation. + */ + if (!is_null_oid(oid)) + entry->oid = oid; + + va_start(ap, format); + stats_table_vaddf(table, entry, format, ap); + va_end(ap); +} + static void stats_table_size_addf(struct stats_table *table, size_t value, const char *format, ...) { @@ -340,6 +392,27 @@ static void stats_table_size_addf(struct stats_table *table, size_t value, va_end(ap); } +static void stats_table_object_size_addf(struct stats_table *table, + struct object_id *oid, size_t value, + const char *format, ...) +{ + struct stats_table_entry *entry; + va_list ap; + + CALLOC_ARRAY(entry, 1); + humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT); + + /* + * A NULL OID should not have a table annotation. + */ + if (!is_null_oid(oid)) + entry->oid = oid; + + va_start(ap, format); + stats_table_vaddf(table, entry, format, ap); + va_end(ap); +} + static inline size_t get_total_reference_count(struct ref_stats *stats) { return stats->branches + stats->remotes + stats->tags + stats->others; @@ -404,8 +477,41 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs")); stats_table_size_addf(table, objects->disk_sizes.tags, " * %s", _("Tags")); + + stats_table_addf(table, ""); + stats_table_addf(table, "* %s", _("Largest objects")); + stats_table_addf(table, " * %s", _("Commits")); + stats_table_object_size_addf(table, + &objects->largest.commit_size.oid, + objects->largest.commit_size.value, + " * %s", _("Maximum size")); + stats_table_object_count_addf(table, + &objects->largest.parent_count.oid, + objects->largest.parent_count.value, + " * %s", _("Maximum parents")); + stats_table_addf(table, " * %s", _("Trees")); + stats_table_object_size_addf(table, + &objects->largest.tree_size.oid, + objects->largest.tree_size.value, + " * %s", _("Maximum size")); + stats_table_object_count_addf(table, + &objects->largest.tree_entries.oid, + objects->largest.tree_entries.value, + " * %s", _("Maximum entries")); + stats_table_addf(table, " * %s", _("Blobs")); + stats_table_object_size_addf(table, + &objects->largest.blob_size.oid, + objects->largest.blob_size.value, + " * %s", _("Maximum size")); + stats_table_addf(table, " * %s", _("Tags")); + stats_table_object_size_addf(table, + &objects->largest.tag_size.oid, + objects->largest.tag_size.value, + " * %s", _("Maximum size")); } +#define INDEX_WIDTH 4 + static void stats_table_print_structure(const struct stats_table *table) { const char *name_col_title = _("Repository structure"); @@ -424,7 +530,8 @@ static void stats_table_print_structure(const struct stats_table *table) value_col_width = title_value_width - unit_col_width; strbuf_addstr(&buf, "| "); - strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, name_col_title); + strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH, + name_col_title); strbuf_addstr(&buf, " | "); strbuf_utf8_align(&buf, ALIGN_LEFT, value_col_width + unit_col_width + 1, value_col_title); @@ -432,7 +539,7 @@ static void stats_table_print_structure(const struct stats_table *table) printf("%s\n", buf.buf); printf("| "); - for (int i = 0; i < name_col_width; i++) + for (int i = 0; i < name_col_width + INDEX_WIDTH; i++) putchar('-'); printf(" | "); for (int i = 0; i < value_col_width + unit_col_width + 1; i++) @@ -453,6 +560,13 @@ static void stats_table_print_structure(const struct stats_table *table) strbuf_reset(&buf); strbuf_addstr(&buf, "| "); strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string); + + if (entry && entry->oid) + strbuf_addf(&buf, " [%" PRIuMAX "]", + (uintmax_t)entry->index); + else + strbuf_addchars(&buf, ' ', INDEX_WIDTH); + strbuf_addstr(&buf, " | "); strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value); strbuf_addch(&buf, ' '); @@ -461,6 +575,12 @@ static void stats_table_print_structure(const struct stats_table *table) printf("%s\n", buf.buf); } + if (table->annotations.nr) { + printf("\n"); + for_each_string_list_item(item, &table->annotations) + printf("%s\n", item->string); + } + strbuf_release(&buf); } @@ -476,46 +596,76 @@ static void stats_table_clear(struct stats_table *table) } string_list_clear(&table->rows, 1); + string_list_clear(&table->annotations, 1); +} + +static inline void print_keyvalue(const char *key, char key_delim, size_t value, + char value_delim) +{ + printf("%s%c%" PRIuMAX "%c", key, key_delim, (uintmax_t)value, + value_delim); +} + +static void print_object_data(const char *key, char key_delim, + struct object_data *data, char value_delim) +{ + print_keyvalue(key, key_delim, data->value, value_delim); + printf("%s_oid%c%s%c", key, key_delim, oid_to_hex(&data->oid), + value_delim); } static void structure_keyvalue_print(struct repo_structure *stats, char key_delim, char value_delim) { - printf("references.branches.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.branches, value_delim); - printf("references.tags.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.tags, value_delim); - printf("references.remotes.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.remotes, value_delim); - printf("references.others.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.others, value_delim); - - printf("objects.commits.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.commits, value_delim); - printf("objects.trees.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.trees, value_delim); - printf("objects.blobs.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.blobs, value_delim); - printf("objects.tags.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.tags, value_delim); - - printf("objects.commits.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.commits, value_delim); - printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.trees, value_delim); - printf("objects.blobs.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.blobs, value_delim); - printf("objects.tags.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.tags, value_delim); - - printf("objects.commits.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.commits, value_delim); - printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.trees, value_delim); - printf("objects.blobs.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.blobs, value_delim); - printf("objects.tags.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.tags, value_delim); + print_keyvalue("references.branches.count", key_delim, + stats->refs.branches, value_delim); + print_keyvalue("references.tags.count", key_delim, + stats->refs.tags, value_delim); + print_keyvalue("references.remotes.count", key_delim, + stats->refs.remotes, value_delim); + print_keyvalue("references.others.count", key_delim, + stats->refs.others, value_delim); + + print_keyvalue("objects.commits.count", key_delim, + stats->objects.type_counts.commits, value_delim); + print_keyvalue("objects.trees.count", key_delim, + stats->objects.type_counts.trees, value_delim); + print_keyvalue("objects.blobs.count", key_delim, + stats->objects.type_counts.blobs, value_delim); + print_keyvalue("objects.tags.count", key_delim, + stats->objects.type_counts.tags, value_delim); + + print_keyvalue("objects.commits.inflated_size", key_delim, + stats->objects.inflated_sizes.commits, value_delim); + print_keyvalue("objects.trees.inflated_size", key_delim, + stats->objects.inflated_sizes.trees, value_delim); + print_keyvalue("objects.blobs.inflated_size", key_delim, + stats->objects.inflated_sizes.blobs, value_delim); + print_keyvalue("objects.tags.inflated_size", key_delim, + stats->objects.inflated_sizes.tags, value_delim); + + print_keyvalue("objects.commits.disk_size", key_delim, + stats->objects.disk_sizes.commits, value_delim); + print_keyvalue("objects.trees.disk_size", key_delim, + stats->objects.disk_sizes.trees, value_delim); + print_keyvalue("objects.blobs.disk_size", key_delim, + stats->objects.disk_sizes.blobs, value_delim); + print_keyvalue("objects.tags.disk_size", key_delim, + stats->objects.disk_sizes.tags, value_delim); + + print_object_data("objects.commits.max_size", key_delim, + &stats->objects.largest.commit_size, value_delim); + print_object_data("objects.trees.max_size", key_delim, + &stats->objects.largest.tree_size, value_delim); + print_object_data("objects.blobs.max_size", key_delim, + &stats->objects.largest.blob_size, value_delim); + print_object_data("objects.tags.max_size", key_delim, + &stats->objects.largest.tag_size, value_delim); + + print_object_data("objects.commits.max_parents", key_delim, + &stats->objects.largest.parent_count, value_delim); + print_object_data("objects.trees.max_entries", key_delim, + &stats->objects.largest.tree_entries, value_delim); fflush(stdout); } @@ -585,55 +735,97 @@ struct count_objects_data { struct progress *progress; }; +static void check_largest(struct object_data *data, struct object_id *oid, + size_t value) +{ + if (value > data->value || is_null_oid(&data->oid)) { + oidcpy(&data->oid, oid); + data->value = value; + } +} + +static size_t count_tree_entries(struct object *obj) +{ + struct tree *t = object_as_type(obj, OBJ_TREE, 0); + struct name_entry entry; + struct tree_desc desc; + size_t count = 0; + + init_tree_desc(&desc, &t->object.oid, t->buffer, t->size); + while (tree_entry(&desc, &entry)) + count++; + + return count; +} + static int count_objects(const char *path UNUSED, struct oid_array *oids, enum object_type type, void *cb_data) { struct count_objects_data *data = cb_data; struct object_stats *stats = data->stats; - size_t inflated_total = 0; - size_t disk_total = 0; size_t object_count; for (size_t i = 0; i < oids->nr; i++) { struct object_info oi = OBJECT_INFO_INIT; unsigned long inflated; + struct commit *commit; + struct object *obj; + void *content; off_t disk; + int eaten; oi.sizep = &inflated; oi.disk_sizep = &disk; + oi.contentp = &content; if (odb_read_object_info_extended(data->odb, &oids->oid[i], &oi, OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) < 0) continue; - inflated_total += inflated; - disk_total += disk; - } + obj = parse_object_buffer(the_repository, &oids->oid[i], type, + inflated, content, &eaten); + + switch (type) { + case OBJ_TAG: + stats->type_counts.tags++; + stats->inflated_sizes.tags += inflated; + stats->disk_sizes.tags += disk; + check_largest(&stats->largest.tag_size, &oids->oid[i], + inflated); + break; + case OBJ_COMMIT: + commit = object_as_type(obj, OBJ_COMMIT, 0); + stats->type_counts.commits++; + stats->inflated_sizes.commits += inflated; + stats->disk_sizes.commits += disk; + check_largest(&stats->largest.commit_size, &oids->oid[i], + inflated); + check_largest(&stats->largest.parent_count, &oids->oid[i], + commit_list_count(commit->parents)); + break; + case OBJ_TREE: + stats->type_counts.trees++; + stats->inflated_sizes.trees += inflated; + stats->disk_sizes.trees += disk; + check_largest(&stats->largest.tree_size, &oids->oid[i], + inflated); + check_largest(&stats->largest.tree_entries, &oids->oid[i], + count_tree_entries(obj)); + break; + case OBJ_BLOB: + stats->type_counts.blobs++; + stats->inflated_sizes.blobs += inflated; + stats->disk_sizes.blobs += disk; + check_largest(&stats->largest.blob_size, &oids->oid[i], + inflated); + break; + default: + BUG("invalid object type"); + } - switch (type) { - case OBJ_TAG: - stats->type_counts.tags += oids->nr; - stats->inflated_sizes.tags += inflated_total; - stats->disk_sizes.tags += disk_total; - break; - case OBJ_COMMIT: - stats->type_counts.commits += oids->nr; - stats->inflated_sizes.commits += inflated_total; - stats->disk_sizes.commits += disk_total; - break; - case OBJ_TREE: - stats->type_counts.trees += oids->nr; - stats->inflated_sizes.trees += inflated_total; - stats->disk_sizes.trees += disk_total; - break; - case OBJ_BLOB: - stats->type_counts.blobs += oids->nr; - stats->inflated_sizes.blobs += inflated_total; - stats->disk_sizes.blobs += disk_total; - break; - default: - BUG("invalid object type"); + if (!eaten) + free(content); } object_count = get_total_object_values(&stats->type_counts); @@ -669,6 +861,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, { struct stats_table table = { .rows = STRING_LIST_INIT_DUP, + .annotations = STRING_LIST_INIT_DUP, }; enum output_format format = FORMAT_TABLE; struct repo_structure stats = { 0 }; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 143f7cb3cca7d0..f3e132888fabd4 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1160,7 +1160,7 @@ static void submodule_summary_callback(struct diff_queue_struct *q, if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode)) continue; - temp = (struct module_cb*)malloc(sizeof(struct module_cb)); + temp = xmalloc(sizeof(*temp)); temp->mod_src = p->one->mode; temp->mod_dst = p->two->mode; temp->oid_src = p->one->oid; diff --git a/builtin/tag.c b/builtin/tag.c index aeb04c487fe95a..e0f05f94fdbe3e 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -499,8 +499,8 @@ int cmd_tag(int argc, OPT_CALLBACK_F('m', "message", &msg, N_("message"), N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), - OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), - N_("add custom trailer(s)"), PARSE_OPT_NONEG), + OPT_STRVEC(0, "trailer", &trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")), OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), OPT_CLEANUP(&cleanup_arg), @@ -568,6 +568,9 @@ int cmd_tag(int argc, if (cmdmode == 'l') setup_auto_pager("tag", 1); + if (trailer_args.nr) + trailer_config_init(); + if (opt.sign == -1) opt.sign = cmdmode ? 0 : config_sign_tag > 0; diff --git a/builtin/worktree.c b/builtin/worktree.c index bc2d0d645ba945..4035b1cb06cba7 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -539,7 +539,7 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_worktree_linking_files(sb_git, sb, opts->relative_paths); + write_worktree_linking_files(sb_git.buf, sb.buf, opts->relative_paths); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); diff --git a/connected.c b/connected.c index 79403108dd8f57..6718503649da8a 100644 --- a/connected.c +++ b/connected.c @@ -45,20 +45,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data, return err; } - if (transport && transport->smart_options && - transport->smart_options->self_contained_and_connected && - transport->pack_lockfiles.nr == 1 && - strip_suffix(transport->pack_lockfiles.items[0].string, - ".keep", &base_len)) { - struct strbuf idx_file = STRBUF_INIT; - strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string, - base_len); - strbuf_addstr(&idx_file, ".idx"); - new_pack = add_packed_git(the_repository, idx_file.buf, - idx_file.len, 1); - strbuf_release(&idx_file); - } - if (repo_has_promisor_remote(the_repository)) { /* * For partial clones, we don't want to have to do a regular @@ -90,7 +76,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data, promisor_pack_found: ; } while ((oid = fn(cb_data)) != NULL); - free(new_pack); return 0; } @@ -127,15 +112,27 @@ int check_connected(oid_iterate_fn fn, void *cb_data, else rev_list.no_stderr = opt->quiet; - if (start_command(&rev_list)) { - free(new_pack); + if (start_command(&rev_list)) return error(_("Could not run 'git rev-list'")); - } sigchain_push(SIGPIPE, SIG_IGN); rev_list_in = xfdopen(rev_list.in, "w"); + if (transport && transport->smart_options && + transport->smart_options->self_contained_and_connected && + transport->pack_lockfiles.nr == 1 && + strip_suffix(transport->pack_lockfiles.items[0].string, + ".keep", &base_len)) { + struct strbuf idx_file = STRBUF_INIT; + strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string, + base_len); + strbuf_addstr(&idx_file, ".idx"); + new_pack = add_packed_git(the_repository, idx_file.buf, + idx_file.len, 1); + strbuf_release(&idx_file); + } + do { /* * If index-pack already checked that: @@ -162,6 +159,9 @@ int check_connected(oid_iterate_fn fn, void *cb_data, err = error_errno(_("failed to close rev-list's stdin")); sigchain_pop(SIGPIPE); - free(new_pack); + if (new_pack) { + close_pack(new_pack); + free(new_pack); + } return finish_command(&rev_list) || err; } diff --git a/dir.c b/dir.c index 026d8516a912af..fcb8f6dd2aa969 100644 --- a/dir.c +++ b/dir.c @@ -3518,15 +3518,15 @@ int get_sparse_checkout_patterns(struct pattern_list *pl) int remove_path(const char *name) { - char *slash; + const char *last; if (unlink(name) && !is_missing_file_error(errno)) return -1; - slash = strrchr(name, '/'); - if (slash) { + last = strrchr(name, '/'); + if (last) { char *dirs = xstrdup(name); - slash = dirs + (slash - name); + char *slash = dirs + (last - name); do { *slash = '\0'; if (startup_info->original_cwd && diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 7f3e7b8f505ebc..cef67e591954a9 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -125,9 +125,9 @@ int gently_parse_list_objects_filter( static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?"; static int has_reserved_character( - struct strbuf *sub_spec, struct strbuf *errbuf) + const char *sub_spec, struct strbuf *errbuf) { - const char *c = sub_spec->buf; + const char *c = sub_spec; while (*c) { if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) { strbuf_addf( @@ -144,7 +144,7 @@ static int has_reserved_character( static int parse_combine_subfilter( struct list_objects_filter_options *filter_options, - struct strbuf *subspec, + const char *subspec, struct strbuf *errbuf) { size_t new_index = filter_options->sub_nr; @@ -155,7 +155,7 @@ static int parse_combine_subfilter( filter_options->sub_alloc); list_objects_filter_init(&filter_options->sub[new_index]); - decoded = url_percent_decode(subspec->buf); + decoded = url_percent_decode(subspec); result = has_reserved_character(subspec, errbuf); if (result) @@ -182,34 +182,34 @@ static int parse_combine_filter( const char *arg, struct strbuf *errbuf) { - struct strbuf **subspecs = strbuf_split_str(arg, '+', 0); - size_t sub; + const char *p = arg; + struct strbuf sub = STRBUF_INIT; int result = 0; - if (!subspecs[0]) { + if (!*p) { strbuf_addstr(errbuf, _("expected something after combine:")); result = 1; goto cleanup; } - for (sub = 0; subspecs[sub] && !result; sub++) { - if (subspecs[sub + 1]) { - /* - * This is not the last subspec. Remove trailing "+" so - * we can parse it. - */ - size_t last = subspecs[sub]->len - 1; - assert(subspecs[sub]->buf[last] == '+'); - strbuf_remove(subspecs[sub], last, 1); - } - result = parse_combine_subfilter( - filter_options, subspecs[sub], errbuf); + while (*p && !result) { + const char *end = strchrnul(p, '+'); + + strbuf_reset(&sub); + strbuf_add(&sub, p, end - p); + + if (sub.len) + result = parse_combine_subfilter(filter_options, sub.buf, errbuf); + + if (!*end) + break; + p = end + 1; } + strbuf_release(&sub); filter_options->choice = LOFC_COMBINE; cleanup: - strbuf_list_free(subspecs); if (result) list_objects_filter_release(filter_options); return result; diff --git a/meson.build b/meson.build index 1036eafc3c0d0c..9e9ba9b82b80ea 100644 --- a/meson.build +++ b/meson.build @@ -1438,7 +1438,7 @@ else 'getpagesize' : [], } - if get_option('b_sanitize').contains('address') + if get_option('b_sanitize').contains('address') or get_option('b_sanitize').contains('leak') libgit_c_args += '-DNO_MMAP' libgit_sources += 'compat/mmap.c' else diff --git a/object-file.c b/object-file.c index a3ff7f586c91f3..c62e5496e0795d 100644 --- a/object-file.c +++ b/object-file.c @@ -2211,7 +2211,7 @@ int odb_source_loose_read_object_stream(struct odb_read_stream **out, return 0; error: git_inflate_end(&st->z); - munmap(st->mapped, st->mapsize); + munmap(mapped, mapsize); free(st); return -1; } diff --git a/pack-revindex.c b/pack-revindex.c index 56cd803a6798d5..1fe0afe89907c5 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -277,6 +277,10 @@ int load_pack_revindex_from_disk(struct packed_git *p) { char *revindex_name; int ret; + + if (p->revindex_data) + return 0; + if (open_pack_index(p)) return -1; diff --git a/patch-ids.c b/patch-ids.c index a5683b462c6e76..1fbc88cbec874e 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -41,7 +41,14 @@ static int patch_id_neq(const void *cmpfn_data, const struct hashmap_entry *entry_or_key, const void *keydata UNUSED) { - /* NEEDSWORK: const correctness? */ + /* + * We drop the 'const' modifier here intentionally. + * + * Even though eptr and entry_or_key are const, we want to + * lazily compute their .patch_id members; see b3dfeebb (rebase: + * avoid computing unnecessary patch IDs, 2016-07-29). So we cast + * the constness away with container_of(). + */ struct diff_options *opt = (void *)cmpfn_data; struct patch_id *a, *b; diff --git a/pretty.c b/pretty.c index ebf4da4834075f..74673714c8d39a 100644 --- a/pretty.c +++ b/pretty.c @@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (!commit->object.parsed) parse_object(the_repository, &commit->object.oid); + if (starts_with(placeholder, "(count)")) { + if (!c->pretty_ctx->rev) + die(_("this format specifier can't be used with this command")); + strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total), + c->pretty_ctx->rev->nr); + return 7; + } + + if (starts_with(placeholder, "(total)")) { + if (!c->pretty_ctx->rev) + die(_("this format specifier can't be used with this command")); + strbuf_addf(sb, "%d", c->pretty_ctx->rev->total); + return 7; + } + switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); diff --git a/run-command.c b/run-command.c index a8a8974638d7ef..32c290ee6a221f 100644 --- a/run-command.c +++ b/run-command.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -742,8 +741,8 @@ int start_command(struct child_process *cmd) fflush(NULL); - if (cmd->close_object_store) - odb_close(the_repository->objects); + if (cmd->odb_to_close) + odb_close(cmd->odb_to_close); #ifndef GIT_WINDOWS_NATIVE { @@ -1937,11 +1936,12 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) trace2_region_leave(tr2_category, tr2_label, NULL); } -int prepare_auto_maintenance(int quiet, struct child_process *maint) +int prepare_auto_maintenance(struct repository *r, int quiet, + struct child_process *maint) { int enabled, auto_detach; - if (!repo_config_get_bool(the_repository, "maintenance.auto", &enabled) && + if (!repo_config_get_bool(r, "maintenance.auto", &enabled) && !enabled) return 0; @@ -1950,12 +1950,12 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint) * honoring `gc.autoDetach`. This is somewhat weird, but required to * retain behaviour from when we used to run git-gc(1) here. */ - if (repo_config_get_bool(the_repository, "maintenance.autodetach", &auto_detach) && - repo_config_get_bool(the_repository, "gc.autodetach", &auto_detach)) + if (repo_config_get_bool(r, "maintenance.autodetach", &auto_detach) && + repo_config_get_bool(r, "gc.autodetach", &auto_detach)) auto_detach = git_env_bool("GIT_TEST_MAINT_AUTO_DETACH", true); maint->git_cmd = 1; - maint->close_object_store = 1; + maint->odb_to_close = r->objects; strvec_pushl(&maint->args, "maintenance", "run", "--auto", NULL); strvec_push(&maint->args, quiet ? "--quiet" : "--no-quiet"); strvec_push(&maint->args, auto_detach ? "--detach" : "--no-detach"); @@ -1963,10 +1963,10 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint) return 1; } -int run_auto_maintenance(int quiet) +int run_auto_maintenance(struct repository *r, int quiet) { struct child_process maint = CHILD_PROCESS_INIT; - if (!prepare_auto_maintenance(quiet, &maint)) + if (!prepare_auto_maintenance(r, quiet, &maint)) return 0; return run_command(&maint); } diff --git a/run-command.h b/run-command.h index 428d76ebd620ff..8ca496d7bdebfd 100644 --- a/run-command.h +++ b/run-command.h @@ -5,6 +5,8 @@ #include "strvec.h" +struct repository; + /** * The run-command API offers a versatile tool to run sub-processes with * redirected input and output as well as with a modified environment @@ -136,7 +138,7 @@ struct child_process { * want to repack because that would delete `.pack` files (and on * Windows, you cannot delete files that are still in use). */ - unsigned close_object_store:1; + struct object_database *odb_to_close; unsigned stdout_to_stderr:1; unsigned clean_on_exit:1; @@ -227,12 +229,13 @@ int run_command(struct child_process *); * process has been prepared and is ready to run, or 0 in case auto-maintenance * should be skipped. */ -int prepare_auto_maintenance(int quiet, struct child_process *maint); +int prepare_auto_maintenance(struct repository *r, int quiet, + struct child_process *maint); /* * Trigger an auto-gc */ -int run_auto_maintenance(int quiet); +int run_auto_maintenance(struct repository *r, int quiet); /** * Execute the given command, sending "in" to its stdin, and capturing its diff --git a/sequencer.c b/sequencer.c index aafd0bc959664a..e5af49cecd0859 100644 --- a/sequencer.c +++ b/sequencer.c @@ -209,6 +209,7 @@ static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedul static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-reschedule-failed-exec") static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") +static GIT_PATH_FUNC(rebase_path_trailer, "rebase-merge/trailer") /* * A 'struct replay_ctx' represents the private state of the sequencer. @@ -420,6 +421,7 @@ void replay_opts_release(struct replay_opts *opts) if (opts->revs) release_revisions(opts->revs); free(opts->revs); + strvec_clear(&opts->trailer_args); replay_ctx_release(ctx); free(opts->ctx); } @@ -2027,12 +2029,15 @@ static int append_squash_message(struct strbuf *buf, const char *body, if (is_fixup_flag(command, flag) && !seen_squash(ctx)) { /* * We're replacing the commit message so we need to - * append the Signed-off-by: trailer if the user - * requested '--signoff'. + * append any trailers if the user requested + * '--signoff' or '--trailer'. */ if (opts->signoff) append_signoff(buf, 0, 0); + if (opts->trailer_args.nr) + amend_strbuf_with_trailers(buf, &opts->trailer_args); + if ((command == TODO_FIXUP) && (flag & TODO_REPLACE_FIXUP_MSG) && (file_exists(rebase_path_fixup_msg()) || @@ -2451,6 +2456,9 @@ static int do_pick_commit(struct repository *r, if (opts->signoff && !is_fixup(command)) append_signoff(&ctx->message, 0, 0); + if (opts->trailer_args.nr && !is_fixup(command)) + amend_strbuf_with_trailers(&ctx->message, &opts->trailer_args); + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) res = -1; else if (!opts->strategy || @@ -3180,6 +3188,33 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) parse_strategy_opts(opts, buf->buf); } +static int read_trailers(struct replay_opts *opts, struct strbuf *buf) +{ + ssize_t len; + + strbuf_reset(buf); + len = strbuf_read_file(buf, rebase_path_trailer(), 0); + if (len > 0) { + char *p = buf->buf, *nl; + + trailer_config_init(); + + while ((nl = strchr(p, '\n'))) { + *nl = '\0'; + if (!*p) + return error(_("trailers file contains empty line")); + strvec_push(&opts->trailer_args, p); + p = nl + 1; + } + } else if (!len) { + return error(_("trailers file is empty")); + } else if (errno != ENOENT) { + return error(_("cannot read trailers files")); + } + + return 0; +} + static int read_populate_opts(struct replay_opts *opts) { struct replay_ctx *ctx = opts->ctx; @@ -3241,6 +3276,11 @@ static int read_populate_opts(struct replay_opts *opts) opts->keep_redundant_commits = 1; read_strategy_opts(opts, &buf); + + if (read_trailers(opts, &buf)) { + ret = -1; + goto done_rebase_i; + } strbuf_reset(&buf); if (read_oneliner(&ctx->current_fixups, @@ -3336,6 +3376,14 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, write_file(rebase_path_reschedule_failed_exec(), "%s", ""); else write_file(rebase_path_no_reschedule_failed_exec(), "%s", ""); + if (opts->trailer_args.nr) { + struct strbuf buf = STRBUF_INIT; + + for (size_t i = 0; i < opts->trailer_args.nr; i++) + strbuf_addf(&buf, "%s\n", opts->trailer_args.v[i]); + write_file(rebase_path_trailer(), "%s", buf.buf); + strbuf_release(&buf); + } return 0; } diff --git a/sequencer.h b/sequencer.h index 719684c8a9fb2e..bea20da085f0a2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -57,6 +57,8 @@ struct replay_opts { int ignore_date; int commit_use_reference; + struct strvec trailer_args; + int mainline; char *gpg_sign; @@ -84,6 +86,7 @@ struct replay_opts { #define REPLAY_OPTS_INIT { \ .edit = -1, \ .action = -1, \ + .trailer_args = STRVEC_INIT, \ .xopts = STRVEC_INIT, \ .ctx = replay_ctx_new(), \ } diff --git a/setup.c b/setup.c index 393b970ae4f96d..7ec4427368a2a7 100644 --- a/setup.c +++ b/setup.c @@ -920,8 +920,10 @@ int verify_repository_format(const struct repository_format *format, void read_gitfile_error_die(int error_code, const char *path, const char *dir) { switch (error_code) { - case READ_GITFILE_ERR_STAT_FAILED: case READ_GITFILE_ERR_NOT_A_FILE: + case READ_GITFILE_ERR_STAT_FAILED: + case READ_GITFILE_ERR_MISSING: + case READ_GITFILE_ERR_IS_A_DIR: /* non-fatal; follow return path */ break; case READ_GITFILE_ERR_OPEN_FAILED: @@ -964,8 +966,14 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) static struct strbuf realpath = STRBUF_INIT; if (stat(path, &st)) { - /* NEEDSWORK: discern between ENOENT vs other errors */ - error_code = READ_GITFILE_ERR_STAT_FAILED; + if (errno == ENOENT || errno == ENOTDIR) + error_code = READ_GITFILE_ERR_MISSING; + else + error_code = READ_GITFILE_ERR_STAT_FAILED; + goto cleanup_return; + } + if (S_ISDIR(st.st_mode)) { + error_code = READ_GITFILE_ERR_IS_A_DIR; goto cleanup_return; } if (!S_ISREG(st.st_mode)) { @@ -1601,20 +1609,37 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (offset > min_offset) strbuf_addch(dir, '/'); strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT); - gitdirenv = read_gitfile_gently(dir->buf, die_on_error ? - NULL : &error_code); + gitdirenv = read_gitfile_gently(dir->buf, &error_code); if (!gitdirenv) { - if (die_on_error || - error_code == READ_GITFILE_ERR_NOT_A_FILE) { - /* NEEDSWORK: fail if .git is not file nor dir */ + switch (error_code) { + case READ_GITFILE_ERR_MISSING: + /* no .git in this directory, move on */ + break; + case READ_GITFILE_ERR_IS_A_DIR: if (is_git_directory(dir->buf)) { gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; gitdir_path = xstrdup(dir->buf); } - } else if (error_code != READ_GITFILE_ERR_STAT_FAILED) - return GIT_DIR_INVALID_GITFILE; - } else + break; + case READ_GITFILE_ERR_STAT_FAILED: + if (die_on_error) + die(_("error reading '%s'"), dir->buf); + else + return GIT_DIR_INVALID_GITFILE; + case READ_GITFILE_ERR_NOT_A_FILE: + if (die_on_error) + die(_("not a regular file: '%s'"), dir->buf); + else + return GIT_DIR_INVALID_GITFILE; + default: + if (die_on_error) + read_gitfile_error_die(error_code, dir->buf, NULL); + else + return GIT_DIR_INVALID_GITFILE; + } + } else { gitfile = xstrdup(dir->buf); + } /* * Earlier, we tentatively added DEFAULT_GIT_DIR_ENVIRONMENT * to check that directory for a repository. diff --git a/setup.h b/setup.h index bcb16b0b4a6662..80bc6e5f078af8 100644 --- a/setup.h +++ b/setup.h @@ -36,6 +36,8 @@ int is_nonbare_repository_dir(struct strbuf *path); #define READ_GITFILE_ERR_NO_PATH 6 #define READ_GITFILE_ERR_NOT_A_REPO 7 #define READ_GITFILE_ERR_TOO_LARGE 8 +#define READ_GITFILE_ERR_MISSING 9 +#define READ_GITFILE_ERR_IS_A_DIR 10 void read_gitfile_error_die(int error_code, const char *path, const char *dir); const char *read_gitfile_gently(const char *path, int *return_error_code); #define read_gitfile(path) read_gitfile_gently((path), NULL) diff --git a/sideband.c b/sideband.c index ea7c25211ef7e1..04282a568edd90 100644 --- a/sideband.c +++ b/sideband.c @@ -10,6 +10,7 @@ #include "help.h" #include "pkt-line.h" #include "write-or-die.h" +#include "urlmatch.h" struct keyword_entry { /* @@ -26,6 +27,85 @@ static struct keyword_entry keywords[] = { { "error", GIT_COLOR_BOLD_RED }, }; +static enum { + ALLOW_CONTROL_SEQUENCES_UNSET = -1, + ALLOW_NO_CONTROL_CHARACTERS = 0, + ALLOW_ANSI_COLOR_SEQUENCES = 1<<0, + ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1, + ALLOW_ANSI_ERASE = 1<<2, + ALLOW_ALL_CONTROL_CHARACTERS = 1<<3, + ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES +} allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET; + +static inline int skip_prefix_in_csv(const char *value, const char *prefix, + const char **out) +{ + if (!skip_prefix(value, prefix, &value) || + (*value && *value != ',')) + return 0; + *out = value + !!*value; + return 1; +} + +int sideband_allow_control_characters_config(const char *var, const char *value) +{ + switch (git_parse_maybe_bool(value)) { + case 0: + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + return 0; + case 1: + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; + return 0; + default: + break; + } + + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + while (*value) { + if (skip_prefix_in_csv(value, "color", &value)) + allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES; + else if (skip_prefix_in_csv(value, "cursor", &value)) + allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS; + else if (skip_prefix_in_csv(value, "erase", &value)) + allow_control_characters |= ALLOW_ANSI_ERASE; + else if (skip_prefix_in_csv(value, "true", &value)) + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; + else if (skip_prefix_in_csv(value, "false", &value)) + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; + else + warning(_("unrecognized value for '%s': '%s'"), var, value); + } + return 0; +} + +static int sideband_config_callback(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *data UNUSED) +{ + if (!strcmp(var, "sideband.allowcontrolcharacters")) + return sideband_allow_control_characters_config(var, value); + + return 0; +} + +void sideband_apply_url_config(const char *url) +{ + struct urlmatch_config config = URLMATCH_CONFIG_INIT; + char *normalized_url; + + if (!url) + BUG("must not call sideband_apply_url_config(NULL)"); + + config.section = "sideband"; + config.collect_fn = sideband_config_callback; + + normalized_url = url_normalize(url, &config.url); + repo_config(the_repository, urlmatch_config_entry, &config); + free(normalized_url); + string_list_clear(&config.vars, 1); + urlmatch_config_release(&config); +} + /* Returns a color setting (GIT_COLOR_NEVER, etc). */ static enum git_colorbool use_sideband_colors(void) { @@ -39,6 +119,14 @@ static enum git_colorbool use_sideband_colors(void) if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN) return use_sideband_colors_cached; + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) { + if (!repo_config_get_value(the_repository, "sideband.allowcontrolcharacters", &value)) + sideband_allow_control_characters_config("sideband.allowcontrolcharacters", value); + + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) + allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES; + } + if (!repo_config_get_string_tmp(the_repository, key, &value)) use_sideband_colors_cached = git_config_colorbool(key, value); else if (!repo_config_get_string_tmp(the_repository, "color.ui", &value)) @@ -66,6 +154,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref list_config_item(list, prefix, keywords[i].keyword); } +static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n) +{ + int i; + + /* + * Valid ANSI color sequences are of the form + * + * ESC [ [ [; ]*] m + * + * These are part of the Select Graphic Rendition sequences which + * contain more than just color sequences, for more details see + * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR. + * + * The cursor movement sequences are: + * + * ESC [ n A - Cursor up n lines (CUU) + * ESC [ n B - Cursor down n lines (CUD) + * ESC [ n C - Cursor forward n columns (CUF) + * ESC [ n D - Cursor back n columns (CUB) + * ESC [ n E - Cursor next line, beginning (CNL) + * ESC [ n F - Cursor previous line, beginning (CPL) + * ESC [ n G - Cursor to column n (CHA) + * ESC [ n ; m H - Cursor position (row n, col m) (CUP) + * ESC [ n ; m f - Same as H (HVP) + * + * The sequences to erase characters are: + * + * + * ESC [ 0 J - Clear from cursor to end of screen (ED) + * ESC [ 1 J - Clear from cursor to beginning of screen (ED) + * ESC [ 2 J - Clear entire screen (ED) + * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension + * ESC [ 0 K - Clear from cursor to end of line (EL) + * ESC [ 1 K - Clear from cursor to beginning of line (EL) + * ESC [ 2 K - Clear entire line (EL) + * ESC [ n M - Delete n lines (DL) + * ESC [ n P - Delete n characters (DCH) + * ESC [ n X - Erase n characters (ECH) + * + * For a comprehensive list of common ANSI Escape sequences, see + * https://www.xfree86.org/current/ctlseqs.html + */ + + if (n < 3 || src[0] != '\x1b' || src[1] != '[') + return 0; + + for (i = 2; i < n; i++) { + if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES) && + src[i] == 'm') || + ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS) && + strchr("ABCDEFGHf", src[i])) || + ((allow_control_characters & ALLOW_ANSI_ERASE) && + strchr("JKMPX", src[i]))) { + strbuf_add(dest, src, i + 1); + return i; + } + if (!isdigit(src[i]) && src[i] != ';') + break; + } + + return 0; +} + +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) +{ + int i; + + if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS)) { + strbuf_add(dest, src, n); + return; + } + + strbuf_grow(dest, n); + for (; n && *src; src++, n--) { + if (!iscntrl(*src) || *src == '\t' || *src == '\n') { + strbuf_addch(dest, *src); + } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS && + (i = handle_ansi_sequence(dest, src, n))) { + src += i; + n -= i; + } else { + strbuf_addch(dest, '^'); + strbuf_addch(dest, *src == 0x7f ? '?' : 0x40 + *src); + } + } +} + /* * Optionally highlight one keyword in remote output if it appears at the start * of the line. This should be called for a single line only, which is @@ -81,7 +256,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) int i; if (!want_color_stderr(use_sideband_colors())) { - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); return; } @@ -114,7 +289,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) } } - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); } diff --git a/sideband.h b/sideband.h index 5a25331be55d30..d15fa4015fa0a3 100644 --- a/sideband.h +++ b/sideband.h @@ -30,4 +30,18 @@ int demultiplex_sideband(const char *me, int status, void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); +/* + * Apply sideband configuration for the given URL. This should be called + * when a transport is created to allow URL-specific configuration of + * sideband behavior (e.g., sideband..allowControlCharacters). + */ +void sideband_apply_url_config(const char *url); + +/* + * Parse and set the sideband allow control characters configuration. + * The var parameter should be the key name (without section prefix). + * Returns 0 if the variable was recognized and handled, non-zero otherwise. + */ +int sideband_allow_control_characters_config(const char *var, const char *value); + #endif diff --git a/submodule.c b/submodule.c index 4f9aaa2c75679b..cd879a5cfe73ec 100644 --- a/submodule.c +++ b/submodule.c @@ -2559,7 +2559,7 @@ void absorb_git_dir_into_superproject(const char *path, const struct submodule *sub; struct strbuf sub_gitdir = STRBUF_INIT; - if (err_code == READ_GITFILE_ERR_STAT_FAILED) { + if (err_code == READ_GITFILE_ERR_MISSING) { /* unpopulated as expected */ strbuf_release(&gitdir); return; diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c index 229d495ecfa668..998a1f0b428bed 100644 --- a/t/helper/test-example-tap.c +++ b/t/helper/test-example-tap.c @@ -63,6 +63,8 @@ static void t_messages(void) check_str("NULL", NULL); check_char('a', ==, '\n'); check_char('\\', ==, '\''); + check_char('\a', ==, '\v'); + check_char('\x00', ==, '\x01'); } static void t_empty(void) @@ -123,6 +125,8 @@ int cmd__example_tap(int argc UNUSED, const char **argv UNUSED) check_str("NULL", NULL); check_char('a', ==, '\n'); check_char('\\', ==, '\''); + check_char('\a', ==, '\v'); + check_char('\x00', ==, '\x01'); } if_test ("if_test test with no checks") ; /* nothing */ diff --git a/t/meson.build b/t/meson.build index f66a73f8a07d93..e539117ca4fedd 100644 --- a/t/meson.build +++ b/t/meson.build @@ -81,6 +81,7 @@ integration_tests = [ 't0006-date.sh', 't0007-git-var.sh', 't0008-ignores.sh', + 't0009-git-dir-validation.sh', 't0010-racy-git.sh', 't0012-help.sh', 't0013-sha1dc.sh', @@ -392,6 +393,7 @@ integration_tests = [ 't3436-rebase-more-options.sh', 't3437-rebase-fixup-options.sh', 't3438-rebase-broken-files.sh', + 't3440-rebase-trailer.sh', 't3450-history.sh', 't3451-history-reword.sh', 't3500-cherry.sh', diff --git a/t/t0009-git-dir-validation.sh b/t/t0009-git-dir-validation.sh new file mode 100755 index 00000000000000..33d21ed9ea1061 --- /dev/null +++ b/t/t0009-git-dir-validation.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='setup: validation of .git file/directory types + +Verify that setup_git_directory() correctly handles: +1. Valid .git directories (including symlinks to them). +2. Invalid .git files (FIFOs, sockets) by erroring out. +3. Invalid .git files (garbage) by erroring out. +' + +. ./test-lib.sh + +test_expect_success 'setup: create parent git repository' ' + git init parent && + test_commit -C parent "root-commit" +' + +test_expect_success SYMLINKS 'setup: .git as a symlink to a directory is valid' ' + test_when_finished "rm -rf parent/link-to-dir" && + mkdir -p parent/link-to-dir && + ( + cd parent/link-to-dir && + git init real-repo && + ln -s real-repo/.git .git && + git rev-parse --git-dir >actual && + echo .git >expect && + test_cmp expect actual + ) +' + +test_expect_success PIPE 'setup: .git as a FIFO (named pipe) is rejected' ' + test_when_finished "rm -rf parent/fifo-trap" && + mkdir -p parent/fifo-trap && + ( + cd parent/fifo-trap && + mkfifo .git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "not a regular file" stderr + ) +' + +test_expect_success SYMLINKS,PIPE 'setup: .git as a symlink to a FIFO is rejected' ' + test_when_finished "rm -rf parent/symlink-fifo-trap" && + mkdir -p parent/symlink-fifo-trap && + ( + cd parent/symlink-fifo-trap && + mkfifo target-fifo && + ln -s target-fifo .git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "not a regular file" stderr + ) +' + +test_expect_success 'setup: .git with garbage content is rejected' ' + test_when_finished "rm -rf parent/garbage-trap" && + mkdir -p parent/garbage-trap && + ( + cd parent/garbage-trap && + echo "garbage" >.git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "invalid gitfile format" stderr + ) +' + +test_expect_success 'setup: .git as an empty directory is ignored' ' + test_when_finished "rm -rf parent/empty-dir" && + mkdir -p parent/empty-dir && + ( + cd parent/empty-dir && + git rev-parse --git-dir >expect && + mkdir .git && + git rev-parse --git-dir >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh index 3db10f095c2edc..66838a00b220cc 100755 --- a/t/t0080-unit-test-output.sh +++ b/t/t0080-unit-test-output.sh @@ -6,10 +6,10 @@ test_description='Test the output of the unit test framework' test_expect_success 'TAP output from unit tests' - <<\EOT cat >expect <<-EOF && - # BUG: check outside of test at t/helper/test-example-tap.c:75 + # BUG: check outside of test at t/helper/test-example-tap.c:77 ok 1 - passing test ok 2 - passing test and assertion return 1 - # check "1 == 2" failed at t/helper/test-example-tap.c:79 + # check "1 == 2" failed at t/helper/test-example-tap.c:81 # left: 1 # right: 2 not ok 3 - failing test @@ -34,53 +34,65 @@ test_expect_success 'TAP output from unit tests' - <<\EOT not ok 15 - failing check after TEST_TODO() ok 16 - failing check after TEST_TODO() returns 0 # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:62 - # left: "\011hello\\\\" - # right: "there\"\012" + # left: "\thello\\\\" + # right: "there\"\n" # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:63 # left: "NULL" # right: NULL # check "'a' == '\n'" failed at t/helper/test-example-tap.c:64 # left: 'a' - # right: '\012' + # right: '\n' # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:65 # left: '\\\\' # right: '\\'' + # check "'\a' == '\v'" failed at t/helper/test-example-tap.c:66 + # left: '\a' + # right: '\v' + # check "'\x00' == '\x01'" failed at t/helper/test-example-tap.c:67 + # left: '\000' + # right: '\001' not ok 17 - messages from failing string and char comparison - # BUG: test has no checks at t/helper/test-example-tap.c:94 + # BUG: test has no checks at t/helper/test-example-tap.c:96 not ok 18 - test with no checks ok 19 - test with no checks returns 0 ok 20 - if_test passing test - # check "1 == 2" failed at t/helper/test-example-tap.c:100 + # check "1 == 2" failed at t/helper/test-example-tap.c:102 # left: 1 # right: 2 not ok 21 - if_test failing test not ok 22 - if_test passing TEST_TODO() # TODO - # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104 + # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:106 not ok 23 - if_test failing TEST_TODO() - # check "0" failed at t/helper/test-example-tap.c:106 + # check "0" failed at t/helper/test-example-tap.c:108 # skipping test - missing prerequisite - # skipping check '1' at t/helper/test-example-tap.c:108 + # skipping check '1' at t/helper/test-example-tap.c:110 ok 24 - if_test test_skip() # SKIP # skipping test - missing prerequisite ok 25 - if_test test_skip() inside TEST_TODO() # SKIP - # check "0" failed at t/helper/test-example-tap.c:113 + # check "0" failed at t/helper/test-example-tap.c:115 not ok 26 - if_test TEST_TODO() after failing check - # check "0" failed at t/helper/test-example-tap.c:119 + # check "0" failed at t/helper/test-example-tap.c:121 not ok 27 - if_test failing check after TEST_TODO() - # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:122 - # left: "\011hello\\\\" - # right: "there\"\012" - # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:123 + # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:124 + # left: "\thello\\\\" + # right: "there\"\n" + # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:125 # left: "NULL" # right: NULL - # check "'a' == '\n'" failed at t/helper/test-example-tap.c:124 + # check "'a' == '\n'" failed at t/helper/test-example-tap.c:126 # left: 'a' - # right: '\012' - # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:125 + # right: '\n' + # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:127 # left: '\\\\' # right: '\\'' + # check "'\a' == '\v'" failed at t/helper/test-example-tap.c:128 + # left: '\a' + # right: '\v' + # check "'\x00' == '\x01'" failed at t/helper/test-example-tap.c:129 + # left: '\000' + # right: '\001' not ok 28 - if_test messages from failing string and char comparison - # BUG: test has no checks at t/helper/test-example-tap.c:127 + # BUG: test has no checks at t/helper/test-example-tap.c:131 not ok 29 - if_test test with no checks 1..29 EOF diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh index a67b38ab178125..98921ce1cbb66f 100755 --- a/t/t1901-repo-structure.sh +++ b/t/t1901-repo-structure.sh @@ -27,31 +27,43 @@ test_expect_success 'empty repository' ' ( cd repo && cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ------ | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | - | | | - | * Reachable objects | | - | * Count | 0 | - | * Commits | 0 | - | * Trees | 0 | - | * Blobs | 0 | - | * Tags | 0 | - | * Inflated size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - | * Disk size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | + | Repository structure | Value | + | ------------------------- | ------ | + | * References | | + | * Count | 0 | + | * Branches | 0 | + | * Tags | 0 | + | * Remotes | 0 | + | * Others | 0 | + | | | + | * Reachable objects | | + | * Count | 0 | + | * Commits | 0 | + | * Trees | 0 | + | * Blobs | 0 | + | * Tags | 0 | + | * Inflated size | 0 B | + | * Commits | 0 B | + | * Trees | 0 B | + | * Blobs | 0 B | + | * Tags | 0 B | + | * Disk size | 0 B | + | * Commits | 0 B | + | * Trees | 0 B | + | * Blobs | 0 B | + | * Tags | 0 B | + | | | + | * Largest objects | | + | * Commits | | + | * Maximum size | 0 B | + | * Maximum parents | 0 | + | * Trees | | + | * Maximum size | 0 B | + | * Maximum entries | 0 | + | * Blobs | | + | * Maximum size | 0 B | + | * Tags | | + | * Maximum size | 0 B | EOF git repo structure >out 2>err && @@ -79,31 +91,50 @@ test_expect_success SHA1 'repository with references and objects' ' # git-rev-list(1) --disk-usage=human option printing the full # "byte/bytes" unit string instead of just "B". cat >expect <<-EOF && - | Repository structure | Value | - | -------------------- | ---------- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | - | | | - | * Reachable objects | | - | * Count | 3.02 k | - | * Commits | 1.01 k | - | * Trees | 1.01 k | - | * Blobs | 1.01 k | - | * Tags | 1 | - | * Inflated size | 16.03 MiB | - | * Commits | 217.92 KiB | - | * Trees | 15.81 MiB | - | * Blobs | 11.68 KiB | - | * Tags | 132 B | - | * Disk size | $(object_type_disk_usage all true) | - | * Commits | $(object_type_disk_usage commit true) | - | * Trees | $(object_type_disk_usage tree true) | - | * Blobs | $(object_type_disk_usage blob true) | - | * Tags | $(object_type_disk_usage tag) B | + | Repository structure | Value | + | ------------------------- | ---------- | + | * References | | + | * Count | 4 | + | * Branches | 1 | + | * Tags | 1 | + | * Remotes | 1 | + | * Others | 1 | + | | | + | * Reachable objects | | + | * Count | 3.02 k | + | * Commits | 1.01 k | + | * Trees | 1.01 k | + | * Blobs | 1.01 k | + | * Tags | 1 | + | * Inflated size | 16.03 MiB | + | * Commits | 217.92 KiB | + | * Trees | 15.81 MiB | + | * Blobs | 11.68 KiB | + | * Tags | 132 B | + | * Disk size | $(object_type_disk_usage all true) | + | * Commits | $(object_type_disk_usage commit true) | + | * Trees | $(object_type_disk_usage tree true) | + | * Blobs | $(object_type_disk_usage blob true) | + | * Tags | $(object_type_disk_usage tag) B | + | | | + | * Largest objects | | + | * Commits | | + | * Maximum size [1] | 223 B | + | * Maximum parents [2] | 1 | + | * Trees | | + | * Maximum size [3] | 32.29 KiB | + | * Maximum entries [4] | 1.01 k | + | * Blobs | | + | * Maximum size [5] | 13 B | + | * Tags | | + | * Maximum size [6] | 132 B | + + [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a + [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a + [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c + [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c + [5] 97d808e45116bf02103490294d3d46dad7a2ac62 + [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2 EOF git repo structure >out 2>err && @@ -138,6 +169,18 @@ test_expect_success SHA1 'lines and nul format' ' objects.trees.disk_size=$(object_type_disk_usage tree) objects.blobs.disk_size=$(object_type_disk_usage blob) objects.tags.disk_size=$(object_type_disk_usage tag) + objects.commits.max_size=221 + objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39 + objects.trees.max_size=1335 + objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5 + objects.blobs.max_size=11 + objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f + objects.tags.max_size=132 + objects.tags.max_size_oid=1ee0f2b16ea37d895dbe9dbd76cd2ac70446176c + objects.commits.max_parents=1 + objects.commits.max_parents_oid=de3508174b5c2ace6993da67cae9be9069e2df39 + objects.trees.max_entries=42 + objects.trees.max_entries_oid=09931deea9d81ec21300d3e13c74412f32eacec5 EOF git repo structure --format=lines >out 2>err && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index c58e505c43f9b9..e7829c2c4bfdc3 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1494,7 +1494,8 @@ test_expect_success 'refuse --edit-description on unborn branch for now' ' ' test_expect_success '--merged catches invalid object names' ' - test_must_fail git branch --merged 0000000000000000000000000000000000000000 + test_must_fail git branch --merged $ZERO_OID 2>err && + test_grep "must point to a commit" err ' test_expect_success '--list during rebase' ' diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index f0054b0a39c541..0bb366fdb8fce1 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -227,7 +227,8 @@ test_expect_success 'merge z into m (== y) with default ("manual") resolver => C # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cat <expect_notes_z @@ -375,8 +376,10 @@ EOF git notes merge --commit && notes_merge_files_gone && # Merge commit has pre-merge y and pre-merge z as parents - test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && - test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && + git rev-parse refs/notes/m^1 >actual && + test_cmp pre_merge_y actual && + git rev-parse refs/notes/m^2 >actual && + test_cmp pre_merge_z actual && # Merge commit mentions the notes refs merged git log -1 --format=%B refs/notes/m > merge_commit_msg && grep -q refs/notes/m merge_commit_msg && @@ -428,14 +431,16 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' test_expect_success 'abort notes merge' ' git notes merge --abort && notes_merge_files_gone && # m has not moved (still == y) - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" && + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -460,7 +465,8 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cat <expect_notes_m @@ -500,8 +506,10 @@ EOF git notes merge --commit && notes_merge_files_gone && # Merge commit has pre-merge y and pre-merge z as parents - test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && - test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && + git rev-parse refs/notes/m^1 >actual && + test_cmp pre_merge_y actual && + git rev-parse refs/notes/m^2 >actual && + test_cmp pre_merge_z actual && # Merge commit mentions the notes refs merged git log -1 --format=%B refs/notes/m > merge_commit_msg && grep -q refs/notes/m merge_commit_msg && @@ -539,7 +547,8 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cp expect_notes_w expect_notes_m @@ -548,7 +557,7 @@ cp expect_log_w expect_log_m test_expect_success 'reset notes ref m to somewhere else (w)' ' git update-ref refs/notes/m refs/notes/w && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" + test_cmp_rev refs/notes/m refs/notes/w ' test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' ' @@ -569,13 +578,15 @@ EOF test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha3 && test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha4 && # Refs are unchanged - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && - test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && - test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && + test_cmp_rev refs/notes/m refs/notes/w && + test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 && + test_cmp_rev ! refs/notes/m NOTES_MERGE_PARTIAL^1 && # Mention refs/notes/m, and its current and expected value in output test_grep -q "refs/notes/m" output && - test_grep -q "$(git rev-parse refs/notes/m)" output && - test_grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output && + oid=$(git rev-parse refs/notes/m) && + test_grep -q "$oid" output && + oid=$(git rev-parse NOTES_MERGE_PARTIAL^1) && + test_grep -q "$oid" output && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -587,7 +598,7 @@ test_expect_success 'resolve situation by aborting the notes merge' ' git notes merge --abort && notes_merge_files_gone && # m has not moved (still == w) - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && + test_cmp_rev refs/notes/m refs/notes/w && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -606,8 +617,8 @@ test_expect_success 'switch cwd before committing notes merge' ' test_must_fail git notes merge refs/notes/other && ( cd .git/NOTES_MERGE_WORKTREE && - echo "foo" > $(git rev-parse HEAD) && - echo "bar" >> $(git rev-parse HEAD) && + oid=$(git rev-parse HEAD) && + test_write_lines foo bar >"$oid" && git notes merge --commit ) && git notes show HEAD > actual_notes && diff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh new file mode 100755 index 00000000000000..8b4757956603db --- /dev/null +++ b/t/t3440-rebase-trailer.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# + +test_description='git rebase --trailer integration tests +We verify that --trailer works with the merge backend, +and that it is rejected early when the apply backend is requested.' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh # test_commit_message, helpers + +REVIEWED_BY_TRAILER="Reviewed-by: Dev " +SP=" " + +test_expect_success 'setup repo with a small history' ' + git commit --allow-empty -m "Initial empty commit" && + test_commit first file a && + test_commit second file && + git checkout -b conflict-branch first && + test_commit file-2 file-2 && + test_commit conflict file && + test_commit third file && + git checkout main +' + +test_expect_success 'apply backend is rejected with --trailer' ' + git checkout -B apply-backend third && + test_expect_code 128 \ + git rebase --apply --trailer "$REVIEWED_BY_TRAILER" HEAD^ 2>err && + test_grep "fatal: --trailer requires the merge backend" err +' + +test_expect_success 'reject empty --trailer argument' ' + git checkout -B empty-trailer third && + test_expect_code 128 git rebase --trailer "" HEAD^ 2>err && + test_grep "empty --trailer" err +' + +test_expect_success 'reject trailer with missing key before separator' ' + git checkout -B missing-key third && + test_expect_code 128 git rebase --trailer ": no-key" HEAD^ 2>err && + test_grep "missing key before separator" err +' + +test_expect_success 'allow trailer with missing value after separator' ' + git checkout -B missing-value third && + git rebase --trailer "Acked-by:" HEAD^ && + test_commit_message HEAD <<-EOF + third + + Acked-by:${SP} + EOF +' + +test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' ' + git checkout -B replace-policy third && + git -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \ + rebase --trailer "Bug: 123" --trailer "Bug: 456" HEAD^ && + test_commit_message HEAD <<-EOF + third + + Bug: 456 + EOF +' + +test_expect_success 'multiple Signed-off-by trailers all preserved' ' + git checkout -B multiple-signoff third && + git rebase --trailer "Signed-off-by: Dev A " \ + --trailer "Signed-off-by: Dev B " HEAD^ && + test_commit_message HEAD <<-EOF + third + + Signed-off-by: Dev A + Signed-off-by: Dev B + EOF +' + +test_expect_success 'rebase --trailer adds trailer after conflicts' ' + git checkout -B trailer-conflict third && + test_commit fourth file && + test_must_fail git rebase --trailer "$REVIEWED_BY_TRAILER" second && + git checkout --theirs file && + git add file && + git rebase --continue && + test_commit_message HEAD <<-EOF && + fourth + + $REVIEWED_BY_TRAILER + EOF + test_commit_message HEAD^ <<-EOF + third + + $REVIEWED_BY_TRAILER + EOF +' + +test_expect_success '--trailer handles fixup commands in todo list' ' + git checkout -B fixup-trailer third && + test_commit fixup-base base && + test_commit fixup-second second && + cat >todo <<-\EOF && + pick fixup-base fixup-base + fixup fixup-second fixup-second + EOF + ( + set_replace_editor todo && + git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2 + ) && + test_commit_message HEAD <<-EOF && + fixup-base + + $REVIEWED_BY_TRAILER + EOF + git reset --hard fixup-second && + cat >todo <<-\EOF && + pick fixup-base fixup-base + fixup -C fixup-second fixup-second + EOF + ( + set_replace_editor todo && + git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2 + ) && + test_commit_message HEAD <<-EOF + fixup-second + + $REVIEWED_BY_TRAILER + EOF +' + +test_expect_success 'rebase --root honors trailer..key' ' + git checkout -B root-trailer first && + git -c trailer.review.key=Reviewed-by rebase --root \ + --trailer=review="Dev " && + test_commit_message HEAD <<-EOF && + first + + Reviewed-by: Dev + EOF + test_commit_message HEAD^ <<-EOF + Initial empty commit + + Reviewed-by: Dev + EOF +' +test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index bcdb944017da4d..7c67bdf92267f6 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -380,6 +380,107 @@ test_expect_success 'filename limit applies only to basename' ' done ' +test_expect_success 'cover letter with subject, author and count' ' + rm -rf patches && + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches result test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --cover-letter \ + --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 && + grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expected_success 'cover letter with author and count' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches result test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --cover-letter \ + --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 && + grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expect_success 'cover letter shortlog' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches result test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --cover-letter --cover-letter-format=shortlog \ + -o patches HEAD~1 && + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expect_success 'cover letter no format' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches result test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --cover-letter -o patches HEAD~1 && + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expect_success 'cover letter config with count, subject and author' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config with count and author' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config commitlistformat set but no format' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + printf "\tcommitlistformat" >> .git/config && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config commitlistformat set to shortlog' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat shortlog && + git format-patch -o patches HEAD~2 && + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expect_success 'cover letter config commitlistformat not set' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + git config set format.coverletter true && + git format-patch -o patches HEAD~2 && + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + test_expect_success 'reroll count' ' rm -fr patches && git format-patch -o patches --cover-letter --reroll-count 4 main..side >list && diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index fa5de4500a4f50..3010913bb113e4 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -98,4 +98,96 @@ test_expect_success 'fallback to color.ui' ' grep "error: error" decoded ' +test_expect_success 'disallow (color) control sequences in sideband' ' + write_script .git/color-me-surprised <<-\EOF && + printf "error: Have you \\033[31mread\\033[m this?\\a\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./color-me-surprised && + test_commit need-at-least-one-commit && + + git clone --no-local . throw-away 2>stderr && + test_decode_color decoded && + test_grep RED decoded && + test_grep "\\^G" stderr && + tr -dc "\\007" actual && + test_must_be_empty actual && + + rm -rf throw-away && + git -c sideband.allowControlCharacters=false \ + clone --no-local . throw-away 2>stderr && + test_decode_color decoded && + test_grep ! RED decoded && + test_grep "\\^G" stderr && + + rm -rf throw-away && + git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && + test_decode_color decoded && + test_grep RED decoded && + tr -dc "\\007" actual && + test_file_not_empty actual +' + +test_decode_csi() { + awk '{ + while (match($0, /\033/) != 0) { + printf "%sCSI ", substr($0, 1, RSTART-1); + $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1); + } + print + }' +} + +test_expect_success 'control sequences in sideband allowed by default' ' + write_script .git/color-me-surprised <<-\EOF && + printf "error: \\033[31mcolor\\033[m\\033[Goverwrite\\033[Gerase\\033[K\\033?25l\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./color-me-surprised && + test_commit need-at-least-one-commit-at-least && + + rm -rf throw-away && + git clone --no-local . throw-away 2>stderr && + test_decode_color color-decoded && + test_decode_csi decoded && + test_grep ! "CSI \\[K" decoded && + test_grep ! "CSI \\[G" decoded && + test_grep "\\^\\[?25l" decoded && + + rm -rf throw-away && + git -c sideband.allowControlCharacters=erase,cursor,color \ + clone --no-local . throw-away 2>stderr && + test_decode_color color-decoded && + test_decode_csi decoded && + test_grep "RED" decoded && + test_grep "CSI \\[K" decoded && + test_grep "CSI \\[G" decoded && + test_grep ! "\\^\\[\\[K" decoded && + test_grep ! "\\^\\[\\[G" decoded +' + +test_expect_success 'allow all control sequences for a specific URL' ' + write_script .git/eraser <<-\EOF && + printf "error: Ohai!\\r\\033[K" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectsHook ./eraser && + test_commit one-more-please && + + rm -rf throw-away && + git clone --no-local . throw-away 2>stderr && + test_decode_color color-decoded && + test_decode_csi decoded && + test_grep ! "CSI \\[K" decoded && + test_grep "\\^\\[\\[K" decoded && + + rm -rf throw-away && + git -c "sideband.file://.allowControlCharacters=true" \ + clone --no-local "file://$PWD" throw-away 2>stderr && + test_decode_color color-decoded && + test_decode_csi decoded && + test_grep "CSI \\[K" decoded && + test_grep ! "\\^\\[\\[K" decoded +' + test_done diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 0387f35a326d74..39211ef989cc58 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -483,10 +483,6 @@ test_expect_success 'combine:... with non-encoded reserved chars' ' "must escape char in sub-filter-spec: .\~." ' -test_expect_success 'validate err msg for "combine:+"' ' - expect_invalid_filter_spec combine:tree:2+ "expected .tree:." -' - test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' ' git -C r3 rev-list --objects --filter="combine:tree:2+bl%6Fb:n%6fne" \ HEAD >actual && diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index a44eabf0d80fa8..14cbe9652779bc 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -30,13 +30,17 @@ export CVSROOT CVSWORK GIT_DIR rm -rf "$CVSROOT" "$CVSWORK" -cvs init && -test -d "$CVSROOT" && -cvs -Q co -d "$CVSWORK" . && -echo >empty && -git add empty && -git commit -q -a -m "Initial" 2>/dev/null || -exit 1 +if ! cvs init || ! test -d "$CVSROOT" || ! cvs -Q co -d "$CVSWORK" . +then + skip_all="cvs repository set-up fails" + test_done +fi + +test_expect_success 'git setup' ' + echo >empty && + git add empty && + git commit -q -a -m Initial +' check_entries () { # $1 == directory, $2 == expected @@ -303,7 +307,7 @@ test_expect_success 're-commit a removed filename which remains in CVS attic' ' git commit -m "Added attic_gremlin" && git cvsexportcommit -w "$CVSWORK" -c HEAD && (cd "$CVSWORK" && cvs -Q update -d) && - test -f "$CVSWORK/attic_gremlin" + test_path_is_file "$CVSWORK/attic_gremlin" ' # the state of the CVS sandbox may be indeterminate for ' space' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 2f9a597ec7f493..35e20b53511499 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2775,6 +2775,7 @@ test_expect_success PERL 'send-email' ' test_completion "git send-email --cov" <<-\EOF && --cover-from-description=Z --cover-letter Z + --cover-letter-format=Z EOF test_completion "git send-email --val" <<-\EOF && --validate Z diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c index 87e1f5c201f253..72ee20a06f3951 100644 --- a/t/unit-tests/test-lib.c +++ b/t/unit-tests/test-lib.c @@ -396,8 +396,23 @@ int check_uint_loc(const char *loc, const char *check, int ok, static void print_one_char(char ch, char quote) { if ((unsigned char)ch < 0x20u || ch == 0x7f) { - /* TODO: improve handling of \a, \b, \f ... */ - printf("\\%03o", (unsigned char)ch); + char esc; + switch (ch) { + case '\a': esc = 'a'; break; + case '\b': esc = 'b'; break; + case '\t': esc = 't'; break; + case '\n': esc = 'n'; break; + case '\v': esc = 'v'; break; + case '\f': esc = 'f'; break; + case '\r': esc = 'r'; break; + default: esc = 0; break; + } + if (esc) { + putc('\\', stdout); + putc(esc, stdout); + } else { + printf("\\%03o", (unsigned char)ch); + } } else { if (ch == '\\' || ch == quote) putc('\\', stdout); diff --git a/trailer.c b/trailer.c index 911a81ed993ec6..ca8abd18826bf2 100644 --- a/trailer.c +++ b/trailer.c @@ -7,8 +7,11 @@ #include "string-list.h" #include "run-command.h" #include "commit.h" +#include "strvec.h" #include "trailer.h" #include "list.h" +#include "tempfile.h" + /* * Copyright (c) 2013, 2014 Christian Couder */ @@ -772,6 +775,35 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head, free(cl_separators); } +int validate_trailer_args(const struct strvec *cli_args) +{ + char *cl_separators; + int ret = 0; + + trailer_config_init(); + + cl_separators = xstrfmt("=%s", separators); + + for (size_t i = 0; i < cli_args->nr; i++) { + const char *txt = cli_args->v[i]; + ssize_t separator_pos; + + if (!*txt) { + ret = error(_("empty --trailer argument")); + goto out; + } + separator_pos = find_separator(txt, cl_separators); + if (separator_pos == 0) { + ret = error(_("invalid trailer '%s': missing key before separator"), + txt); + goto out; + } + } +out: + free(cl_separators); + return ret; +} + static const char *next_line(const char *str) { const char *nl = strchrnul(str, '\n'); @@ -1224,14 +1256,146 @@ void trailer_iterator_release(struct trailer_iterator *iter) strbuf_release(&iter->key); } -int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) +struct tempfile *trailer_create_in_place_tempfile(const char *file) +{ + struct tempfile *tempfile = NULL; + struct stat st; + struct strbuf filename_template = STRBUF_INIT; + const char *tail; + + if (stat(file, &st)) { + error_errno(_("could not stat %s"), file); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + error(_("file %s is not a regular file"), file); + return NULL; + } + if (!(st.st_mode & S_IWUSR)) { + error(_("file %s is not writable by user"), file); + return NULL; + } + /* Create temporary file in the same directory as the original */ + tail = find_last_dir_sep(file); + if (tail) + strbuf_add(&filename_template, file, tail - file + 1); + strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); + + tempfile = mks_tempfile_m(filename_template.buf, st.st_mode); + + strbuf_release(&filename_template); + + return tempfile; +} + +int amend_strbuf_with_trailers(struct strbuf *buf, + const struct strvec *trailer_args) { - struct child_process run_trailer = CHILD_PROCESS_INIT; - - run_trailer.git_cmd = 1; - strvec_pushl(&run_trailer.args, "interpret-trailers", - "--in-place", "--no-divider", - path, NULL); - strvec_pushv(&run_trailer.args, trailer_args->v); - return run_command(&run_trailer); + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + LIST_HEAD(new_trailer_head); + struct strbuf out = STRBUF_INIT; + size_t i; + int ret = 0; + + opts.no_divider = 1; + + for (i = 0; i < trailer_args->nr; i++) { + const char *text = trailer_args->v[i]; + struct new_trailer_item *item; + + if (!*text) { + ret = error(_("empty --trailer argument")); + goto out; + } + item = xcalloc(1, sizeof(*item)); + item->text = xstrdup(text); + list_add_tail(&item->list, &new_trailer_head); + } + + process_trailers(&opts, &new_trailer_head, buf, &out); + + strbuf_swap(buf, &out); +out: + strbuf_release(&out); + free_trailers(&new_trailer_head); + + return ret; +} + +static int write_file_in_place(const char *path, const struct strbuf *buf) +{ + struct tempfile *tempfile = trailer_create_in_place_tempfile(path); + if (!tempfile) + return -1; + + if (write_in_full(tempfile->fd, buf->buf, buf->len) < 0) + return error_errno(_("could not write to temporary file")); + + if (rename_tempfile(&tempfile, path)) + return error_errno(_("could not rename temporary file to %s"), path); + + return 0; +} + +int amend_file_with_trailers(const char *path, + const struct strvec *trailer_args) +{ + struct strbuf buf = STRBUF_INIT; + int ret = 0; + + if (!trailer_args) + BUG("amend_file_with_trailers called with NULL trailer_args"); + if (!trailer_args->nr) + return 0; + + if (validate_trailer_args(trailer_args)) { + ret = -1; + goto out; + } + if (strbuf_read_file(&buf, path, 0) < 0) + ret = error_errno(_("could not read '%s'"), path); + else + amend_strbuf_with_trailers(&buf, trailer_args); + + if (!ret) + ret = write_file_in_place(path, &buf); +out: + strbuf_release(&buf); + return ret; +} + +void process_trailers(const struct process_trailer_options *opts, + struct list_head *new_trailer_head, + struct strbuf *input, struct strbuf *out) +{ + LIST_HEAD(head); + struct trailer_block *trailer_block; + + trailer_block = parse_trailers(opts, input->buf, &head); + + /* Print the lines before the trailer block */ + if (!opts->only_trailers) + strbuf_add(out, input->buf, trailer_block_start(trailer_block)); + + if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block)) + strbuf_addch(out, '\n'); + + if (!opts->only_input) { + LIST_HEAD(config_head); + LIST_HEAD(arg_head); + parse_trailers_from_config(&config_head); + parse_trailers_from_command_line_args(&arg_head, new_trailer_head); + list_splice(&config_head, &arg_head); + process_trailers_lists(&head, &arg_head); + } + + /* Print trailer block. */ + format_trailers(opts, &head, out); + free_trailers(&head); + + /* Print the lines after the trailer block as is. */ + if (!opts->only_trailers) + strbuf_add(out, input->buf + trailer_block_end(trailer_block), + input->len - trailer_block_end(trailer_block)); + trailer_block_release(trailer_block); } diff --git a/trailer.h b/trailer.h index 4740549586a8e5..b49338858c482a 100644 --- a/trailer.h +++ b/trailer.h @@ -68,6 +68,8 @@ void parse_trailers_from_config(struct list_head *config_head); void parse_trailers_from_command_line_args(struct list_head *arg_head, struct list_head *new_trailer_head); +int validate_trailer_args(const struct strvec *cli_args); + void process_trailers_lists(struct list_head *head, struct list_head *arg_head); @@ -196,10 +198,38 @@ int trailer_iterator_advance(struct trailer_iterator *iter); void trailer_iterator_release(struct trailer_iterator *iter); /* - * Augment a file to add trailers to it by running git-interpret-trailers. - * This calls run_command() and its return value is the same (i.e. 0 for - * success, various non-zero for other errors). See run-command.h. + * Append trailers specified in trailer_args to buf in-place. + * + * Each element of trailer_args should be in the same format as the value + * accepted by --trailer= (i.e., without the --trailer= prefix). + */ +int amend_strbuf_with_trailers(struct strbuf *buf, + const struct strvec *trailer_args); + +/* + * Augment a file by appending trailers specified in trailer_args. + * + * Each element of trailer_args should be in the same format as the value + * accepted by --trailer= (i.e., without the --trailer= prefix). + * + * Returns 0 on success or a non-zero error code on failure. */ int amend_file_with_trailers(const char *path, const struct strvec *trailer_args); +/* + * Create a tempfile ""git-interpret-trailers-XXXXXX" in the same + * directory as file. + */ +struct tempfile *trailer_create_in_place_tempfile(const char *file); + +/* + * Rewrite the contents of input by processing its trailer block according to + * opts and (optionally) appending trailers from new_trailer_head. + * + * The rewritten message is appended to out (callers should strbuf_reset() + * first if needed). + */ +void process_trailers(const struct process_trailer_options *opts, + struct list_head *new_trailer_head, + struct strbuf *input, struct strbuf *out); #endif /* TRAILER_H */ diff --git a/transport.c b/transport.c index 107f4fa5dce96a..259e9d7b701121 100644 --- a/transport.c +++ b/transport.c @@ -29,6 +29,7 @@ #include "object-name.h" #include "color.h" #include "bundle-uri.h" +#include "sideband.h" static enum git_colorbool transport_use_color = GIT_COLOR_UNKNOWN; static char transport_colors[][COLOR_MAXLEN] = { @@ -1246,6 +1247,8 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->hash_algo = &hash_algos[GIT_HASH_SHA1_LEGACY]; + sideband_apply_url_config(ret->url); + return ret; } diff --git a/worktree.c b/worktree.c index e9ff6e6ef2eb58..56732f8f3312bf 100644 --- a/worktree.c +++ b/worktree.c @@ -445,7 +445,7 @@ void update_worktree_location(struct worktree *wt, const char *path_, strbuf_realpath(&path, path_, 1); strbuf_addf(&dotgit, "%s/.git", path.buf); if (fspathcmp(wt->path, path.buf)) { - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); free(wt->path); wt->path = strbuf_detach(&path, NULL); @@ -673,7 +673,8 @@ static void repair_gitfile(struct worktree *wt, } } - if (err == READ_GITFILE_ERR_NOT_A_FILE) + if (err == READ_GITFILE_ERR_NOT_A_FILE || + err == READ_GITFILE_ERR_IS_A_DIR) fn(1, wt->path, _(".git is not a file"), cb_data); else if (err) repair = _(".git file broken"); @@ -684,7 +685,7 @@ static void repair_gitfile(struct worktree *wt, if (repair) { fn(0, wt->path, repair, cb_data); - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); } done: @@ -742,7 +743,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path if (!file_exists(dotgit.buf)) goto done; - write_worktree_linking_files(dotgit, gitdir, is_relative_path); + write_worktree_linking_files(dotgit.buf, gitdir.buf, is_relative_path); done: strbuf_release(&gitdir); strbuf_release(&dotgit); @@ -853,7 +854,8 @@ void repair_worktree_at_path(const char *path, strbuf_addstr(&backlink, dotgit_contents); strbuf_realpath_forgiving(&backlink, backlink.buf, 0); } - } else if (err == READ_GITFILE_ERR_NOT_A_FILE) { + } else if (err == READ_GITFILE_ERR_NOT_A_FILE || + err == READ_GITFILE_ERR_IS_A_DIR) { fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); goto done; } else if (err == READ_GITFILE_ERR_NOT_A_REPO) { @@ -913,7 +915,7 @@ void repair_worktree_at_path(const char *path, if (repair) { fn(0, gitdir.buf, repair, cb_data); - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); } done: free(dotgit_contents); @@ -1087,17 +1089,17 @@ int init_worktree_config(struct repository *r) return res; } -void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, +void write_worktree_linking_files(const char *dotgit, const char *gitdir, int use_relative_paths) { struct strbuf path = STRBUF_INIT; struct strbuf repo = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT; - strbuf_addbuf(&path, &dotgit); + strbuf_addstr(&path, dotgit); strbuf_strip_suffix(&path, "/.git"); strbuf_realpath(&path, path.buf, 1); - strbuf_addbuf(&repo, &gitdir); + strbuf_addstr(&repo, gitdir); strbuf_strip_suffix(&repo, "/gitdir"); strbuf_realpath(&repo, repo.buf, 1); @@ -1110,11 +1112,11 @@ void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, } if (use_relative_paths) { - write_file(gitdir.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + write_file(gitdir, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); + write_file(dotgit, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); } else { - write_file(gitdir.buf, "%s/.git", path.buf); - write_file(dotgit.buf, "gitdir: %s", repo.buf); + write_file(gitdir, "%s/.git", path.buf); + write_file(dotgit, "gitdir: %s", repo.buf); } strbuf_release(&path); diff --git a/worktree.h b/worktree.h index e450d1a3317e23..026ef303e81f5a 100644 --- a/worktree.h +++ b/worktree.h @@ -240,7 +240,7 @@ int init_worktree_config(struct repository *r); * dotgit: "/path/to/foo/.git" * gitdir: "/path/to/repo/worktrees/foo/gitdir" */ -void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, +void write_worktree_linking_files(const char *dotgit, const char *gitdir, int use_relative_paths); #endif diff --git a/wt-status.c b/wt-status.c index 543000311fd615..479ccc3304bc33 100644 --- a/wt-status.c +++ b/wt-status.c @@ -150,11 +150,11 @@ void wt_status_prepare(struct repository *r, struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = GIT_COLOR_UNKNOWN; s->relative_paths = 1; - s->branch = refs_resolve_refdup(get_main_ref_store(the_repository), + s->branch = refs_resolve_refdup(get_main_ref_store(r), "HEAD", 0, NULL, NULL); s->reference = "HEAD"; s->fp = stdout; - s->index_file = repo_get_index_file(the_repository); + s->index_file = repo_get_index_file(r); s->change.strdup_strings = 1; s->untracked.strdup_strings = 1; s->ignored.strdup_strings = 1; @@ -670,7 +670,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) repo_init_revisions(s->repo, &rev, NULL); memset(&opt, 0, sizeof(opt)); - opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference; + opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference; setup_revisions(0, NULL, &rev, &opt); rev.diffopt.flags.override_submodule_config = 1; @@ -1008,17 +1008,17 @@ static int stash_count_refs(const char *refname UNUSED, return 0; } -static int count_stash_entries(void) +static int count_stash_entries(struct repository *r) { int n = 0; - refs_for_each_reflog_ent(get_main_ref_store(the_repository), + refs_for_each_reflog_ent(get_main_ref_store(r), "refs/stash", stash_count_refs, &n); return n; } static void wt_longstatus_print_stash_summary(struct wt_status *s) { - int stash_count = count_stash_entries(); + int stash_count = count_stash_entries(s->repo); if (stash_count > 0) status_printf_ln(s, GIT_COLOR_NORMAL, @@ -1170,7 +1170,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s) rev.diffopt.ita_invisible_in_index = 1; memset(&opt, 0, sizeof(opt)); - opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference; + opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference; setup_revisions(0, NULL, &rev, &opt); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; @@ -1311,10 +1311,10 @@ static void show_am_in_progress(struct wt_status *s, wt_longstatus_print_trailer(s); } -static char *read_line_from_git_path(const char *filename) +static char *read_line_from_git_path(struct repository *r, const char *filename) { struct strbuf buf = STRBUF_INIT; - FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf, + FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf, "%s", filename), "r"); if (!fp) { @@ -1341,16 +1341,16 @@ static int split_commit_in_progress(struct wt_status *s) !s->branch || strcmp(s->branch, "HEAD")) return 0; - if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + if (refs_read_ref_full(get_main_ref_store(s->repo), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, &head_oid, &head_flags) || - refs_read_ref_full(get_main_ref_store(the_repository), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + refs_read_ref_full(get_main_ref_store(s->repo), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, &orig_head_oid, &orig_head_flags)) return 0; if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF) return 0; - rebase_amend = read_line_from_git_path("rebase-merge/amend"); - rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head"); + rebase_amend = read_line_from_git_path(s->repo, "rebase-merge/amend"); + rebase_orig_head = read_line_from_git_path(s->repo, "rebase-merge/orig-head"); if (!rebase_amend || !rebase_orig_head) ; /* fall through, no split in progress */ @@ -1374,7 +1374,7 @@ static int split_commit_in_progress(struct wt_status *s) * The function assumes that the line does not contain useless spaces * before or after the command. */ -static void abbrev_oid_in_line(struct strbuf *line) +static void abbrev_oid_in_line(struct repository *r, struct strbuf *line) { struct string_list split = STRING_LIST_INIT_DUP; struct object_id oid; @@ -1386,7 +1386,7 @@ static void abbrev_oid_in_line(struct strbuf *line) return; if ((2 <= string_list_split(&split, line->buf, " ", 2)) && - !repo_get_oid(the_repository, split.items[1].string, &oid)) { + !repo_get_oid(r, split.items[1].string, &oid)) { strbuf_reset(line); strbuf_addf(line, "%s ", split.items[0].string); strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV); @@ -1396,10 +1396,10 @@ static void abbrev_oid_in_line(struct strbuf *line) string_list_clear(&split, 0); } -static int read_rebase_todolist(const char *fname, struct string_list *lines) +static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines) { struct strbuf buf = STRBUF_INIT; - FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r"); + FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r"); int ret; if (!f) { @@ -1408,7 +1408,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) goto out; } die_errno("Could not open file %s for reading", - repo_git_path_replace(the_repository, &buf, "%s", fname)); + repo_git_path_replace(r, &buf, "%s", fname)); } while (!strbuf_getline_lf(&buf, f)) { if (starts_with(buf.buf, comment_line_str)) @@ -1416,7 +1416,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) strbuf_trim(&buf); if (!buf.len) continue; - abbrev_oid_in_line(&buf); + abbrev_oid_in_line(r, &buf); string_list_append(lines, buf.buf); } fclose(f); @@ -1437,8 +1437,8 @@ static void show_rebase_information(struct wt_status *s, struct string_list have_done = STRING_LIST_INIT_DUP; struct string_list yet_to_do = STRING_LIST_INIT_DUP; - read_rebase_todolist("rebase-merge/done", &have_done); - if (read_rebase_todolist("rebase-merge/git-rebase-todo", + read_rebase_todolist(s->repo, "rebase-merge/done", &have_done); + if (read_rebase_todolist(s->repo, "rebase-merge/git-rebase-todo", &yet_to_do)) status_printf_ln(s, color, _("git-rebase-todo is missing.")); @@ -1456,7 +1456,7 @@ static void show_rebase_information(struct wt_status *s, i++) status_printf_ln(s, color, " %s", have_done.items[i].string); if (have_done.nr > nr_lines_to_show && s->hints) { - char *path = repo_git_path(the_repository, "rebase-merge/done"); + char *path = repo_git_path(s->repo, "rebase-merge/done"); status_printf_ln(s, color, _(" (see more in file %s)"), path); free(path); @@ -1558,7 +1558,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s, else status_printf_ln(s, color, _("You are currently cherry-picking commit %s."), - repo_find_unique_abbrev(the_repository, &s->state.cherry_pick_head_oid, + repo_find_unique_abbrev(s->repo, &s->state.cherry_pick_head_oid, DEFAULT_ABBREV)); if (s->hints) { @@ -1588,7 +1588,7 @@ static void show_revert_in_progress(struct wt_status *s, else status_printf_ln(s, color, _("You are currently reverting commit %s."), - repo_find_unique_abbrev(the_repository, &s->state.revert_head_oid, + repo_find_unique_abbrev(s->repo, &s->state.revert_head_oid, DEFAULT_ABBREV)); if (s->hints) { if (has_unmerged(s)) @@ -1715,7 +1715,7 @@ static void wt_status_get_detached_from(struct repository *r, char *ref = NULL; strbuf_init(&cb.buf, 0); - if (refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository), "HEAD", grab_1st_switch, &cb) <= 0) { + if (refs_for_each_reflog_ent_reverse(get_main_ref_store(r), "HEAD", grab_1st_switch, &cb) <= 0) { strbuf_release(&cb.buf); return; } @@ -1848,10 +1848,10 @@ void wt_status_get_state(struct repository *r, if (!sequencer_get_last_command(r, &action)) { if (action == REPLAY_PICK && !state->cherry_pick_in_progress) { state->cherry_pick_in_progress = 1; - oidcpy(&state->cherry_pick_head_oid, null_oid(the_hash_algo)); + oidcpy(&state->cherry_pick_head_oid, null_oid(r->hash_algo)); } else if (action == REPLAY_REVERT && !state->revert_in_progress) { state->revert_in_progress = 1; - oidcpy(&state->revert_head_oid, null_oid(the_hash_algo)); + oidcpy(&state->revert_head_oid, null_oid(r->hash_algo)); } } if (get_detached_from) @@ -2134,7 +2134,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) upstream_is_gone = 1; } - short_base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), + short_base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo), base, 0); color_fprintf(s->fp, header_color, "..."); color_fprintf(s->fp, branch_color_remote, "%s", short_base); @@ -2268,7 +2268,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s) ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind, &base, 0, s->ahead_behind_flags); if (base) { - base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), + base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo), base, 0); fprintf(s->fp, "# branch.upstream %s%c", base, eol); free((char *)base); @@ -2294,7 +2294,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s) */ static void wt_porcelain_v2_print_stash(struct wt_status *s) { - int stash_count = count_stash_entries(); + int stash_count = count_stash_entries(s->repo); char eol = s->null_termination ? '\0' : '\n'; if (stash_count > 0) @@ -2665,7 +2665,7 @@ int has_uncommitted_changes(struct repository *r, * We have no head (or it's corrupt); use the empty tree, * which will complain if the index is non-empty. */ - struct tree *tree = lookup_tree(r, the_hash_algo->empty_tree); + struct tree *tree = lookup_tree(r, r->hash_algo->empty_tree); add_pending_object(&rev_info, &tree->object, ""); }