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/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-var.adoc b/Documentation/git-var.adoc index b606c2d649979f..697c10adedcca6 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -22,7 +22,7 @@ OPTIONS Display the logical variables. In addition, all the variables of the Git configuration file .git/config are listed as well. (However, the configuration variables listing functionality - is deprecated in favor of `git config -l`.) + is deprecated in favor of `git config list`.) EXAMPLES -------- diff --git a/Documentation/gitcvs-migration.adoc b/Documentation/gitcvs-migration.adoc index 1cd1283d0f817d..905d08cd5f991c 100644 --- a/Documentation/gitcvs-migration.adoc +++ b/Documentation/gitcvs-migration.adoc @@ -49,8 +49,7 @@ them first before running git pull. ================================ The 'pull' command knows where to get updates from because of certain configuration variables that were set by the first 'git clone' -command; see `git config -l` and the linkgit:git-config[1] man -page for details. +command; see the subcommand `list` in linkgit:git-config[1] for details. ================================ You can update the shared repository with your changes by first committing diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index f985cb4c474953..ac5102b64f99ec 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -659,7 +659,7 @@ use by the client, MUST indicate prerequisites (in any) with standard applicable. + The advertised URI may alternatively contain a plaintext file that `git -config --list` would accept (with the `--file` option). The key-value +config list` would accept (with the `--file` option). The key-value pairs in this list are in the `bundle.*` namespace (see linkgit:git-config[1]). diff --git a/Documentation/gittutorial.adoc b/Documentation/gittutorial.adoc index f89ad30cf652b4..519b8d8be2cc46 100644 --- a/Documentation/gittutorial.adoc +++ b/Documentation/gittutorial.adoc @@ -432,7 +432,7 @@ bob$ git config --get remote.origin.url ------------------------------------- (The complete configuration created by `git clone` is visible using -`git config -l`, and the linkgit:git-config[1] man page +`git config list`, and the linkgit:git-config[1] man page explains the meaning of each option.) Git also keeps a pristine copy of Alice's `master` branch under the diff --git a/Documentation/technical/api-trace2.adoc b/Documentation/technical/api-trace2.adoc index cf493dae03f3ac..918e517c2e6edb 100644 --- a/Documentation/technical/api-trace2.adoc +++ b/Documentation/technical/api-trace2.adoc @@ -1253,7 +1253,7 @@ it. $ git config --system color.ui never $ git config --global color.ui always $ git config --local color.ui auto -$ git config --list --show-scope | grep 'color.ui' +$ git config list --show-scope | grep 'color.ui' system color.ui=never global color.ui=always local color.ui=auto diff --git a/Documentation/user-manual.adoc b/Documentation/user-manual.adoc index 64009baf370231..5ec65cebe29dd7 100644 --- a/Documentation/user-manual.adoc +++ b/Documentation/user-manual.adoc @@ -2865,7 +2865,7 @@ stored in Git configuration variables, which you can see using linkgit:git-config[1]: ------------------------------------------------- -$ git config -l +$ git config list core.repositoryformatversion=0 core.filemode=true core.logallrefupdates=true diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index dada55884a0b06..878aa7f0ed9eb5 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1029,8 +1029,8 @@ static int read_proc_receive_report(struct packet_reader *reader, for (;;) { struct object_id old_oid, new_oid; - const char *head; - const char *refname; + char *head; + char *refname; char *p; enum packet_read_status status; @@ -1054,7 +1054,8 @@ static int read_proc_receive_report(struct packet_reader *reader, } *p++ = '\0'; if (!strcmp(head, "option")) { - const char *key, *val; + char *key; + const char *val; if (!hint || !(report || new_report)) { if (!once++) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 854d82ece368b3..8f63003709242e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -25,6 +25,7 @@ #include "oidset.h" #include "oidmap.h" #include "packfile.h" +#include "commit-reach.h" #include "quote.h" #include "strbuf.h" @@ -633,6 +634,61 @@ static int try_bitmap_disk_usage(struct rev_info *revs, return 0; } +/* + * If revs->maximal_only is set and no other walk modifiers are provided, + * run a faster computation to filter the independent commits and prepare + * them for output. Set revs->no_walk to prevent later walking. + * + * If this algorithm doesn't apply, then no changes are made to revs. + */ +static void prepare_maximal_independent(struct rev_info *revs) +{ + struct commit_list *c; + + if (!revs->maximal_only) + return; + + for (c = revs->commits; c; c = c->next) { + if (c->item->object.flags & UNINTERESTING) + return; + } + + if (revs->limited || + revs->topo_order || + revs->first_parent_only || + revs->reverse || + revs->max_count >= 0 || + revs->skip_count >= 0 || + revs->min_age != (timestamp_t)-1 || + revs->max_age != (timestamp_t)-1 || + revs->min_parents > 0 || + revs->max_parents >= 0 || + revs->prune_data.nr || + revs->count || + revs->left_right || + revs->boundary || + revs->tag_objects || + revs->tree_objects || + revs->blob_objects || + revs->filter.choice || + revs->reflog_info || + revs->diff || + revs->grep_filter.pattern_list || + revs->grep_filter.header_list || + revs->verbose_header || + revs->print_parents || + revs->edge_hint || + revs->unpacked || + revs->no_kept_objects || + revs->line_level_traverse) + return; + + reduce_heads_replace(&revs->commits); + + /* Modify 'revs' to only output this commit list. */ + revs->no_walk = 1; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix, @@ -875,6 +931,9 @@ int cmd_rev_list(int argc, if (prepare_revision_walk(&revs)) die("revision walk setup failed"); + + prepare_maximal_independent(&revs); + if (revs.tree_objects) mark_edges_uninteresting(&revs, show_edge, 0); diff --git a/convert.c b/convert.c index a34ec6ecdc057e..eae36c8a5936f4 100644 --- a/convert.c +++ b/convert.c @@ -1168,7 +1168,8 @@ static int ident_to_worktree(const char *src, size_t len, struct strbuf *buf, int ident) { struct object_id oid; - char *to_free = NULL, *dollar, *spc; + char *to_free = NULL; + const char *dollar, *spc; int cnt; if (!ident) diff --git a/git-compat-util.h b/git-compat-util.h index 4b4ea2498f13ef..ae1bdc90a4cd6a 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -335,11 +335,7 @@ static inline int is_path_owned_by_current_uid(const char *path, #endif #ifndef find_last_dir_sep -static inline char *git_find_last_dir_sep(const char *path) -{ - return strrchr(path, '/'); -} -#define find_last_dir_sep git_find_last_dir_sep +#define find_last_dir_sep(path) strrchr((path), '/') #endif #ifndef has_dir_sep @@ -467,6 +463,21 @@ void set_warn_routine(report_fn routine); report_fn get_warn_routine(void); void set_die_is_recursing_routine(int (*routine)(void)); +/* + * Check that an out-parameter is "at least as const as" a matching + * in-parameter. For example, skip_prefix() will return "out" that is a subset + * of "str". So: + * + * const str, const out: ok + * non-const str, const out: ok + * non-const str, non-const out: ok + * const str, non-const out: compile error + * + * See the skip_prefix macro below for an example of use. + */ +#define CONST_OUTPARAM(in, out) \ + ((const char **)(0 ? ((*(out) = (in)),(out)) : (out))) + /* * If the string "str" begins with the string found in "prefix", return true. * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in @@ -483,8 +494,10 @@ void set_die_is_recursing_routine(int (*routine)(void)); * [skip prefix if present, otherwise use whole string] * skip_prefix(name, "refs/heads/", &name); */ -static inline bool skip_prefix(const char *str, const char *prefix, - const char **out) +#define skip_prefix(str, prefix, out) \ + skip_prefix_impl((str), (prefix), CONST_OUTPARAM((str), (out))) +static inline bool skip_prefix_impl(const char *str, const char *prefix, + const char **out) { do { if (!*prefix) { @@ -889,8 +902,10 @@ static inline size_t xsize_t(off_t len) * is done via tolower(), so it is strictly ASCII (no multi-byte characters or * locale-specific conversions). */ -static inline bool skip_iprefix(const char *str, const char *prefix, - const char **out) +#define skip_iprefix(str, prefix, out) \ + skip_iprefix_impl((str), (prefix), CONST_OUTPARAM((str), (out))) +static inline bool skip_iprefix_impl(const char *str, const char *prefix, + const char **out) { do { if (!*prefix) { diff --git a/hex.c b/hex.c index 865a232167553d..bc756722ca623b 100644 --- a/hex.c +++ b/hex.c @@ -54,9 +54,9 @@ int get_oid_hex(const char *hex, struct object_id *oid) return get_oid_hex_algop(hex, oid, the_hash_algo); } -int parse_oid_hex_algop(const char *hex, struct object_id *oid, - const char **end, - const struct git_hash_algo *algop) +int parse_oid_hex_algop_impl(const char *hex, struct object_id *oid, + const char **end, + const struct git_hash_algo *algop) { int ret = get_oid_hex_algop(hex, oid, algop); if (!ret) diff --git a/hex.h b/hex.h index e9ccb54065c0bc..1e9a65d83a4f6b 100644 --- a/hex.h +++ b/hex.h @@ -40,8 +40,10 @@ char *oid_to_hex(const struct object_id *oid); /* same static buffer */ * other invalid character. end is only updated on success; otherwise, it is * unmodified. */ -int parse_oid_hex_algop(const char *hex, struct object_id *oid, const char **end, - const struct git_hash_algo *algo); +#define parse_oid_hex_algop(hex, oid, end, algo) \ + parse_oid_hex_algop_impl((hex), (oid), CONST_OUTPARAM((hex), (end)), (algo)) +int parse_oid_hex_algop_impl(const char *hex, struct object_id *oid, const char **end, + const struct git_hash_algo *algo); /* * These functions work like get_oid_hex and parse_oid_hex, but they will parse diff --git a/http-push.c b/http-push.c index 06c3acbb5d9a21..d143fe28455623 100644 --- a/http-push.c +++ b/http-push.c @@ -99,7 +99,7 @@ static struct object_list *objects; struct repo { char *url; - char *path; + const char *path; int path_len; int has_info_refs; int can_update_info_refs; diff --git a/http.c b/http.c index d8d016891b7974..67c9c6fc60673d 100644 --- a/http.c +++ b/http.c @@ -748,7 +748,7 @@ static int has_proxy_cert_password(void) static int redact_sensitive_header(struct strbuf *header, size_t offset) { int ret = 0; - const char *sensitive_header; + char *sensitive_header; if (trace_curl_redact && (skip_iprefix(header->buf + offset, "Authorization:", &sensitive_header) || @@ -765,7 +765,7 @@ static int redact_sensitive_header(struct strbuf *header, size_t offset) } else if (trace_curl_redact && skip_iprefix(header->buf + offset, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; - const char *cookie; + char *cookie; while (isspace(*sensitive_header)) sensitive_header++; diff --git a/pager.c b/pager.c index 5531fff50eb73f..35b210e0484f90 100644 --- a/pager.c +++ b/pager.c @@ -108,10 +108,11 @@ const char *git_pager(struct repository *r, int stdout_is_tty) static void setup_pager_env(struct strvec *env) { - const char **argv; + char **argv; int i; char *pager_env = xstrdup(PAGER_ENV); - int n = split_cmdline(pager_env, &argv); + /* split_cmdline splits in place, so we know the result is writable */ + int n = split_cmdline(pager_env, (const char ***)&argv); if (n < 0) die("malformed build-time PAGER_ENV: %s", diff --git a/pkt-line.h b/pkt-line.h index 3b33cc64f34dcc..e6cf85e34ee3c4 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -184,7 +184,7 @@ struct packet_reader { int pktlen; /* the last line read */ - const char *line; + char *line; /* indicates if a line has been peeked */ int line_peeked; diff --git a/pseudo-merge.c b/pseudo-merge.c index a2d5bd85f95ebf..ff18b6c364245e 100644 --- a/pseudo-merge.c +++ b/pseudo-merge.c @@ -638,14 +638,21 @@ static int pseudo_merge_commit_cmp(const void *va, const void *vb) return 0; } -static struct pseudo_merge_commit *find_pseudo_merge(const struct pseudo_merge_map *pm, - uint32_t pos) +static int find_pseudo_merge(const struct pseudo_merge_map *pm, uint32_t pos, + struct pseudo_merge_commit *out) { + const unsigned char *at; + if (!pm->commits_nr) - return NULL; + return 0; - return bsearch(&pos, pm->commits, pm->commits_nr, - PSEUDO_MERGE_COMMIT_RAWSZ, pseudo_merge_commit_cmp); + at = bsearch(&pos, pm->commits, pm->commits_nr, + PSEUDO_MERGE_COMMIT_RAWSZ, pseudo_merge_commit_cmp); + if (!at) + return 0; + + read_pseudo_merge_commit_at(out, at); + return 1; } int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, @@ -653,16 +660,15 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, struct commit *commit, uint32_t commit_pos) { struct pseudo_merge *merge; - struct pseudo_merge_commit *merge_commit; + struct pseudo_merge_commit merge_commit; int ret = 0; - merge_commit = find_pseudo_merge(pm, commit_pos); - if (!merge_commit) + if (!find_pseudo_merge(pm, commit_pos, &merge_commit)) return 0; - if (merge_commit->pseudo_merge_ofs & ((uint64_t)1<<63)) { + if (merge_commit.pseudo_merge_ofs & ((uint64_t)1<<63)) { struct pseudo_merge_commit_ext ext = { 0 }; - off_t ofs = merge_commit->pseudo_merge_ofs & ~((uint64_t)1<<63); + off_t ofs = merge_commit.pseudo_merge_ofs & ~((uint64_t)1<<63); uint32_t i; if (pseudo_merge_ext_at(pm, &ext, ofs) < -1) { @@ -673,11 +679,11 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, } for (i = 0; i < ext.nr; i++) { - if (nth_pseudo_merge_ext(pm, &ext, merge_commit, i) < 0) + if (nth_pseudo_merge_ext(pm, &ext, &merge_commit, i) < 0) return ret; merge = pseudo_merge_at(pm, &commit->object.oid, - merge_commit->pseudo_merge_ofs); + merge_commit.pseudo_merge_ofs); if (!merge) return ret; @@ -687,7 +693,7 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, } } else { merge = pseudo_merge_at(pm, &commit->object.oid, - merge_commit->pseudo_merge_ofs); + merge_commit.pseudo_merge_ofs); if (!merge) return ret; diff --git a/range-diff.c b/range-diff.c index 2712a9a107ab06..8e2dd2eb193eb9 100644 --- a/range-diff.c +++ b/range-diff.c @@ -88,7 +88,7 @@ static int read_patches(const char *range, struct string_list *list, line = contents.buf; size = contents.len; for (; size > 0; size -= len, line += len) { - const char *p; + char *p; char *eol; eol = memchr(line, '\n', size); diff --git a/refs/files-backend.c b/refs/files-backend.c index 0537a72b2af9e0..b3b0c25f84e503 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2190,7 +2190,7 @@ static int show_one_reflog_ent(struct files_ref_store *refs, char *email_end, *message; timestamp_t timestamp; int tz; - const char *p = sb->buf; + char *p = sb->buf; /* old SP new SP name SP time TAB msg LF */ if (!sb->len || sb->buf[sb->len - 1] != '\n' || diff --git a/run-command.c b/run-command.c index 574d5c40f0a1a5..c146a56532a139 100644 --- a/run-command.c +++ b/run-command.c @@ -604,11 +604,11 @@ static void trace_add_env(struct strbuf *dst, const char *const *deltaenv) /* Last one wins, see run-command.c:prep_childenv() for context */ for (e = deltaenv; e && *e; e++) { struct strbuf key = STRBUF_INIT; - char *equals = strchr(*e, '='); + const char *equals = strchr(*e, '='); if (equals) { strbuf_add(&key, *e, equals - *e); - string_list_insert(&envs, key.buf)->util = equals + 1; + string_list_insert(&envs, key.buf)->util = (void *)(equals + 1); } else { string_list_insert(&envs, *e)->util = NULL; } diff --git a/send-pack.c b/send-pack.c index 07ecfae4de92a2..b4361d5610dc91 100644 --- a/send-pack.c +++ b/send-pack.c @@ -175,8 +175,8 @@ static int receive_status(struct repository *r, ret = receive_unpack_status(reader); while (1) { struct object_id old_oid, new_oid; - const char *head; - const char *refname; + char *head; + char *refname; char *p; if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; @@ -190,7 +190,8 @@ static int receive_status(struct repository *r, *p++ = '\0'; if (!strcmp(head, "option")) { - const char *key, *val; + char *key; + const char *val; if (!hint || !(report || new_report)) { if (!once++) diff --git a/sideband.c b/sideband.c index 1ed6614eaf1baf..c6a3c00115b47d 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/t/perf/p6011-rev-list-maximal.sh b/t/perf/p6011-rev-list-maximal.sh new file mode 100755 index 00000000000000..e868e83ff847b3 --- /dev/null +++ b/t/perf/p6011-rev-list-maximal.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description='Test --maximal-only and --independent options' + +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup' ' + git for-each-ref --format="%(*objecttype) %(objecttype) %(objectname)" \ + "refs/heads/*" "refs/tags/*" | + sed -n -e "s/^commit commit //p" -e "s/^ commit //p" | + head -n 50 >commits && + git commit-graph write --reachable +' + +test_perf 'merge-base --independent' ' + git merge-base --independent $(cat commits) >/dev/null +' + +test_perf 'rev-list --maximal-only' ' + git rev-list --maximal-only $(cat commits) >/dev/null +' + +test_perf 'rev-list --maximal-only --since' ' + git rev-list --maximal-only --since=2000-01-01 $(cat commits) >/dev/null +' + +test_done 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/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index d0a2a866100d56..a95ba576fa24b2 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -263,4 +263,35 @@ test_expect_success 'rev-list --boundary incompatible with --maximal-only' ' test_grep "cannot be used together" err ' +test_expect_success 'rev-list --maximal-only and --pretty' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + oid1=$(git -C repo rev-parse HEAD) && + test_commit -C repo 2 && + oid2=$(git -C repo rev-parse HEAD) && + git -C repo checkout --detach HEAD~1 && + test_commit -C repo 3 && + oid3=$(git -C repo rev-parse HEAD) && + + cat >expect <<-EOF && + commit $oid3 + $oid3 + commit $oid2 + $oid2 + EOF + + git -C repo rev-list --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out && + test_cmp expect out && + + cat >expect <<-EOF && + $oid3 + $oid2 + EOF + + git -C repo log --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out && + test_cmp expect out +' + test_done diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index 2613075894282d..dc0421ed2f3726 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh @@ -837,4 +837,49 @@ test_expect_success 'rev-list --maximal-only (range)' ' --first-parent --exclude-first-parent-only ' +test_expect_success 'rev-list --maximal-only matches merge-base --independent' ' + # Mix of independent and dependent + git merge-base --independent \ + refs/heads/commit-5-2 \ + refs/heads/commit-3-2 \ + refs/heads/commit-2-5 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-5-2 \ + refs/heads/commit-3-2 \ + refs/heads/commit-2-5 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted && + + # All independent commits. + git merge-base --independent \ + refs/heads/commit-5-2 \ + refs/heads/commit-4-3 \ + refs/heads/commit-3-4 \ + refs/heads/commit-2-5 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-5-2 \ + refs/heads/commit-4-3 \ + refs/heads/commit-3-4 \ + refs/heads/commit-2-5 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted && + + # Only one independent. + git merge-base --independent \ + refs/heads/commit-1-1 \ + refs/heads/commit-4-2 \ + refs/heads/commit-4-4 \ + refs/heads/commit-8-4 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-1-1 \ + refs/heads/commit-4-2 \ + refs/heads/commit-4-4 \ + refs/heads/commit-8-4 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted +' + test_done diff --git a/transport-helper.c b/transport-helper.c index 570d7c6439569a..4e5d1d914fb12a 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -783,7 +783,8 @@ static int push_update_ref_status(struct strbuf *buf, if (starts_with(buf->buf, "option ")) { struct object_id old_oid, new_oid; - const char *key, *val; + char *key; + const char *val; char *p; if (!state->hint || !(state->report || state->new_report)) diff --git a/transport.c b/transport.c index e53936d87b641f..470d3258748524 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; }