diff options
106 files changed, 2275 insertions, 995 deletions
diff --git a/Documentation/RelNotes/2.14.2.txt b/Documentation/RelNotes/2.14.2.txt new file mode 100644 index 0000000000..5517afcf59 --- /dev/null +++ b/Documentation/RelNotes/2.14.2.txt @@ -0,0 +1,35 @@ +Git v2.14.2 Release Notes +========================= + +Fixes since v2.14.1 +------------------- + + * Because recent Git for Windows do come with a real msgfmt, the + build procedure for git-gui has been updated to use it instead of a + hand-rolled substitute. + + * "%C(color name)" in the pretty print format always produced ANSI + color escape codes, which was an early design mistake. They now + honor the configuration (e.g. "color.ui = never") and also tty-ness + of the output medium. + + * The http.{sslkey,sslCert} configuration variables are to be + interpreted as a pathname that honors "~[username]/" prefix, but + weren't, which has been fixed. + + * Numerous bugs in walking of reflogs via "log -g" and friends have + been fixed. + + * "git commit" when seeing an totally empty message said "you did not + edit the message", which is clearly wrong. The message has been + corrected. + + * When a directory is not readable, "gitweb" fails to build the + project list. Work this around by skipping such a directory. + + * A recently added test for the "credential-cache" helper revealed + that EOF detection done around the time the connection to the cache + daemon is torn down were flaky. This was fixed by reacting to + ECONNRESET and behaving as if we got an EOF. + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index cbce8ec638..5ca942ab5e 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -200,7 +200,10 @@ diff.algorithm:: + diff.wsErrorHighlight:: - A comma separated list of `old`, `new`, `context`, that - specifies how whitespace errors on lines are highlighted - with `color.diff.whitespace`. Can be overridden by the - command line option `--ws-error-highlight=<kind>` + Highlight whitespace errors in the `context`, `old` or `new` + lines of the diff. Multiple values are separated by comma, + `none` resets previous values, `default` reset the list to + `new` and `all` is a shorthand for `old,new,context`. The + whitespace errors are colored with `color.diff.whitespace`. + The command line option `--ws-error-highlight=<kind>` + overrides this setting. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 43d18a4c5c..56dedafcd4 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -300,15 +300,14 @@ ifndef::git-format-patch[] with --exit-code. --ws-error-highlight=<kind>:: - Highlight whitespace errors on lines specified by <kind> - in the color specified by `color.diff.whitespace`. <kind> - is a comma separated list of `old`, `new`, `context`. When - this option is not given, only whitespace errors in `new` - lines are highlighted. E.g. `--ws-error-highlight=new,old` - highlights whitespace errors on both deleted and added lines. - `all` can be used as a short-hand for `old,new,context`. - The `diff.wsErrorHighlight` configuration variable can be - used to specify the default behaviour. + Highlight whitespace errors in the `context`, `old` or `new` + lines of the diff. Multiple values are separated by comma, + `none` resets previous values, `default` reset the list to + `new` and `all` is a shorthand for `old,new,context`. When + this option is not given, and the configuration variable + `diff.wsErrorHighlight` is not set, only whitespace errors in + `new` lines are highlighted. The whitespace errors are colored + whith `color.diff.whitespace`. endif::git-format-patch[] diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 03e187a105..cc42c12832 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -156,8 +156,10 @@ HEAD:: otherwise. color:: - Change output color. Followed by `:<colorname>`, where names - are described in `color.branch.*`. + Change output color. Followed by `:<colorname>`, where color + names are described under Values in the "CONFIGURATION FILE" + section of linkgit:git-config[1]. For example, + `%(color:bold red)`. align:: Left-, middle-, or right-align the content between diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 1eb15afa1c..d97aad3439 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -205,6 +205,9 @@ it in the repository configuration as follows: signingKey = <gpg-keyid> ------------------------------------- +`pager.tag` is only respected when listing tags, i.e., when `-l` is +used or implied. The default is to use a pager. +See linkgit:git-config[1]. DISCUSSION ---------- diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 2a2d7e2a4d..c4f2be2542 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -425,8 +425,8 @@ packet: git< capability=clean packet: git< capability=smudge packet: git< 0000 ------------------------ -Supported filter capabilities in version 2 are "clean" and -"smudge". +Supported filter capabilities in version 2 are "clean", "smudge", +and "delay". Afterwards Git sends a list of "key=value" pairs terminated with a flush packet. The list will contain at least the filter command @@ -512,12 +512,73 @@ the protocol then Git will stop the filter process and restart it with the next file that needs to be processed. Depending on the `filter.<driver>.required` flag Git will interpret that as error. -After the filter has processed a blob it is expected to wait for -the next "key=value" list containing a command. Git will close +After the filter has processed a command it is expected to wait for +a "key=value" list containing the next command. Git will close the command pipe on exit. The filter is expected to detect EOF and exit gracefully on its own. Git will wait until the filter process has stopped. +Delay +^^^^^ + +If the filter supports the "delay" capability, then Git can send the +flag "can-delay" after the filter command and pathname. This flag +denotes that the filter can delay filtering the current blob (e.g. to +compensate network latencies) by responding with no content but with +the status "delayed" and a flush packet. +------------------------ +packet: git> command=smudge +packet: git> pathname=path/testfile.dat +packet: git> can-delay=1 +packet: git> 0000 +packet: git> CONTENT +packet: git> 0000 +packet: git< status=delayed +packet: git< 0000 +------------------------ + +If the filter supports the "delay" capability then it must support the +"list_available_blobs" command. If Git sends this command, then the +filter is expected to return a list of pathnames representing blobs +that have been delayed earlier and are now available. +The list must be terminated with a flush packet followed +by a "success" status that is also terminated with a flush packet. If +no blobs for the delayed paths are available, yet, then the filter is +expected to block the response until at least one blob becomes +available. The filter can tell Git that it has no more delayed blobs +by sending an empty list. As soon as the filter responds with an empty +list, Git stops asking. All blobs that Git has not received at this +point are considered missing and will result in an error. + +------------------------ +packet: git> command=list_available_blobs +packet: git> 0000 +packet: git< pathname=path/testfile.dat +packet: git< pathname=path/otherfile.dat +packet: git< 0000 +packet: git< status=success +packet: git< 0000 +------------------------ + +After Git received the pathnames, it will request the corresponding +blobs again. These requests contain a pathname and an empty content +section. The filter is expected to respond with the smudged content +in the usual way as explained above. +------------------------ +packet: git> command=smudge +packet: git> pathname=path/testfile.dat +packet: git> 0000 +packet: git> 0000 # empty content! +packet: git< status=success +packet: git< 0000 +packet: git< SMUDGED_CONTENT +packet: git< 0000 +packet: git< 0000 # empty list, keep "status=success" unchanged! +------------------------ + +Example +^^^^^^^ + A long running filter demo implementation can be found in `contrib/long-running-filter/example.pl` located in the Git core repository. If you develop your own long running filter diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 4d6dac5770..973d19606b 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -173,13 +173,17 @@ endif::git-rev-list[] - '%Cblue': switch color to blue - '%Creset': reset color - '%C(...)': color specification, as described under Values in the - "CONFIGURATION FILE" section of linkgit:git-config[1]; - adding `auto,` at the beginning (e.g. `%C(auto,red)`) will emit - color only when colors are enabled for log output (by `color.diff`, - `color.ui`, or `--color`, and respecting the `auto` settings of the - former if we are going to a terminal). `auto` alone (i.e. - `%C(auto)`) will turn on auto coloring on the next placeholders - until the color is switched again. + "CONFIGURATION FILE" section of linkgit:git-config[1]. + By default, colors are shown only when enabled for log output (by + `color.diff`, `color.ui`, or `--color`, and respecting the `auto` + settings of the former if we are going to a terminal). `%C(auto,...)` + is accepted as a historical synonym for the default (e.g., + `%C(auto,red)`). Specifying `%C(always,...) will show the colors + even when color is not otherwise enabled (though consider + just using `--color=always` to enable color for the whole output, + including this format and anything else git might color). `auto` + alone (i.e. `%C(auto)`) will turn on auto coloring on the next + placeholders until the color is switched again. - '%m': left (`<`), right (`>`) or boundary (`-`) mark - '%n': newline - '%%': a raw '%' diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt deleted file mode 100644 index 22a39b9299..0000000000 --- a/Documentation/technical/api-builtin.txt +++ /dev/null @@ -1,73 +0,0 @@ -builtin API -=========== - -Adding a new built-in ---------------------- - -There are 4 things to do to add a built-in command implementation to -Git: - -. Define the implementation of the built-in command `foo` with - signature: - - int cmd_foo(int argc, const char **argv, const char *prefix); - -. Add the external declaration for the function to `builtin.h`. - -. Add the command to the `commands[]` table defined in `git.c`. - The entry should look like: - - { "foo", cmd_foo, <options> }, -+ -where options is the bitwise-or of: - -`RUN_SETUP`:: - If there is not a Git directory to work on, abort. If there - is a work tree, chdir to the top of it if the command was - invoked in a subdirectory. If there is no work tree, no - chdir() is done. - -`RUN_SETUP_GENTLY`:: - If there is a Git directory, chdir as per RUN_SETUP, otherwise, - don't chdir anywhere. - -`USE_PAGER`:: - - If the standard output is connected to a tty, spawn a pager and - feed our output to it. - -`NEED_WORK_TREE`:: - - Make sure there is a work tree, i.e. the command cannot act - on bare repositories. - This only makes sense when `RUN_SETUP` is also set. - -. Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`. - -Additionally, if `foo` is a new command, there are 3 more things to do: - -. Add tests to `t/` directory. - -. Write documentation in `Documentation/git-foo.txt`. - -. Add an entry for `git-foo` to `command-list.txt`. - -. Add an entry for `/git-foo` to `.gitignore`. - - -How a built-in is called ------------------------- - -The implementation `cmd_foo()` takes three parameters, `argc`, `argv, -and `prefix`. The first two are similar to what `main()` of a -standalone command would be called with. - -When `RUN_SETUP` is specified in the `commands[]` table, and when you -were started from a subdirectory of the work tree, `cmd_foo()` is called -after chdir(2) to the top of the work tree, and `prefix` gets the path -to the subdirectory the command started from. This allows you to -convert a user-supplied pathname (typically relative to that directory) -to a pathname relative to the top of the work tree. - -The return value from `cmd_foo()` becomes the exit status of the -command. diff --git a/Documentation/technical/api-sub-process.txt b/Documentation/technical/api-sub-process.txt deleted file mode 100644 index 793508cf3e..0000000000 --- a/Documentation/technical/api-sub-process.txt +++ /dev/null @@ -1,59 +0,0 @@ -sub-process API -=============== - -The sub-process API makes it possible to run background sub-processes -for the entire lifetime of a Git invocation. If Git needs to communicate -with an external process multiple times, then this can reduces the process -invocation overhead. Git and the sub-process communicate through stdin and -stdout. - -The sub-processes are kept in a hashmap by command name and looked up -via the subprocess_find_entry function. If an existing instance can not -be found then a new process should be created and started. When the -parent git command terminates, all sub-processes are also terminated. - -This API is based on the run-command API. - -Data structures ---------------- - -* `struct subprocess_entry` - -The sub-process structure. Members should not be accessed directly. - -Types ------ - -'int(*subprocess_start_fn)(struct subprocess_entry *entry)':: - - User-supplied function to initialize the sub-process. This is - typically used to negotiate the interface version and capabilities. - - -Functions ---------- - -`cmd2process_cmp`:: - - Function to test two subprocess hashmap entries for equality. - -`subprocess_start`:: - - Start a subprocess and add it to the subprocess hashmap. - -`subprocess_stop`:: - - Kill a subprocess and remove it from the subprocess hashmap. - -`subprocess_find_entry`:: - - Find a subprocess in the subprocess hashmap. - -`subprocess_get_child_process`:: - - Get the underlying `struct child_process` from a subprocess. - -`subprocess_read_status`:: - - Helper function to read packets looking for the last "status=<foo>" - key/value pair. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2388ba13ca..1ab52e381e 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.14.1 +DEF_VER=v2.14.2 LF=' ' @@ -1 +1 @@ -Documentation/RelNotes/2.14.1.txt
\ No newline at end of file +Documentation/RelNotes/2.14.2.txt
\ No newline at end of file @@ -2839,13 +2839,10 @@ static void update_image(struct apply_state *state, img->line_allocated = img->line; } if (preimage_limit != postimage->nr) - memmove(img->line + applied_pos + postimage->nr, - img->line + applied_pos + preimage_limit, - (img->nr - (applied_pos + preimage_limit)) * - sizeof(*img->line)); - memcpy(img->line + applied_pos, - postimage->line, - postimage->nr * sizeof(*img->line)); + MOVE_ARRAY(img->line + applied_pos + postimage->nr, + img->line + applied_pos + preimage_limit, + img->nr - (applied_pos + preimage_limit)); + COPY_ARRAY(img->line + applied_pos, postimage->line, postimage->nr); if (!state->allow_overlap) for (i = 0; i < postimage->nr; i++) img->line[applied_pos + i].flag |= LINE_PATCHED; @@ -6,6 +6,94 @@ #include "cache.h" #include "commit.h" +/* + * builtin API + * =========== + * + * Adding a new built-in + * --------------------- + * + * There are 4 things to do to add a built-in command implementation to + * Git: + * + * . Define the implementation of the built-in command `foo` with + * signature: + * + * int cmd_foo(int argc, const char **argv, const char *prefix); + * + * . Add the external declaration for the function to `builtin.h`. + * + * . Add the command to the `commands[]` table defined in `git.c`. + * The entry should look like: + * + * { "foo", cmd_foo, <options> }, + * + * where options is the bitwise-or of: + * + * `RUN_SETUP`: + * If there is not a Git directory to work on, abort. If there + * is a work tree, chdir to the top of it if the command was + * invoked in a subdirectory. If there is no work tree, no + * chdir() is done. + * + * `RUN_SETUP_GENTLY`: + * If there is a Git directory, chdir as per RUN_SETUP, otherwise, + * don't chdir anywhere. + * + * `USE_PAGER`: + * + * If the standard output is connected to a tty, spawn a pager and + * feed our output to it. + * + * `NEED_WORK_TREE`: + * + * Make sure there is a work tree, i.e. the command cannot act + * on bare repositories. + * This only makes sense when `RUN_SETUP` is also set. + * + * `SUPPORT_SUPER_PREFIX`: + * + * The built-in supports `--super-prefix`. + * + * `DELAY_PAGER_CONFIG`: + * + * If RUN_SETUP or RUN_SETUP_GENTLY is set, git.c normally handles + * the `pager.<cmd>`-configuration. If this flag is used, git.c + * will skip that step, instead allowing the built-in to make a + * more informed decision, e.g., by ignoring `pager.<cmd>` for + * certain subcommands. + * + * . Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`. + * + * Additionally, if `foo` is a new command, there are 4 more things to do: + * + * . Add tests to `t/` directory. + * + * . Write documentation in `Documentation/git-foo.txt`. + * + * . Add an entry for `git-foo` to `command-list.txt`. + * + * . Add an entry for `/git-foo` to `.gitignore`. + * + * + * How a built-in is called + * ------------------------ + * + * The implementation `cmd_foo()` takes three parameters, `argc`, `argv, + * and `prefix`. The first two are similar to what `main()` of a + * standalone command would be called with. + * + * When `RUN_SETUP` is specified in the `commands[]` table, and when you + * were started from a subdirectory of the work tree, `cmd_foo()` is called + * after chdir(2) to the top of the work tree, and `prefix` gets the path + * to the subdirectory the command started from. This allows you to + * convert a user-supplied pathname (typically relative to that directory) + * to a pathname relative to the top of the work tree. + * + * The return value from `cmd_foo()` becomes the exit status of the + * command. + */ + #define DEFAULT_MERGE_LOG_LEN 20 extern const char git_usage_string[]; @@ -25,6 +113,18 @@ struct fmt_merge_msg_opts { extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct fmt_merge_msg_opts *); +/** + * If a built-in has DELAY_PAGER_CONFIG set, the built-in should call this early + * when it wishes to respect the `pager.foo`-config. The `cmd` is the name of + * the built-in, e.g., "foo". If a paging-choice has already been setup, this + * does nothing. The default in `def` should be 0 for "pager off", 1 for "pager + * on" or -1 for "punt". + * + * You should most likely use a default of 0 or 1. "Punt" (-1) could be useful + * to be able to fall back to some historical compatibility name. + */ +extern void setup_auto_pager(const char *cmd, int def); + extern int is_builtin(const char *s); extern int cmd_add(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c index c973bd96dc..73f542bec5 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -431,6 +431,14 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "utf8", 1); state->utf8 = !strcmp(sb.buf, "t"); + if (file_exists(am_path(state, "rerere-autoupdate"))) { + read_state_file(&sb, state, "rerere-autoupdate", 1); + state->allow_rerere_autoupdate = strcmp(sb.buf, "t") ? + RERERE_NOAUTOUPDATE : RERERE_AUTOUPDATE; + } else { + state->allow_rerere_autoupdate = 0; + } + read_state_file(&sb, state, "keep", 1); if (!strcmp(sb.buf, "t")) state->keep = KEEP_TRUE; @@ -1003,6 +1011,10 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_state_bool(state, "sign", state->signoff); write_state_bool(state, "utf8", state->utf8); + if (state->allow_rerere_autoupdate) + write_state_bool(state, "rerere-autoupdate", + state->allow_rerere_autoupdate == RERERE_AUTOUPDATE); + switch (state->keep) { case KEEP_FALSE: str = "f"; @@ -1181,34 +1193,10 @@ static void NORETURN die_user_resolve(const struct am_state *state) */ static void am_append_signoff(struct am_state *state) { - char *cp; - struct strbuf mine = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len); - - /* our sign-off */ - strbuf_addf(&mine, "\n%s%s\n", - sign_off_header, - fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - - /* Does sb end with it already? */ - if (mine.len < sb.len && - !strcmp(mine.buf, sb.buf + sb.len - mine.len)) - goto exit; /* no need to duplicate */ - - /* Does it have any Signed-off-by: in the text */ - for (cp = sb.buf; - cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL; - cp = strchr(cp, '\n')) { - if (sb.buf == cp || cp[-1] == '\n') - break; - } - - strbuf_addstr(&sb, mine.buf + !!cp); -exit: - strbuf_release(&mine); + append_signoff(&sb, 0, 0); state->msg = strbuf_detach(&sb, &state->msg_len); } diff --git a/builtin/branch.c b/builtin/branch.c index 8a0595e115..16d391b407 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -92,7 +92,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); return color_parse(value, branch_colors[slot]); } - return git_color_default_config(var, value, cb); + return git_default_config(var, value, cb); } static const char *branch_get_color(enum color_branch ix) @@ -383,7 +383,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r return strbuf_detach(&fmt, NULL); } -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) { int i; struct ref_array array; @@ -407,14 +407,17 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - if (!format) - format = to_free = build_format(filter, maxwidth, remote_prefix); - verify_ref_format(format); + if (!format->format) + format->format = to_free = build_format(filter, maxwidth, remote_prefix); + format->use_color = branch_use_color; + + if (verify_ref_format(format)) + die(_("unable to parse format string")); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { - format_ref_array_item(array.items[i], format, 0, &out); + format_ref_array_item(array.items[i], format, &out); if (column_active(colopts)) { assert(!filter->verbose && "--column and --verbose are incompatible"); /* format to a string_list to let print_columns() do its job */ @@ -549,7 +552,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct ref_filter filter; int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - const char *format = NULL; + struct ref_format format = REF_FORMAT_INIT; struct option options[] = { OPT_GROUP(N_("Generic options")), @@ -593,7 +596,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) N_("print only branches of the object"), 0, parse_opt_object_name }, OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), }; @@ -667,7 +670,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); sorting->ignore_case = icase; - print_ref_list(&filter, sorting, format); + print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index 9661e1bcba..2d75ac66c7 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -358,6 +358,8 @@ static int checkout_paths(const struct checkout_opts *opts, state.force = 1; state.refresh_cache = 1; state.istate = &the_index; + + enable_delayed_checkout(&state); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -372,6 +374,7 @@ static int checkout_paths(const struct checkout_opts *opts, pos = skip_same_name(ce, pos) - 1; } } + errs |= finish_delayed_checkout(&state); if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); diff --git a/builtin/clean.c b/builtin/clean.c index 057fc97fe4..c1bafda5b6 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -125,8 +125,7 @@ static int git_clean_config(const char *var, const char *value, void *cb) return 0; } - /* inspect the color.ui config variable and others */ - return git_color_default_config(var, value, cb); + return git_default_config(var, value, cb); } static const char *clean_get_color(enum color_clean ix) diff --git a/builtin/clone.c b/builtin/clone.c index 08b5cc433c..f7e17d2295 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -768,6 +768,9 @@ static int checkout(int submodule_progress) if (submodule_progress) argv_array_push(&args, "--progress"); + if (option_verbosity < 0) + argv_array_push(&args, "--quiet"); + err = run_command_v_opt(args.argv, RUN_GIT_CMD); argv_array_clear(&args); } diff --git a/builtin/commit.c b/builtin/commit.c index 8e93802511..1a0da71a43 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1739,17 +1739,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (verbose || /* Truncate the message just before the diff, if any. */ cleanup_mode == CLEANUP_SCISSORS) strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len)); - if (cleanup_mode != CLEANUP_NONE) strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (template_untouched(&sb) && !allow_empty_message) { + + if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); + fprintf(stderr, _("Aborting commit due to empty commit message.\n")); exit(1); } - if (message_is_empty(&sb) && !allow_empty_message) { + if (template_untouched(&sb) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, _("Aborting commit due to empty commit message.\n")); + fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); exit(1); } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 10cbb43416..e99b5ddbf9 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -408,7 +408,8 @@ static void shortlog(const char *name, } static void fmt_merge_msg_title(struct strbuf *out, - const char *current_branch) { + const char *current_branch) +{ int i = 0; char *sep = ""; diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 52be99cbac..5d7c921a77 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -17,25 +17,25 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { int i; - const char *format = "%(objectname) %(objecttype)\t%(refname)"; struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - int maxcount = 0, quote_style = 0, icase = 0; + int maxcount = 0, icase = 0; struct ref_array array; struct ref_filter filter; + struct ref_format format = REF_FORMAT_INIT; struct option opts[] = { - OPT_BIT('s', "shell", "e_style, + OPT_BIT('s', "shell", &format.quote_style, N_("quote placeholders suitably for shells"), QUOTE_SHELL), - OPT_BIT('p', "perl", "e_style, + OPT_BIT('p', "perl", &format.quote_style, N_("quote placeholders suitably for perl"), QUOTE_PERL), - OPT_BIT(0 , "python", "e_style, + OPT_BIT(0 , "python", &format.quote_style, N_("quote placeholders suitably for python"), QUOTE_PYTHON), - OPT_BIT(0 , "tcl", "e_style, + OPT_BIT(0 , "tcl", &format.quote_style, N_("quote placeholders suitably for Tcl"), QUOTE_TCL), OPT_GROUP(""), OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), N_("field name to sort on"), &parse_opt_ref_sorting), OPT_CALLBACK(0, "points-at", &filter.points_at, @@ -52,16 +52,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) memset(&array, 0, sizeof(array)); memset(&filter, 0, sizeof(filter)); + format.format = "%(objectname) %(objecttype)\t%(refname)"; + + git_config(git_default_config, NULL); + parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); if (maxcount < 0) { error("invalid --count argument: `%d'", maxcount); usage_with_options(for_each_ref_usage, opts); } - if (HAS_MULTI_BITS(quote_style)) { + if (HAS_MULTI_BITS(format.quote_style)) { error("more than one quoting style?"); usage_with_options(for_each_ref_usage, opts); } - if (verify_ref_format(format)) + if (verify_ref_format(&format)) usage_with_options(for_each_ref_usage, opts); if (!sorting) @@ -69,9 +73,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) sorting->ignore_case = icase; filter.ignore_case = icase; - /* for warn_ambiguous_refs */ - git_config(git_default_config, NULL); - filter.name_patterns = argv; filter.match_as_path = 1; filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); @@ -80,7 +81,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!maxcount || array.nr < maxcount) maxcount = array.nr; for (i = 0; i < maxcount; i++) - show_ref_array_item(array.items[i], format, quote_style); + show_ref_array_item(array.items[i], &format); ref_array_clear(&array); return 0; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 99dea7adf6..d18244ab54 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -19,6 +19,8 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 #define HAS_OBJ 0x0004 +/* This flag is set if something points to this object. */ +#define USED 0x0008 static int show_root; static int show_tags; @@ -168,18 +170,7 @@ static void mark_object_reachable(struct object *obj) static int traverse_one_object(struct object *obj) { - int result; - struct tree *tree = NULL; - - if (obj->type == OBJ_TREE) { - tree = (struct tree *)obj; - if (parse_tree(tree) < 0) - return 1; /* error already displayed */ - } - result = fsck_walk(obj, obj, &fsck_walk_options); - if (tree) - free_tree_buffer(tree); - return result; + return fsck_walk(obj, obj, &fsck_walk_options); } static int traverse_reachable(void) @@ -206,7 +197,7 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio { if (!obj) return 1; - obj->used = 1; + obj->flags |= USED; return 0; } @@ -255,7 +246,7 @@ static void check_unreachable_object(struct object *obj) } /* - * "!used" means that nothing at all points to it, including + * "!USED" means that nothing at all points to it, including * other unreachable objects. In other words, it's the "tip" * of some set of unreachable objects, usually a commit that * got dropped. @@ -266,7 +257,7 @@ static void check_unreachable_object(struct object *obj) * deleted a branch by mistake, this is a prime candidate to * start looking at, for example. */ - if (!obj->used) { + if (!(obj->flags & USED)) { if (show_dangling) printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); @@ -335,6 +326,8 @@ static void check_connectivity(void) static int fsck_obj(struct object *obj) { + int err; + if (obj->flags & SEEN) return 0; obj->flags |= SEEN; @@ -345,20 +338,13 @@ static int fsck_obj(struct object *obj) if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); - if (fsck_object(obj, NULL, 0, &fsck_obj_options)) - return -1; - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - - free_tree_buffer(item); - } + err = fsck_object(obj, NULL, 0, &fsck_obj_options); + if (err) + goto out; if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; - free_commit_buffer(commit); - if (!commit->parents && show_root) printf("root %s\n", describe_object(&commit->object)); } @@ -374,7 +360,12 @@ static int fsck_obj(struct object *obj) } } - return 0; +out: + if (obj->type == OBJ_TREE) + free_tree_buffer((struct tree *)obj); + if (obj->type == OBJ_COMMIT) + free_commit_buffer((struct commit *)obj); + return err; } static int fsck_obj_buffer(const struct object_id *oid, enum object_type type, @@ -390,7 +381,8 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type, errors_found |= ERROR_OBJECT; return error("%s: object corrupt or missing", oid_to_hex(oid)); } - obj->flags = HAS_OBJ; + obj->flags &= ~(REACHABLE | SEEN); + obj->flags |= HAS_OBJ; return fsck_obj(obj); } @@ -408,7 +400,7 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, add_decoration(fsck_walk_options.object_names, obj, xstrfmt("%s@{%"PRItime"}", refname, timestamp)); - obj->used = 1; + obj->flags |= USED; mark_object_reachable(obj); } else { error("%s: invalid reflog entry %s", refname, oid_to_hex(oid)); @@ -456,7 +448,7 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, errors_found |= ERROR_REFS; } default_refs++; - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(refname)); @@ -524,7 +516,8 @@ static int fsck_loose(const struct object_id *oid, const char *path, void *data) return 0; /* keep checking other objects */ } - obj->flags = HAS_OBJ; + obj->flags &= ~(REACHABLE | SEEN); + obj->flags |= HAS_OBJ; if (fsck_obj(obj)) errors_found |= ERROR_OBJECT; return 0; @@ -606,7 +599,7 @@ static int fsck_cache_tree(struct cache_tree *it) errors_found |= ERROR_REFS; return 1; } - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(":")); @@ -667,7 +660,7 @@ static struct option fsck_opts[] = { int cmd_fsck(int argc, const char **argv, const char *prefix) { - int i, heads; + int i; struct alternate_object_database *alt; errors_found = 0; @@ -735,7 +728,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } } - heads = 0; for (i = 0; i < argc; i++) { const char *arg = argv[i]; unsigned char sha1[20]; @@ -748,12 +740,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; } - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(arg)); mark_object_reachable(obj); - heads++; continue; } error("invalid parameter: expected sha1, got '%s'", arg); @@ -785,7 +776,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (!blob) continue; obj = &blob->object; - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, diff --git a/builtin/grep.c b/builtin/grep.c index 7e79eb1a75..42ff87065a 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -284,7 +284,7 @@ static int wait_all(void) static int grep_cmd_config(const char *var, const char *value, void *cb) { int st = grep_config(var, value, cb); - if (git_color_default_config(var, value, cb) < 0) + if (git_default_config(var, value, cb) < 0) st = -1; if (!strcmp(var, "grep.threads")) { diff --git a/builtin/log.c b/builtin/log.c index c6362cf92e..5ffc380bd7 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -372,11 +372,14 @@ static int cmd_log_walk(struct rev_info *rev) */ rev->max_count++; if (!rev->reflog_info) { - /* we allow cycles in reflog ancestry */ + /* + * We may show a given commit multiple times when + * walking the reflogs. + */ free_commit_buffer(commit); + free_commit_list(commit->parents); + commit->parents = NULL; } - free_commit_list(commit->parents); - commit->parents = NULL; if (saved_nrl < rev->diffopt.needed_rename_limit) saved_nrl = rev->diffopt.needed_rename_limit; if (rev->diffopt.degraded_cc_to_c) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index b8514a0029..c6126eae55 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -362,7 +362,7 @@ static void prune_index(struct index_state *istate, int pos; unsigned int first, last; - if (!prefix) + if (!prefix || !istate->cache_nr) return; pos = index_name_pos(istate, prefix, prefixlen); if (pos < 0) @@ -378,8 +378,7 @@ static void prune_index(struct index_state *istate, } last = next; } - memmove(istate->cache, istate->cache + pos, - (last - pos) * sizeof(struct cache_entry *)); + MOVE_ARRAY(istate->cache, istate->cache + pos, last - pos); istate->cache_nr = last - pos; } diff --git a/builtin/merge.c b/builtin/merge.c index 900bafdb45..d5797b8fe7 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -537,7 +537,7 @@ static void parse_branch_merge_options(char *bmo) die(_("Bad branch.%s.mergeoptions string: %s"), branch, split_cmdline_strerror(argc)); REALLOC_ARRAY(argv, argc + 2); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + MOVE_ARRAY(argv + 1, argv, argc + 1); argc++; argv[0] = "branch.*.mergeoptions"; parse_options(argc, argv, NULL, builtin_merge_options, diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f4a8441fe9..c753e9237a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1289,7 +1289,7 @@ static int done_pbase_path_pos(unsigned hash) static int check_pbase_path(unsigned hash) { - int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); + int pos = done_pbase_path_pos(hash); if (0 <= pos) return 1; pos = -pos - 1; @@ -1298,9 +1298,8 @@ static int check_pbase_path(unsigned hash) done_pbase_paths_alloc); done_pbase_paths_num++; if (pos < done_pbase_paths_num) - memmove(done_pbase_paths + pos + 1, - done_pbase_paths + pos, - (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); + MOVE_ARRAY(done_pbase_paths + pos + 1, done_pbase_paths + pos, + done_pbase_paths_num - pos - 1); done_pbase_paths[pos] = hash; return 0; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 95d84d5cda..95b4128250 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -11,6 +11,7 @@ #include "graph.h" #include "bisect.h" #include "progress.h" +#include "reflog-walk.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -122,6 +123,7 @@ static void show_commit(struct commit *commit, void *data) ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; ctx.output_encoding = get_log_output_encoding(); + ctx.color = revs->diffopt.use_color; pretty_print_commit(&ctx, commit, &buf); if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) @@ -348,9 +350,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) /* Only --header was specified */ revs.commit_format = CMIT_FMT_RAW; - if ((!revs.commits && + if ((!revs.commits && reflog_walk_empty(revs.reflog_info) && (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) && - !revs.pending.nr)) || + !revs.pending.nr) && + !revs.rev_input_given) || revs.diff) usage(rev_list_usage); diff --git a/builtin/revert.c b/builtin/revert.c index 16028b9ea8..b9d927eb09 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -155,6 +155,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) "--strategy-option", opts->xopts ? 1 : 0, "-x", opts->record_origin, "--ff", opts->allow_ff, + "--rerere-autoupdate", opts->allow_rerere_auto == RERERE_AUTOUPDATE, + "--no-rerere-autoupdate", opts->allow_rerere_auto == RERERE_NOAUTOUPDATE, NULL); } diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 7073a3eb97..28f245c8cc 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -554,7 +554,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) return 0; } - return git_color_default_config(var, value, cb); + return git_default_config(var, value, cb); } static int omit_in_dense(struct commit *commit, struct commit **rev, int n) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 6abdad3294..895555c93a 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -930,7 +930,7 @@ static int update_clone_task_finished(int result, const struct cache_entry *ce; struct submodule_update_clone *suc = suc_cb; - int *idxP = *(int**)idx_task_cb; + int *idxP = idx_task_cb; int idx = *idxP; free(idxP); @@ -1108,9 +1108,28 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, static int push_check(int argc, const char **argv, const char *prefix) { struct remote *remote; + const char *superproject_head; + char *head; + int detached_head = 0; + struct object_id head_oid; - if (argc < 2) - die("submodule--helper push-check requires at least 1 argument"); + if (argc < 3) + die("submodule--helper push-check requires at least 2 arguments"); + + /* + * superproject's resolved head ref. + * if HEAD then the superproject is in a detached head state, otherwise + * it will be the resolved head ref. + */ + superproject_head = argv[1]; + argv++; + argc--; + /* Get the submodule's head ref and determine if it is detached */ + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + if (!strcmp(head, "HEAD")) + detached_head = 1; /* * The remote must be configured. @@ -1133,18 +1152,30 @@ static int push_check(int argc, const char **argv, const char *prefix) if (rs->pattern || rs->matching) continue; - /* - * LHS must match a single ref - * NEEDSWORK: add logic to special case 'HEAD' once - * working with submodules in a detached head state - * ceases to be the norm. - */ - if (count_refspec_match(rs->src, local_refs, NULL) != 1) + /* LHS must match a single ref */ + switch (count_refspec_match(rs->src, local_refs, NULL)) { + case 1: + break; + case 0: + /* + * If LHS matches 'HEAD' then we need to ensure + * that it matches the same named branch + * checked out in the superproject. + */ + if (!strcmp(rs->src, "HEAD")) { + if (!detached_head && + !strcmp(head, superproject_head)) + break; + die("HEAD does not match the named branch in the superproject"); + } + default: die("src refspec '%s' must name a ref", rs->src); + } } free_refspec(refspec_nr, refspec); } + free(head); return 0; } diff --git a/builtin/tag.c b/builtin/tag.c index 01154ea8dc..7a70d5a9bb 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -32,7 +32,8 @@ static const char * const git_tag_usage[] = { static unsigned int colopts; static int force_sign_annotate; -static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, + struct ref_format *format) { struct ref_array array; char *to_free = NULL; @@ -43,23 +44,24 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (filter->lines == -1) filter->lines = 0; - if (!format) { + if (!format->format) { if (filter->lines) { to_free = xstrfmt("%s %%(contents:lines=%d)", "%(align:15)%(refname:lstrip=2)%(end)", filter->lines); - format = to_free; + format->format = to_free; } else - format = "%(refname:lstrip=2)"; + format->format = "%(refname:lstrip=2)"; } - verify_ref_format(format); + if (verify_ref_format(format)) + die(_("unable to parse format string")); filter->with_commit_tag_algo = 1; filter_refs(&array, filter, FILTER_REFS_TAGS); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) - show_ref_array_item(array.items[i], format, 0); + show_ref_array_item(array.items[i], format); ref_array_clear(&array); free(to_free); @@ -105,17 +107,17 @@ static int verify_tag(const char *name, const char *ref, const struct object_id *oid, const void *cb_data) { int flags; - const char *fmt_pretty = cb_data; + const struct ref_format *format = cb_data; flags = GPG_VERIFY_VERBOSE; - if (fmt_pretty) + if (format->format) flags = GPG_VERIFY_OMIT_STATUS; if (gpg_verify_tag(oid->hash, name, flags)) return -1; - if (fmt_pretty) - pretty_print_ref(name, oid->hash, fmt_pretty); + if (format->format) + pretty_print_ref(name, oid->hash, format); return 0; } @@ -134,30 +136,6 @@ static const char tag_template_nocleanup[] = "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); -/* Parse arg given and add it the ref_sorting array */ -static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail) -{ - struct ref_sorting *s; - int len; - - s = xcalloc(1, sizeof(*s)); - s->next = *sorting_tail; - *sorting_tail = s; - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - if (skip_prefix(arg, "version:", &arg) || - skip_prefix(arg, "v:", &arg)) - s->version = 1; - - len = strlen(arg); - s->atom = parse_ref_filter_atom(arg, arg+len); - - return 0; -} - static int git_tag_config(const char *var, const char *value, void *cb) { int status; @@ -166,7 +144,7 @@ static int git_tag_config(const char *var, const char *value, void *cb) if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_sorting_string(value, sorting_tail); + parse_ref_sorting(sorting_tail, value); return 0; } @@ -392,7 +370,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct strbuf err = STRBUF_INIT; struct ref_filter filter; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - const char *format = NULL; + struct ref_format format = REF_FORMAT_INIT; int icase = 0; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), @@ -431,7 +409,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, parse_opt_object_name, (intptr_t) "HEAD" }, - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_STRING( 0 , "format", &format.format, N_("format"), + N_("format to use for the output")), OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END() }; @@ -461,6 +440,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) cmdmode = 'l'; } + if (cmdmode == 'l') + setup_auto_pager("tag", 1); + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); @@ -483,7 +465,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) run_column_filter(colopts, &copts); } filter.name_patterns = argv; - ret = list_tags(&filter, sorting, format); + ret = list_tags(&filter, sorting, &format); if (column_active(colopts)) stop_column_filter(); return ret; @@ -501,9 +483,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag, NULL); if (cmdmode == 'v') { - if (format) - verify_ref_format(format); - return for_each_tag_name(argv, verify_tag, format); + if (format.format && verify_ref_format(&format)) + usage_with_options(git_tag_usage, options); + return for_each_tag_name(argv, verify_tag, &format); } if (msg.given || msgfile) { diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index f9a5f7535a..87d73e856a 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -32,11 +32,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; - char *fmt_pretty = NULL; + struct ref_format format = REF_FORMAT_INIT; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), - OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")), + OPT_STRING(0, "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -50,8 +50,10 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; - if (fmt_pretty) { - verify_ref_format(fmt_pretty); + if (format.format) { + if (verify_ref_format(&format)) + usage_with_options(verify_tag_usage, + verify_tag_options); flags |= GPG_VERIFY_OMIT_STATUS; } @@ -68,8 +70,8 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) continue; } - if (fmt_pretty) - pretty_print_ref(name, sha1, fmt_pretty); + if (format.format) + pretty_print_ref(name, sha1, &format); } return had_error; } diff --git a/cache-tree.c b/cache-tree.c index ec23d8c03d..2440d1dc89 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -131,9 +131,8 @@ static int do_invalidate_path(struct cache_tree *it, const char *path) * move 4 and 5 up one place (2 entries) * 2 = 6 - 3 - 1 = subtree_nr - pos - 1 */ - memmove(it->down+pos, it->down+pos+1, - sizeof(struct cache_tree_sub *) * - (it->subtree_nr - pos - 1)); + MOVE_ARRAY(it->down + pos, it->down + pos + 1, + it->subtree_nr - pos - 1); it->subtree_nr--; } return 1; @@ -1500,6 +1500,7 @@ struct checkout { struct index_state *istate; const char *base_dir; int base_dir_len; + struct delayed_checkout *delayed_checkout; unsigned force:1, quiet:1, not_new:1, @@ -1509,6 +1510,8 @@ struct checkout { #define TEMPORARY_FILENAME_LENGTH 25 extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); +extern void enable_delayed_checkout(struct checkout *state); +extern int finish_delayed_checkout(struct checkout *state); struct cache_def { struct strbuf path; @@ -361,14 +361,6 @@ int git_color_config(const char *var, const char *value, void *cb) return 0; } -int git_color_default_config(const char *var, const char *value, void *cb) -{ - if (git_color_config(var, value, cb) < 0) - return -1; - - return git_default_config(var, value, cb); -} - void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb) { if (*color) @@ -223,9 +223,8 @@ int unregister_shallow(const struct object_id *oid) if (pos < 0) return -1; if (pos + 1 < commit_graft_nr) - memmove(commit_graft + pos, commit_graft + pos + 1, - sizeof(struct commit_graft *) - * (commit_graft_nr - pos - 1)); + MOVE_ARRAY(commit_graft + pos, commit_graft + pos + 1, + commit_graft_nr - pos - 1); commit_graft_nr--; return 0; } diff --git a/compat/bswap.h b/compat/bswap.h index d47c003544..7d063e9e40 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -162,19 +162,29 @@ static inline uint64_t git_bswap64(uint64_t x) #else -#define get_be16(p) ( \ - (*((unsigned char *)(p) + 0) << 8) | \ - (*((unsigned char *)(p) + 1) << 0) ) -#define get_be32(p) ( \ - (*((unsigned char *)(p) + 0) << 24) | \ - (*((unsigned char *)(p) + 1) << 16) | \ - (*((unsigned char *)(p) + 2) << 8) | \ - (*((unsigned char *)(p) + 3) << 0) ) -#define put_be32(p, v) do { \ - unsigned int __v = (v); \ - *((unsigned char *)(p) + 0) = __v >> 24; \ - *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) +static inline uint16_t get_be16(const void *ptr) +{ + const unsigned char *p = ptr; + return (uint16_t)p[0] << 8 | + (uint16_t)p[1] << 0; +} + +static inline uint32_t get_be32(const void *ptr) +{ + const unsigned char *p = ptr; + return (uint32_t)p[0] << 24 | + (uint32_t)p[1] << 16 | + (uint32_t)p[2] << 8 | + (uint32_t)p[3] << 0; +} + +static inline void put_be32(void *ptr, uint32_t value) +{ + unsigned char *p = ptr; + p[0] = value >> 24; + p[1] = value >> 16; + p[2] = value >> 8; + p[3] = value >> 0; +} #endif diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c index 6c7c9b6053..161978d720 100644 --- a/compat/win32/syslog.c +++ b/compat/win32/syslog.c @@ -43,8 +43,10 @@ void syslog(int priority, const char *fmt, ...) va_end(ap); while ((pos = strstr(str, "%1")) != NULL) { + char *oldstr = str; str = realloc(str, st_add(++str_len, 1)); if (!str) { + free(oldstr); warning_errno("realloc failed"); return; } @@ -16,6 +16,7 @@ #include "string-list.h" #include "utf8.h" #include "dir.h" +#include "color.h" struct config_source { struct config_source *prev; @@ -1350,6 +1351,9 @@ int git_default_config(const char *var, const char *value, void *dummy) if (starts_with(var, "advice.")) return git_default_advice_config(var, value); + if (git_color_config(var, value, dummy) < 0) + return -1; + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 4ba98b7eaf..c61d1ca8dc 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -27,6 +27,23 @@ expression n; @@ type T; +T *dst; +T *src; +expression n; +@@ +( +- memmove(dst, src, (n) * sizeof(*dst)); ++ MOVE_ARRAY(dst, src, n); +| +- memmove(dst, src, (n) * sizeof(*src)); ++ MOVE_ARRAY(dst, src, n); +| +- memmove(dst, src, (n) * sizeof(T)); ++ MOVE_ARRAY(dst, src, n); +) + +@@ +type T; T *ptr; expression n; @@ @@ -501,6 +501,7 @@ static int apply_single_file_filter(const char *path, const char *src, size_t le #define CAP_CLEAN (1u<<0) #define CAP_SMUDGE (1u<<1) +#define CAP_DELAY (1u<<2) struct cmd2process { struct subprocess_entry subprocess; /* must be the first member! */ @@ -512,69 +513,49 @@ static struct hashmap subprocess_map; static int start_multi_file_filter_fn(struct subprocess_entry *subprocess) { - int err; + static int versions[] = {2, 0}; + static struct subprocess_capability capabilities[] = { + { "clean", CAP_CLEAN }, + { "smudge", CAP_SMUDGE }, + { "delay", CAP_DELAY }, + { NULL, 0 } + }; struct cmd2process *entry = (struct cmd2process *)subprocess; - struct string_list cap_list = STRING_LIST_INIT_NODUP; - char *cap_buf; - const char *cap_name; - struct child_process *process = &subprocess->process; - const char *cmd = subprocess->cmd; - - sigchain_push(SIGPIPE, SIG_IGN); - - err = packet_writel(process->in, "git-filter-client", "version=2", NULL); - if (err) - goto done; - - err = strcmp(packet_read_line(process->out, NULL), "git-filter-server"); - if (err) { - error("external filter '%s' does not support filter protocol version 2", cmd); - goto done; - } - err = strcmp(packet_read_line(process->out, NULL), "version=2"); - if (err) - goto done; - err = packet_read_line(process->out, NULL) != NULL; - if (err) - goto done; - - err = packet_writel(process->in, "capability=clean", "capability=smudge", NULL); - - for (;;) { - cap_buf = packet_read_line(process->out, NULL); - if (!cap_buf) - break; - string_list_split_in_place(&cap_list, cap_buf, '=', 1); - - if (cap_list.nr != 2 || strcmp(cap_list.items[0].string, "capability")) - continue; - - cap_name = cap_list.items[1].string; - if (!strcmp(cap_name, "clean")) { - entry->supported_capabilities |= CAP_CLEAN; - } else if (!strcmp(cap_name, "smudge")) { - entry->supported_capabilities |= CAP_SMUDGE; - } else { - warning( - "external filter '%s' requested unsupported filter capability '%s'", - cmd, cap_name - ); - } + return subprocess_handshake(subprocess, "git-filter", versions, NULL, + capabilities, + &entry->supported_capabilities); +} - string_list_clear(&cap_list, 0); +static void handle_filter_error(const struct strbuf *filter_status, + struct cmd2process *entry, + const unsigned int wanted_capability) { + if (!strcmp(filter_status->buf, "error")) + ; /* The filter signaled a problem with the file. */ + else if (!strcmp(filter_status->buf, "abort") && wanted_capability) { + /* + * The filter signaled a permanent problem. Don't try to filter + * files with the same command for the lifetime of the current + * Git process. + */ + entry->supported_capabilities &= ~wanted_capability; + } else { + /* + * Something went wrong with the protocol filter. + * Force shutdown and restart if another blob requires filtering. + */ + error("external filter '%s' failed", entry->subprocess.cmd); + subprocess_stop(&subprocess_map, &entry->subprocess); + free(entry); } - -done: - sigchain_pop(SIGPIPE); - - return err; } static int apply_multi_file_filter(const char *path, const char *src, size_t len, int fd, struct strbuf *dst, const char *cmd, - const unsigned int wanted_capability) + const unsigned int wanted_capability, + struct delayed_checkout *dco) { int err; + int can_delay = 0; struct cmd2process *entry; struct child_process *process; struct strbuf nbuf = STRBUF_INIT; @@ -603,12 +584,12 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len } process = &entry->subprocess.process; - if (!(wanted_capability & entry->supported_capabilities)) + if (!(entry->supported_capabilities & wanted_capability)) return 0; - if (CAP_CLEAN & wanted_capability) + if (wanted_capability & CAP_CLEAN) filter_type = "clean"; - else if (CAP_SMUDGE & wanted_capability) + else if (wanted_capability & CAP_SMUDGE) filter_type = "smudge"; else die("unexpected filter type"); @@ -630,6 +611,14 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len if (err) goto done; + if ((entry->supported_capabilities & CAP_DELAY) && + dco && dco->state == CE_CAN_DELAY) { + can_delay = 1; + err = packet_write_fmt_gently(process->in, "can-delay=1\n"); + if (err) + goto done; + } + err = packet_flush_gently(process->in); if (err) goto done; @@ -645,14 +634,73 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len if (err) goto done; - err = strcmp(filter_status.buf, "success"); + if (can_delay && !strcmp(filter_status.buf, "delayed")) { + string_list_insert(&dco->filters, cmd); + string_list_insert(&dco->paths, path); + } else { + /* The filter got the blob and wants to send us a response. */ + err = strcmp(filter_status.buf, "success"); + if (err) + goto done; + + err = read_packetized_to_strbuf(process->out, &nbuf) < 0; + if (err) + goto done; + + err = subprocess_read_status(process->out, &filter_status); + if (err) + goto done; + + err = strcmp(filter_status.buf, "success"); + } + +done: + sigchain_pop(SIGPIPE); + + if (err) + handle_filter_error(&filter_status, entry, wanted_capability); + else + strbuf_swap(dst, &nbuf); + strbuf_release(&nbuf); + return !err; +} + + +int async_query_available_blobs(const char *cmd, struct string_list *available_paths) +{ + int err; + char *line; + struct cmd2process *entry; + struct child_process *process; + struct strbuf filter_status = STRBUF_INIT; + + assert(subprocess_map_initialized); + entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd); + if (!entry) { + error("external filter '%s' is not available anymore although " + "not all paths have been filtered", cmd); + return 0; + } + process = &entry->subprocess.process; + sigchain_push(SIGPIPE, SIG_IGN); + + err = packet_write_fmt_gently( + process->in, "command=list_available_blobs\n"); if (err) goto done; - err = read_packetized_to_strbuf(process->out, &nbuf) < 0; + err = packet_flush_gently(process->in); if (err) goto done; + while ((line = packet_read_line(process->out, NULL))) { + const char *path; + if (skip_prefix(line, "pathname=", &path)) + string_list_insert(available_paths, xstrdup(path)); + else + ; /* ignore unknown keys */ + } + err = subprocess_read_status(process->out, &filter_status); if (err) goto done; @@ -662,29 +710,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len done: sigchain_pop(SIGPIPE); - if (err) { - if (!strcmp(filter_status.buf, "error")) { - /* The filter signaled a problem with the file. */ - } else if (!strcmp(filter_status.buf, "abort")) { - /* - * The filter signaled a permanent problem. Don't try to filter - * files with the same command for the lifetime of the current - * Git process. - */ - entry->supported_capabilities &= ~wanted_capability; - } else { - /* - * Something went wrong with the protocol filter. - * Force shutdown and restart if another blob requires filtering. - */ - error("external filter '%s' failed", cmd); - subprocess_stop(&subprocess_map, &entry->subprocess); - free(entry); - } - } else { - strbuf_swap(dst, &nbuf); - } - strbuf_release(&nbuf); + if (err) + handle_filter_error(&filter_status, entry, 0); return !err; } @@ -699,7 +726,8 @@ static struct convert_driver { static int apply_filter(const char *path, const char *src, size_t len, int fd, struct strbuf *dst, struct convert_driver *drv, - const unsigned int wanted_capability) + const unsigned int wanted_capability, + struct delayed_checkout *dco) { const char *cmd = NULL; @@ -709,15 +737,16 @@ static int apply_filter(const char *path, const char *src, size_t len, if (!dst) return 1; - if ((CAP_CLEAN & wanted_capability) && !drv->process && drv->clean) + if ((wanted_capability & CAP_CLEAN) && !drv->process && drv->clean) cmd = drv->clean; - else if ((CAP_SMUDGE & wanted_capability) && !drv->process && drv->smudge) + else if ((wanted_capability & CAP_SMUDGE) && !drv->process && drv->smudge) cmd = drv->smudge; if (cmd && *cmd) return apply_single_file_filter(path, src, len, fd, dst, cmd); else if (drv->process && *drv->process) - return apply_multi_file_filter(path, src, len, fd, dst, drv->process, wanted_capability); + return apply_multi_file_filter(path, src, len, fd, dst, + drv->process, wanted_capability, dco); return 0; } @@ -1058,7 +1087,7 @@ int would_convert_to_git_filter_fd(const char *path) if (!ca.drv->required) return 0; - return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN); + return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL); } const char *get_convert_attr_ascii(const char *path) @@ -1096,7 +1125,7 @@ int convert_to_git(const struct index_state *istate, convert_attrs(&ca, path); - ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN); + ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL); if (!ret && ca.drv && ca.drv->required) die("%s: clean filter '%s' failed", path, ca.drv->name); @@ -1124,7 +1153,7 @@ void convert_to_git_filter_fd(const struct index_state *istate, assert(ca.drv); assert(ca.drv->clean || ca.drv->process); - if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN)) + if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL)) die("%s: clean filter '%s' failed", path, ca.drv->name); crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, checksafe); @@ -1133,7 +1162,7 @@ void convert_to_git_filter_fd(const struct index_state *istate, static int convert_to_working_tree_internal(const char *path, const char *src, size_t len, struct strbuf *dst, - int normalizing) + int normalizing, struct delayed_checkout *dco) { int ret = 0, ret_filter = 0; struct conv_attrs ca; @@ -1158,22 +1187,30 @@ static int convert_to_working_tree_internal(const char *path, const char *src, } } - ret_filter = apply_filter(path, src, len, -1, dst, ca.drv, CAP_SMUDGE); + ret_filter = apply_filter( + path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco); if (!ret_filter && ca.drv && ca.drv->required) die("%s: smudge filter %s failed", path, ca.drv->name); return ret | ret_filter; } +int async_convert_to_working_tree(const char *path, const char *src, + size_t len, struct strbuf *dst, + void *dco) +{ + return convert_to_working_tree_internal(path, src, len, dst, 0, dco); +} + int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst) { - return convert_to_working_tree_internal(path, src, len, dst, 0); + return convert_to_working_tree_internal(path, src, len, dst, 0, NULL); } int renormalize_buffer(const struct index_state *istate, const char *path, const char *src, size_t len, struct strbuf *dst) { - int ret = convert_to_working_tree_internal(path, src, len, dst, 1); + int ret = convert_to_working_tree_internal(path, src, len, dst, 1, NULL); if (ret) { src = dst->buf; len = dst->len; @@ -4,6 +4,8 @@ #ifndef CONVERT_H #define CONVERT_H +#include "string-list.h" + struct index_state; enum safe_crlf { @@ -35,6 +37,26 @@ enum eol { #endif }; +enum ce_delay_state { + CE_NO_DELAY = 0, + CE_CAN_DELAY = 1, + CE_RETRY = 2 +}; + +struct delayed_checkout { + /* + * State of the currently processed cache entry. If the state is + * CE_CAN_DELAY, then the filter can delay the current cache entry. + * If the state is CE_RETRY, then this signals the filter that the + * cache entry was requested before. + */ + enum ce_delay_state state; + /* List of filter drivers that signaled delayed blobs. */ + struct string_list filters; + /* List of delayed blobs identified by their path. */ + struct string_list paths; +}; + extern enum eol core_eol; extern const char *get_cached_convert_stats_ascii(const struct index_state *istate, const char *path); @@ -47,6 +69,10 @@ extern int convert_to_git(const struct index_state *istate, struct strbuf *dst, enum safe_crlf checksafe); extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); +extern int async_convert_to_working_tree(const char *path, const char *src, + size_t len, struct strbuf *dst, + void *dco); +extern int async_query_available_blobs(const char *cmd, struct string_list *available_paths); extern int renormalize_buffer(const struct index_state *istate, const char *path, const char *src, size_t len, struct strbuf *dst); diff --git a/credential-cache.c b/credential-cache.c index 91550bfb0b..1cccc3a0b9 100644 --- a/credential-cache.c +++ b/credential-cache.c @@ -25,7 +25,7 @@ static int send_request(const char *socket, const struct strbuf *out) int r; r = read_in_full(fd, in, sizeof(in)); - if (r == 0) + if (r == 0 || (r < 0 && errno == ECONNRESET)) break; if (r < 0) die_errno("read error from cache daemon"); @@ -299,9 +299,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return 0; } - if (git_color_config(var, value, cb) < 0) - return -1; - return git_diff_basic_config(var, value, cb); } @@ -2398,7 +2398,8 @@ struct ondisk_untracked_cache { char exclude_per_dir[FLEX_ARRAY]; }; -#define ouc_size(len) (offsetof(struct ondisk_untracked_cache, exclude_per_dir) + len + 1) +#define ouc_offset(x) offsetof(struct ondisk_untracked_cache, x) +#define ouc_size(len) (ouc_offset(exclude_per_dir) + len + 1) struct write_data { int index; /* number of written untracked_cache_dir */ @@ -2560,17 +2561,18 @@ struct read_data { const unsigned char *end; }; -static void stat_data_from_disk(struct stat_data *to, const struct stat_data *from) +static void stat_data_from_disk(struct stat_data *to, const unsigned char *data) { - to->sd_ctime.sec = get_be32(&from->sd_ctime.sec); - to->sd_ctime.nsec = get_be32(&from->sd_ctime.nsec); - to->sd_mtime.sec = get_be32(&from->sd_mtime.sec); - to->sd_mtime.nsec = get_be32(&from->sd_mtime.nsec); - to->sd_dev = get_be32(&from->sd_dev); - to->sd_ino = get_be32(&from->sd_ino); - to->sd_uid = get_be32(&from->sd_uid); - to->sd_gid = get_be32(&from->sd_gid); - to->sd_size = get_be32(&from->sd_size); + memcpy(to, data, sizeof(*to)); + to->sd_ctime.sec = ntohl(to->sd_ctime.sec); + to->sd_ctime.nsec = ntohl(to->sd_ctime.nsec); + to->sd_mtime.sec = ntohl(to->sd_mtime.sec); + to->sd_mtime.nsec = ntohl(to->sd_mtime.nsec); + to->sd_dev = ntohl(to->sd_dev); + to->sd_ino = ntohl(to->sd_ino); + to->sd_uid = ntohl(to->sd_uid); + to->sd_gid = ntohl(to->sd_gid); + to->sd_size = ntohl(to->sd_size); } static int read_one_dir(struct untracked_cache_dir **untracked_, @@ -2645,7 +2647,7 @@ static void read_stat(size_t pos, void *cb) rd->data = rd->end + 1; return; } - stat_data_from_disk(&ud->stat_data, (struct stat_data *)rd->data); + stat_data_from_disk(&ud->stat_data, rd->data); rd->data += sizeof(struct stat_data); ud->valid = 1; } @@ -2663,22 +2665,22 @@ static void read_sha1(size_t pos, void *cb) } static void load_sha1_stat(struct sha1_stat *sha1_stat, - const struct stat_data *stat, + const unsigned char *data, const unsigned char *sha1) { - stat_data_from_disk(&sha1_stat->stat, stat); + stat_data_from_disk(&sha1_stat->stat, data); hashcpy(sha1_stat->sha1, sha1); sha1_stat->valid = 1; } struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz) { - const struct ondisk_untracked_cache *ouc; struct untracked_cache *uc; struct read_data rd; const unsigned char *next = data, *end = (const unsigned char *)data + sz; const char *ident; int ident_len, len; + const char *exclude_per_dir; if (sz <= 1 || end[-1] != '\0') return NULL; @@ -2690,21 +2692,23 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long ident = (const char *)next; next += ident_len; - ouc = (const struct ondisk_untracked_cache *)next; if (next + ouc_size(0) > end) return NULL; uc = xcalloc(1, sizeof(*uc)); strbuf_init(&uc->ident, ident_len); strbuf_add(&uc->ident, ident, ident_len); - load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat, - ouc->info_exclude_sha1); - load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat, - ouc->excludes_file_sha1); - uc->dir_flags = get_be32(&ouc->dir_flags); - uc->exclude_per_dir = xstrdup(ouc->exclude_per_dir); + load_sha1_stat(&uc->ss_info_exclude, + next + ouc_offset(info_exclude_stat), + next + ouc_offset(info_exclude_sha1)); + load_sha1_stat(&uc->ss_excludes_file, + next + ouc_offset(excludes_file_stat), + next + ouc_offset(excludes_file_sha1)); + uc->dir_flags = get_be32(next + ouc_offset(dir_flags)); + exclude_per_dir = (const char *)next + ouc_offset(exclude_per_dir); + uc->exclude_per_dir = xstrdup(exclude_per_dir); /* NUL after exclude_per_dir is covered by sizeof(*ouc) */ - next += ouc_size(strlen(ouc->exclude_per_dir)); + next += ouc_size(strlen(exclude_per_dir)); if (next >= end) goto done2; @@ -137,6 +137,105 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path, return result; } +void enable_delayed_checkout(struct checkout *state) +{ + if (!state->delayed_checkout) { + state->delayed_checkout = xmalloc(sizeof(*state->delayed_checkout)); + state->delayed_checkout->state = CE_CAN_DELAY; + string_list_init(&state->delayed_checkout->filters, 0); + string_list_init(&state->delayed_checkout->paths, 0); + } +} + +static int remove_available_paths(struct string_list_item *item, void *cb_data) +{ + struct string_list *available_paths = cb_data; + struct string_list_item *available; + + available = string_list_lookup(available_paths, item->string); + if (available) + available->util = (void *)item->string; + return !available; +} + +int finish_delayed_checkout(struct checkout *state) +{ + int errs = 0; + struct string_list_item *filter, *path; + struct delayed_checkout *dco = state->delayed_checkout; + + if (!state->delayed_checkout) + return errs; + + dco->state = CE_RETRY; + while (dco->filters.nr > 0) { + for_each_string_list_item(filter, &dco->filters) { + struct string_list available_paths = STRING_LIST_INIT_NODUP; + + if (!async_query_available_blobs(filter->string, &available_paths)) { + /* Filter reported an error */ + errs = 1; + filter->string = ""; + continue; + } + if (available_paths.nr <= 0) { + /* + * Filter responded with no entries. That means + * the filter is done and we can remove the + * filter from the list (see + * "string_list_remove_empty_items" call below). + */ + filter->string = ""; + continue; + } + + /* + * In dco->paths we store a list of all delayed paths. + * The filter just send us a list of available paths. + * Remove them from the list. + */ + filter_string_list(&dco->paths, 0, + &remove_available_paths, &available_paths); + + for_each_string_list_item(path, &available_paths) { + struct cache_entry* ce; + + if (!path->util) { + error("external filter '%s' signaled that '%s' " + "is now available although it has not been " + "delayed earlier", + filter->string, path->string); + errs |= 1; + + /* + * Do not ask the filter for available blobs, + * again, as the filter is likely buggy. + */ + filter->string = ""; + continue; + } + ce = index_file_exists(state->istate, path->string, + strlen(path->string), 0); + errs |= (ce ? checkout_entry(ce, state, NULL) : 1); + } + } + string_list_remove_empty_items(&dco->filters, 0); + } + string_list_clear(&dco->filters, 0); + + /* At this point we should not have any delayed paths anymore. */ + errs |= dco->paths.nr; + for_each_string_list_item(path, &dco->paths) { + error("'%s' was not filtered properly", path->string); + } + string_list_clear(&dco->paths, 0); + + free(dco); + state->delayed_checkout = NULL; + + return errs; +} + static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile) { @@ -179,11 +278,34 @@ static int write_entry(struct cache_entry *ce, /* * Convert from git internal format to working tree format */ - if (ce_mode_s_ifmt == S_IFREG && - convert_to_working_tree(ce->name, new, size, &buf)) { - free(new); - new = strbuf_detach(&buf, &newsize); - size = newsize; + if (ce_mode_s_ifmt == S_IFREG) { + struct delayed_checkout *dco = state->delayed_checkout; + if (dco && dco->state != CE_NO_DELAY) { + /* Do not send the blob in case of a retry. */ + if (dco->state == CE_RETRY) { + new = NULL; + size = 0; + } + ret = async_convert_to_working_tree( + ce->name, new, size, &buf, dco); + if (ret && string_list_has_string(&dco->paths, ce->name)) { + free(new); + goto finish; + } + } else + ret = convert_to_working_tree( + ce->name, new, size, &buf); + + if (ret) { + free(new); + new = strbuf_detach(&buf, &newsize); + size = newsize; + } + /* + * No "else" here as errors from convert are OK at this + * point. If the error would have been fatal (e.g. + * filter is required), then we would have died already. + */ } fd = open_output_fd(path, ce, to_tempfile); diff --git a/git-compat-util.h b/git-compat-util.h index db9c22de76..7d2c0ca759 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -828,6 +828,14 @@ static inline void copy_array(void *dst, const void *src, size_t n, size_t size) memcpy(dst, src, st_mult(size, n)); } +#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \ + BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src)))) +static inline void move_array(void *dst, const void *src, size_t n, size_t size) +{ + if (n) + memmove(dst, src, st_mult(size, n)); +} + /* * These functions help you allocate structs with flex arrays, and copy * the data directly into the array. For example, if you had: diff --git a/git-gui/Makefile b/git-gui/Makefile index fe30be38dc..918a8de369 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -161,7 +161,9 @@ ifeq ($(uname_S),Darwin) endif endif ifneq (,$(findstring MINGW,$(uname_S))) +ifeq ($(shell expr "$(uname_R)" : '1\.'),2) NO_MSGFMT=1 +endif GITGUI_WINDOWS_WRAPPER := YesPlease GITGUI_RELATIVE := 1 endif diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 75d1da8d31..80f5a59bbb 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -142,6 +142,10 @@ constructor pick {} { -label [mc "Recent Repositories"] } + if {[set lenrecent [llength $sorted_recent]] < $maxrecent} { + set lenrecent $maxrecent + } + ${NS}::label $w_body.space ${NS}::label $w_body.recentlabel \ -anchor w \ @@ -153,7 +157,7 @@ constructor pick {} { -background [get_bg_color $w_body.recentlabel] \ -wrap none \ -width 50 \ - -height $maxrecent + -height $lenrecent $w_recentlist tag conf link \ -foreground blue \ -underline 1 @@ -235,19 +239,19 @@ method _invoke_next {} { proc _get_recentrepos {} { set recent [list] - foreach p [get_config gui.recentrepo] { + foreach p [lsort -unique [get_config gui.recentrepo]] { if {[_is_git [file join $p .git]]} { lappend recent $p } else { _unset_recentrepo $p } } - return [lsort $recent] + return $recent } proc _unset_recentrepo {p} { regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p - git config --global --unset gui.recentrepo "^$p\$" + catch {git config --global --unset-all gui.recentrepo "^$p\$"} load_config 1 } @@ -262,12 +266,11 @@ proc _append_recentrepos {path} { set i [lsearch $recent $path] if {$i >= 0} { _unset_recentrepo $path - set recent [lreplace $recent $i $i] } - lappend recent $path git config --global --add gui.recentrepo $path load_config 1 + set recent [get_config gui.recentrepo] if {[set maxrecent [get_config gui.maxrecentrepo]] eq {}} { set maxrecent 10 @@ -275,7 +278,7 @@ proc _append_recentrepos {path} { while {[llength $recent] > $maxrecent} { _unset_recentrepo [lindex $recent 0] - set recent [lrange $recent 1 end] + set recent [get_config gui.recentrepo] } } diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 375239341f..319933e70a 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -45,7 +45,7 @@ then # itself well to recording empty patches. fortunately, cherry-pick # makes this easy git cherry-pick ${gpg_sign_opt:+"$gpg_sign_opt"} --allow-empty \ - --right-only "$revisions" \ + $allow_rerere_autoupdate --right-only "$revisions" \ ${restrict_revision+^$restrict_revision} ret=$? else @@ -82,6 +82,7 @@ else fi git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \ + $allow_rerere_autoupdate \ ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches" ret=$? diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 90b1fbe9cf..29b7e8824b 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -281,7 +281,7 @@ pick_one () { test -d "$rewritten" && pick_one_preserving_merges "$@" && return - output eval git cherry-pick \ + output eval git cherry-pick $allow_rerere_autoupdate \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" $empty_args $ff "$@" @@ -393,7 +393,8 @@ pick_one_preserving_merges () { merge_args="--no-log --no-ff" if ! do_with_author output eval \ 'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \ - $merge_args $strategy_args -m "$msg_content" $new_parents' + $allow_rerere_autoupdate $merge_args \ + $strategy_args -m "$msg_content" $new_parents' then printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")" @@ -401,7 +402,7 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output eval git cherry-pick \ + output eval git cherry-pick $allow_rerere_autoupdate \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" "$@" || die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")" diff --git a/git-stash.sh b/git-stash.sh index 9b6c2da7b4..5f09a47f0a 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -43,9 +43,16 @@ no_changes () { } untracked_files () { + if test "$1" = "-z" + then + shift + z=-z + else + z= + fi excl_opt=--exclude-standard test "$untracked" = "all" && excl_opt= - git ls-files -o -z $excl_opt -- "$@" + git ls-files -o $z $excl_opt -- "$@" } clear_stash () { @@ -114,7 +121,7 @@ create_stash () { # Untracked files are stored by themselves in a parentless commit, for # ease of unpacking later. u_commit=$( - untracked_files "$@" | ( + untracked_files -z "$@" | ( GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && rm -f "$TMPindex" && @@ -33,6 +33,16 @@ static void commit_pager_choice(void) { } } +void setup_auto_pager(const char *cmd, int def) +{ + if (use_pager != -1 || pager_in_use()) + return; + use_pager = check_pager_config(cmd); + if (use_pager == -1) + use_pager = def; + commit_pager_choice(); +} + static int handle_options(const char ***argv, int *argc, int *envchanged) { const char **orig_argv = *argv; @@ -283,6 +293,7 @@ static int handle_alias(int *argcp, const char ***argv) */ #define NEED_WORK_TREE (1<<3) #define SUPPORT_SUPER_PREFIX (1<<4) +#define DELAY_PAGER_CONFIG (1<<5) struct cmd_struct { const char *cmd; @@ -306,7 +317,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) prefix = setup_git_directory_gently(&nongit_ok); } - if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) + if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) && + !(p->option & DELAY_PAGER_CONFIG)) use_pager = check_pager_config(p->cmd); if (use_pager == -1 && p->option & USE_PAGER) use_pager = 1; @@ -454,7 +466,7 @@ static struct cmd_struct commands[] = { { "stripspace", cmd_stripspace }, { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX}, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, - { "tag", cmd_tag, RUN_SETUP }, + { "tag", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG }, { "unpack-file", cmd_unpack_file, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, @@ -547,7 +559,7 @@ static void execv_dashed_external(const char **argv) if (get_super_prefix()) die("%s doesn't support --super-prefix", argv[0]); - if (use_pager == -1) + if (use_pager == -1 && !is_builtin(argv[0])) use_pager = check_pager_config(argv[0]); commit_pager_choice(); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3d4a8ee27c..9208f42ed1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3071,6 +3071,8 @@ sub git_get_projects_list { return if (m!^[/.]$!); # only directories can be git repositories return unless (-d $_); + # need search permission + return unless (-x $_); # don't traverse too deep (Find is super slow on os x) # $project_maxdepth excludes depth of $projectroot if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { @@ -1821,7 +1821,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle return 0; if (opt->status_only) - return 0; + return opt->unmatch_name_only; if (opt->unmatch_name_only) { /* We did not see any hit, so we want to show this */ show_name(opt, gs->name); @@ -272,10 +272,10 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.sslversion", var)) return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) - return git_config_string(&ssl_cert, var, value); + return git_config_pathname(&ssl_cert, var, value); #if LIBCURL_VERSION_NUM >= 0x070903 if (!strcmp("http.sslkey", var)) - return git_config_string(&ssl_key, var, value); + return git_config_pathname(&ssl_key, var, value); #endif #if LIBCURL_VERSION_NUM >= 0x070908 if (!strcmp("http.sslcapath", var)) diff --git a/notes-merge.c b/notes-merge.c index 70e3fbeefb..c12b354f10 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -99,8 +99,7 @@ static struct notes_merge_pair *find_notes_merge_pair_pos( else { *occupied = 0; if (insert_new && i < len) { - memmove(list + i + 1, list + i, - (len - i) * sizeof(struct notes_merge_pair)); + MOVE_ARRAY(list + i + 1, list + i, len - i); memset(list + i, 0, sizeof(struct notes_merge_pair)); } } @@ -141,7 +141,6 @@ void *create_object(const unsigned char *sha1, void *o) struct object *obj = o; obj->parsed = 0; - obj->used = 0; obj->flags = 0; hashcpy(obj->oid.hash, sha1); @@ -38,6 +38,7 @@ struct object_array { * http-push.c: 16-----19 * commit.c: 16-----19 * sha1_name.c: 20 + * builtin/fsck.c: 0--3 */ #define FLAG_BITS 27 @@ -46,7 +47,6 @@ struct object_array { */ struct object { unsigned parsed : 1; - unsigned used : 1; unsigned type : TYPE_BITS; unsigned flags : FLAG_BITS; struct object_id oid; diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index 98518f4ddb..bc4eed3d75 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -1416,7 +1416,7 @@ sub parse_svn_date { delete $ENV{TZ}; } - my $our_TZ = get_tz_offset(); + my $our_TZ = get_tz_offset($epoch_in_UTC); # This converts $epoch_in_UTC into our local timezone. my ($sec, $min, $hour, $mday, $mon, $year, diff --git a/pkt-line.c b/pkt-line.c index 9d845ecc3c..7db9119573 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -171,25 +171,6 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...) return status; } -int packet_writel(int fd, const char *line, ...) -{ - va_list args; - int err; - va_start(args, line); - for (;;) { - if (!line) - break; - if (strlen(line) > LARGE_PACKET_DATA_MAX) - return -1; - err = packet_write_fmt_gently(fd, "%s\n", line); - if (err) - return err; - line = va_arg(args, const char*); - } - va_end(args); - return packet_flush_gently(fd); -} - static int packet_write_gently(const int fd_out, const char *buf, size_t size) { static char packet_write_buffer[LARGE_PACKET_MAX]; diff --git a/pkt-line.h b/pkt-line.h index 450183b649..66ef610fc4 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -25,8 +25,6 @@ void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_flush_gently(int fd); int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); -LAST_ARG_MUST_BE_NULL -int packet_writel(int fd, const char *line, ...); int write_packetized_from_fd(int fd_in, int fd_out); int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); @@ -947,6 +947,7 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ struct format_commit_context *c) { const char *rest = placeholder; + const char *basic_color = NULL; if (placeholder[1] == '(') { const char *begin = placeholder + 2; @@ -955,23 +956,41 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */ if (!end) return 0; + if (skip_prefix(begin, "auto,", &begin)) { if (!want_color(c->pretty_ctx->color)) return end - placeholder + 1; + } else if (skip_prefix(begin, "always,", &begin)) { + /* nothing to do; we do not respect want_color at all */ + } else { + /* the default is the same as "auto" */ + if (!want_color(c->pretty_ctx->color)) + return end - placeholder + 1; } + if (color_parse_mem(begin, end - begin, color) < 0) die(_("unable to parse --pretty format")); strbuf_addstr(sb, color); return end - placeholder + 1; } + + /* + * We handle things like "%C(red)" above; for historical reasons, there + * are a few colors that can be specified without parentheses (and + * they cannot support things like "auto" or "always" at all). + */ if (skip_prefix(placeholder + 1, "red", &rest)) - strbuf_addstr(sb, GIT_COLOR_RED); + basic_color = GIT_COLOR_RED; else if (skip_prefix(placeholder + 1, "green", &rest)) - strbuf_addstr(sb, GIT_COLOR_GREEN); + basic_color = GIT_COLOR_GREEN; else if (skip_prefix(placeholder + 1, "blue", &rest)) - strbuf_addstr(sb, GIT_COLOR_BLUE); + basic_color = GIT_COLOR_BLUE; else if (skip_prefix(placeholder + 1, "reset", &rest)) - strbuf_addstr(sb, GIT_COLOR_RESET); + basic_color = GIT_COLOR_RESET; + + if (basic_color && want_color(c->pretty_ctx->color)) + strbuf_addstr(sb, basic_color); + return rest - placeholder; } diff --git a/read-cache.c b/read-cache.c index 2121b6e7bb..acfb028f48 100644 --- a/read-cache.c +++ b/read-cache.c @@ -515,9 +515,8 @@ int remove_index_entry_at(struct index_state *istate, int pos) istate->cache_nr--; if (pos >= istate->cache_nr) return 0; - memmove(istate->cache + pos, - istate->cache + pos + 1, - (istate->cache_nr - pos) * sizeof(struct cache_entry *)); + MOVE_ARRAY(istate->cache + pos, istate->cache + pos + 1, + istate->cache_nr - pos); return 1; } diff --git a/ref-filter.c b/ref-filter.c index ae6ecbd1cf..bc591f4f3d 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -97,14 +97,19 @@ static struct used_atom { } u; } *used_atom; static int used_atom_cnt, need_tagged, need_symref; -static int need_color_reset_at_eol; -static void color_atom_parser(struct used_atom *atom, const char *color_value) +static void color_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *color_value) { if (!color_value) die(_("expected format: %%(color:<color>)")); if (color_parse(color_value, atom->u.color) < 0) die(_("unrecognized color: %%(color:%s)"), color_value); + /* + * We check this after we've parsed the color, which lets us complain + * about syntactically bogus color names even if they won't be used. + */ + if (!want_color(format->use_color)) + color_parse("", atom->u.color); } static void refname_atom_parser_internal(struct refname_atom *atom, @@ -127,7 +132,7 @@ static void refname_atom_parser_internal(struct refname_atom *atom, die(_("unrecognized %%(%s) argument: %s"), name, arg); } -static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) +static void remote_ref_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { struct string_list params = STRING_LIST_INIT_DUP; int i; @@ -161,28 +166,28 @@ static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) string_list_clear(¶ms, 0); } -static void body_atom_parser(struct used_atom *atom, const char *arg) +static void body_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (arg) die(_("%%(body) does not take arguments")); atom->u.contents.option = C_BODY_DEP; } -static void subject_atom_parser(struct used_atom *atom, const char *arg) +static void subject_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (arg) die(_("%%(subject) does not take arguments")); atom->u.contents.option = C_SUB; } -static void trailers_atom_parser(struct used_atom *atom, const char *arg) +static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (arg) die(_("%%(trailers) does not take arguments")); atom->u.contents.option = C_TRAILERS; } -static void contents_atom_parser(struct used_atom *atom, const char *arg) +static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (!arg) atom->u.contents.option = C_BARE; @@ -202,7 +207,7 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg) die(_("unrecognized %%(contents) argument: %s"), arg); } -static void objectname_atom_parser(struct used_atom *atom, const char *arg) +static void objectname_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (!arg) atom->u.objectname.option = O_FULL; @@ -219,7 +224,7 @@ static void objectname_atom_parser(struct used_atom *atom, const char *arg) die(_("unrecognized %%(objectname) argument: %s"), arg); } -static void refname_atom_parser(struct used_atom *atom, const char *arg) +static void refname_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { refname_atom_parser_internal(&atom->u.refname, arg, atom->name); } @@ -235,7 +240,7 @@ static align_type parse_align_position(const char *s) return -1; } -static void align_atom_parser(struct used_atom *atom, const char *arg) +static void align_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { struct align *align = &atom->u.align; struct string_list params = STRING_LIST_INIT_DUP; @@ -274,7 +279,7 @@ static void align_atom_parser(struct used_atom *atom, const char *arg) string_list_clear(¶ms, 0); } -static void if_atom_parser(struct used_atom *atom, const char *arg) +static void if_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { if (!arg) { atom->u.if_then_else.cmp_status = COMPARE_NONE; @@ -288,7 +293,7 @@ static void if_atom_parser(struct used_atom *atom, const char *arg) } } -static void head_atom_parser(struct used_atom *atom, const char *arg) +static void head_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg) { struct object_id unused; @@ -298,7 +303,7 @@ static void head_atom_parser(struct used_atom *atom, const char *arg) static struct { const char *name; cmp_type cmp_type; - void (*parser)(struct used_atom *atom, const char *arg); + void (*parser)(const struct ref_format *format, struct used_atom *atom, const char *arg); } valid_atom[] = { { "refname" , FIELD_STR, refname_atom_parser }, { "objecttype" }, @@ -365,7 +370,8 @@ struct atom_value { /* * Used to parse format string and sort specifiers */ -int parse_ref_filter_atom(const char *atom, const char *ep) +static int parse_ref_filter_atom(const struct ref_format *format, + const char *atom, const char *ep) { const char *sp; const char *arg; @@ -413,7 +419,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep) arg = used_atom[at].name + (arg - atom) + 1; memset(&used_atom[at].u, 0, sizeof(used_atom[at].u)); if (valid_atom[i].parser) - valid_atom[i].parser(&used_atom[at], arg); + valid_atom[i].parser(format, &used_atom[at], arg); if (*atom == '*') need_tagged = 1; if (!strcmp(valid_atom[i].name, "symref")) @@ -657,24 +663,26 @@ static const char *find_next(const char *cp) * Make sure the format string is well formed, and parse out * the used atoms. */ -int verify_ref_format(const char *format) +int verify_ref_format(struct ref_format *format) { const char *cp, *sp; - need_color_reset_at_eol = 0; - for (cp = format; *cp && (sp = find_next(cp)); ) { + format->need_color_reset_at_eol = 0; + for (cp = format->format; *cp && (sp = find_next(cp)); ) { const char *color, *ep = strchr(sp, ')'); int at; if (!ep) return error(_("malformed format string %s"), sp); /* sp points at "%(" and ep points at the closing ")" */ - at = parse_ref_filter_atom(sp + 2, ep); + at = parse_ref_filter_atom(format, sp + 2, ep); cp = ep + 1; if (skip_prefix(used_atom[at].name, "color:", &color)) - need_color_reset_at_eol = !!strcmp(color, "reset"); + format->need_color_reset_at_eol = !!strcmp(color, "reset"); } + if (format->need_color_reset_at_eol && !want_color(format->use_color)) + format->need_color_reset_at_eol = 0; return 0; } @@ -2060,35 +2068,34 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting } } -void format_ref_array_item(struct ref_array_item *info, const char *format, - int quote_style, struct strbuf *final_buf) +void format_ref_array_item(struct ref_array_item *info, + const struct ref_format *format, + struct strbuf *final_buf) { const char *cp, *sp, *ep; struct ref_formatting_state state = REF_FORMATTING_STATE_INIT; - state.quote_style = quote_style; + state.quote_style = format->quote_style; push_stack_element(&state.stack); - for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { + for (cp = format->format; *cp && (sp = find_next(cp)); cp = ep + 1) { struct atom_value *atomv; ep = strchr(sp, ')'); if (cp < sp) append_literal(cp, sp, &state); - get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv); + get_ref_atom_value(info, + parse_ref_filter_atom(format, sp + 2, ep), + &atomv); atomv->handler(atomv, &state); } if (*cp) { sp = cp + strlen(cp); append_literal(cp, sp, &state); } - if (need_color_reset_at_eol) { + if (format->need_color_reset_at_eol) { struct atom_value resetv; - char color[COLOR_MAXLEN] = ""; - - if (color_parse("reset", color) < 0) - die("BUG: couldn't parse 'reset' as a color"); - resetv.s = color; + resetv.s = GIT_COLOR_RESET; append_atom(&resetv, &state); } if (state.stack->prev) @@ -2097,26 +2104,38 @@ void format_ref_array_item(struct ref_array_item *info, const char *format, pop_stack_element(&state.stack); } -void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) +void show_ref_array_item(struct ref_array_item *info, + const struct ref_format *format) { struct strbuf final_buf = STRBUF_INIT; - format_ref_array_item(info, format, quote_style, &final_buf); + format_ref_array_item(info, format, &final_buf); fwrite(final_buf.buf, 1, final_buf.len, stdout); strbuf_release(&final_buf); putchar('\n'); } void pretty_print_ref(const char *name, const unsigned char *sha1, - const char *format) + const struct ref_format *format) { struct ref_array_item *ref_item; ref_item = new_ref_array_item(name, sha1, 0); ref_item->kind = ref_kind_from_refname(name); - show_ref_array_item(ref_item, format, 0); + show_ref_array_item(ref_item, format); free_array_item(ref_item); } +static int parse_sorting_atom(const char *atom) +{ + /* + * This parses an atom using a dummy ref_format, since we don't + * actually care about the formatting details. + */ + struct ref_format dummy = REF_FORMAT_INIT; + const char *end = atom + strlen(atom); + return parse_ref_filter_atom(&dummy, atom, end); +} + /* If no sorting option is given, use refname to sort as default */ struct ref_sorting *ref_default_sorting(void) { @@ -2125,18 +2144,13 @@ struct ref_sorting *ref_default_sorting(void) struct ref_sorting *sorting = xcalloc(1, sizeof(*sorting)); sorting->next = NULL; - sorting->atom = parse_ref_filter_atom(cstr_name, cstr_name + strlen(cstr_name)); + sorting->atom = parse_sorting_atom(cstr_name); return sorting; } -int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) +void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg) { - struct ref_sorting **sorting_tail = opt->value; struct ref_sorting *s; - int len; - - if (!arg) /* should --no-sort void the list ? */ - return -1; s = xcalloc(1, sizeof(*s)); s->next = *sorting_tail; @@ -2149,8 +2163,14 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) s->version = 1; - len = strlen(arg); - s->atom = parse_ref_filter_atom(arg, arg+len); + s->atom = parse_sorting_atom(arg); +} + +int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) +{ + if (!arg) /* should --no-sort void the list ? */ + return -1; + parse_ref_sorting(opt->value, arg); return 0; } diff --git a/ref-filter.h b/ref-filter.h index 6552024f09..0d98342b34 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -72,6 +72,21 @@ struct ref_filter { verbose; }; +struct ref_format { + /* + * Set these to define the format; make sure you call + * verify_ref_format() afterwards to finalize. + */ + const char *format; + int quote_style; + int use_color; + + /* Internal state to ref-filter */ + int need_color_reset_at_eol; +}; + +#define REF_FORMAT_INIT { NULL, 0, -1 } + /* Macros for checking --merged and --no-merged options */ #define _OPT_MERGED_NO_MERGED(option, filter, h) \ { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \ @@ -90,17 +105,18 @@ struct ref_filter { int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type); /* Clear all memory allocated to ref_array */ void ref_array_clear(struct ref_array *array); -/* Parse format string and sort specifiers */ -int parse_ref_filter_atom(const char *atom, const char *ep); /* Used to verify if the given format is correct and to parse out the used atoms */ -int verify_ref_format(const char *format); +int verify_ref_format(struct ref_format *format); /* Sort the given ref_array as per the ref_sorting provided */ void ref_array_sort(struct ref_sorting *sort, struct ref_array *array); /* Based on the given format and quote_style, fill the strbuf */ -void format_ref_array_item(struct ref_array_item *info, const char *format, - int quote_style, struct strbuf *final_buf); +void format_ref_array_item(struct ref_array_item *info, + const struct ref_format *format, + struct strbuf *final_buf); /* Print the ref using the given format and quote_style */ -void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style); +void show_ref_array_item(struct ref_array_item *info, const struct ref_format *format); +/* Parse a single sort specifier and add it to the list */ +void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *atom); /* Callback function for parsing the sort option */ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset); /* Default sort option based on refname */ @@ -117,6 +133,6 @@ void setup_ref_filter_porcelain_msg(void); * name must be a fully qualified refname. */ void pretty_print_ref(const char *name, const unsigned char *sha1, - const char *format); + const struct ref_format *format); #endif /* REF_FILTER_H */ diff --git a/reflog-walk.c b/reflog-walk.c index 081f89b70d..74ebe5148f 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -94,45 +94,6 @@ static int get_reflog_recno_by_time(struct complete_reflogs *array, return -1; } -struct commit_info_lifo { - struct commit_info { - struct commit *commit; - void *util; - } *items; - int nr, alloc; -}; - -static struct commit_info *get_commit_info(struct commit *commit, - struct commit_info_lifo *lifo, int pop) -{ - int i; - for (i = 0; i < lifo->nr; i++) - if (lifo->items[i].commit == commit) { - struct commit_info *result = &lifo->items[i]; - if (pop) { - if (i + 1 < lifo->nr) - memmove(lifo->items + i, - lifo->items + i + 1, - (lifo->nr - i) * - sizeof(struct commit_info)); - lifo->nr--; - } - return result; - } - return NULL; -} - -static void add_commit_info(struct commit *commit, void *util, - struct commit_info_lifo *lifo) -{ - struct commit_info *info; - ALLOC_GROW(lifo->items, lifo->nr + 1, lifo->alloc); - info = lifo->items + lifo->nr; - info->commit = commit; - info->util = util; - lifo->nr++; -} - struct commit_reflog { int recno; enum selector_type { @@ -144,7 +105,8 @@ struct commit_reflog { }; struct reflog_walk_info { - struct commit_info_lifo reflogs; + struct commit_reflog **logs; + size_t nr, alloc; struct string_list complete_reflogs; struct commit_reflog *last_commit_reflog; }; @@ -233,52 +195,10 @@ int add_reflog_for_walk(struct reflog_walk_info *info, commit_reflog->selector = selector; commit_reflog->reflogs = reflogs; - add_commit_info(commit, commit_reflog, &info->reflogs); - return 0; -} + ALLOC_GROW(info->logs, info->nr + 1, info->alloc); + info->logs[info->nr++] = commit_reflog; -void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) -{ - struct commit_info *commit_info = - get_commit_info(commit, &info->reflogs, 0); - struct commit_reflog *commit_reflog; - struct object *logobj; - struct reflog_info *reflog; - - info->last_commit_reflog = NULL; - if (!commit_info) - return; - - commit_reflog = commit_info->util; - if (commit_reflog->recno < 0) { - commit->parents = NULL; - return; - } - info->last_commit_reflog = commit_reflog; - - do { - reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; - commit_reflog->recno--; - logobj = parse_object(&reflog->ooid); - } while (commit_reflog->recno && (logobj && logobj->type != OBJ_COMMIT)); - - if (!logobj && commit_reflog->recno >= 0 && is_null_oid(&reflog->ooid)) { - /* a root commit, but there are still more entries to show */ - reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; - logobj = parse_object(&reflog->noid); - if (!logobj) - logobj = parse_object(&reflog->ooid); - } - - if (!logobj || logobj->type != OBJ_COMMIT) { - commit_info->commit = NULL; - commit->parents = NULL; - return; - } - commit_info->commit = (struct commit *)logobj; - - commit->parents = xcalloc(1, sizeof(struct commit_list)); - commit->parents->item = commit_info->commit; + return 0; } void get_reflog_selector(struct strbuf *sb, @@ -344,6 +264,18 @@ const char *get_reflog_ident(struct reflog_walk_info *reflog_info) return info->email; } +timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info) +{ + struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog; + struct reflog_info *info; + + if (!commit_reflog) + return 0; + + info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; + return info->timestamp; +} + void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, const struct date_mode *dmode, int force_date) { @@ -365,3 +297,53 @@ void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline, strbuf_release(&selector); } } + +int reflog_walk_empty(struct reflog_walk_info *info) +{ + return !info || !info->nr; +} + +static struct commit *next_reflog_commit(struct commit_reflog *log) +{ + for (; log->recno >= 0; log->recno--) { + struct reflog_info *entry = &log->reflogs->items[log->recno]; + struct object *obj = parse_object(&entry->noid); + + if (obj && obj->type == OBJ_COMMIT) + return (struct commit *)obj; + } + return NULL; +} + +static timestamp_t log_timestamp(struct commit_reflog *log) +{ + return log->reflogs->items[log->recno].timestamp; +} + +struct commit *next_reflog_entry(struct reflog_walk_info *walk) +{ + struct commit_reflog *best = NULL; + struct commit *best_commit = NULL; + size_t i; + + for (i = 0; i < walk->nr; i++) { + struct commit_reflog *log = walk->logs[i]; + struct commit *commit = next_reflog_commit(log); + + if (!commit) + continue; + + if (!best || log_timestamp(log) > log_timestamp(best)) { + best = log; + best_commit = commit; + } + } + + if (best) { + best->recno--; + walk->last_commit_reflog = best; + return best_commit; + } + + return NULL; +} diff --git a/reflog-walk.h b/reflog-walk.h index 27886f793e..7553c448fe 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -8,16 +8,19 @@ struct reflog_walk_info; extern void init_reflog_walk(struct reflog_walk_info **info); extern int add_reflog_for_walk(struct reflog_walk_info *info, struct commit *commit, const char *name); -extern void fake_reflog_parent(struct reflog_walk_info *info, - struct commit *commit); extern void show_reflog_message(struct reflog_walk_info *info, int, const struct date_mode *, int force_date); extern void get_reflog_message(struct strbuf *sb, struct reflog_walk_info *reflog_info); extern const char *get_reflog_ident(struct reflog_walk_info *reflog_info); +extern timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info); extern void get_reflog_selector(struct strbuf *sb, struct reflog_walk_info *reflog_info, const struct date_mode *dmode, int force_date, int shorten); +extern int reflog_walk_empty(struct reflog_walk_info *walk); + +struct commit *next_reflog_entry(struct reflog_walk_info *reflog_info); + #endif @@ -1160,7 +1160,7 @@ int ref_is_hidden(const char *refname, const char *refname_full) const char *match = hide_refs->items[i].string; const char *subject; int neg = 0; - int len; + const char *p; if (*match == '!') { neg = 1; @@ -1175,10 +1175,9 @@ int ref_is_hidden(const char *refname, const char *refname_full) } /* refname can be NULL when namespaces are used. */ - if (!subject || !starts_with(subject, match)) - continue; - len = strlen(match); - if (!subject[len] || subject[len] == '/') + if (subject && + skip_prefix(subject, match, &p) && + (!*p || *p == '/')) return !neg; } return 0; diff --git a/revision.c b/revision.c index 6603af9444..f032ab2e5c 100644 --- a/revision.c +++ b/revision.c @@ -148,16 +148,14 @@ static void add_pending_object_with_path(struct rev_info *revs, if (revs->reflog_info && obj->type == OBJ_COMMIT) { struct strbuf buf = STRBUF_INIT; int len = interpret_branch_name(name, 0, &buf, 0); - int st; if (0 < len && name[len] && buf.len) strbuf_addstr(&buf, name + len); - st = add_reflog_for_walk(revs->reflog_info, - (struct commit *)obj, - buf.buf[0] ? buf.buf: name); + add_reflog_for_walk(revs->reflog_info, + (struct commit *)obj, + buf.buf[0] ? buf.buf: name); strbuf_release(&buf); - if (st) - return; + return; /* do not add the commit itself */ } add_object_array_with_path(obj, name, &revs->pending, mode, path); } @@ -1168,6 +1166,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, { cb->all_revs = revs; cb->all_flags = flags; + revs->rev_input_given = 1; } void clear_ref_exclusion(struct string_list **ref_excludes_p) @@ -2315,7 +2314,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s opt->tweak(revs, opt); if (revs->show_merge) prepare_show_merge(revs); - if (revs->def && !revs->pending.nr && !got_rev_arg) { + if (revs->def && !revs->pending.nr && !revs->rev_input_given && !got_rev_arg) { struct object_id oid; struct object *object; struct object_context oc; @@ -2364,6 +2363,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->reverse && revs->reflog_info) die("cannot combine --reverse with --walk-reflogs"); + if (revs->reflog_info && revs->limited) + die("cannot combine --walk-reflogs with history-limiting options"); if (revs->rewrite_parents && revs->children.name) die("cannot combine --parents and --children"); @@ -2963,6 +2964,18 @@ static inline int want_ancestry(const struct rev_info *revs) return (revs->rewrite_parents || revs->children.name); } +/* + * Return a timestamp to be used for --since/--until comparisons for this + * commit, based on the revision options. + */ +static timestamp_t comparison_date(const struct rev_info *revs, + struct commit *commit) +{ + return revs->reflog_info ? + get_reflog_timestamp(revs->reflog_info) : + commit->date; +} + enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit) { if (commit->object.flags & SHOWN) @@ -2973,8 +2986,9 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi return commit_show; if (commit->object.flags & UNINTERESTING) return commit_ignore; - if (revs->min_age != -1 && (commit->date > revs->min_age)) - return commit_ignore; + if (revs->min_age != -1 && + comparison_date(revs, commit) > revs->min_age) + return commit_ignore; if (revs->min_parents || (revs->max_parents >= 0)) { int n = commit_list_count(commit->parents); if ((n < revs->min_parents) || @@ -3107,17 +3121,19 @@ static void track_linear(struct rev_info *revs, struct commit *commit) static struct commit *get_revision_1(struct rev_info *revs) { - if (!revs->commits) - return NULL; + while (1) { + struct commit *commit; - do { - struct commit *commit = pop_commit(&revs->commits); + if (revs->reflog_info) + commit = next_reflog_entry(revs->reflog_info); + else + commit = pop_commit(&revs->commits); - if (revs->reflog_info) { - save_parents(revs, commit); - fake_reflog_parent(revs->reflog_info, commit); + if (!commit) + return NULL; + + if (revs->reflog_info) commit->object.flags &= ~(ADDED | SEEN | SHOWN); - } /* * If we haven't done the list limiting, we need to look at @@ -3126,9 +3142,12 @@ static struct commit *get_revision_1(struct rev_info *revs) */ if (!revs->limited) { if (revs->max_age != -1 && - (commit->date < revs->max_age)) + comparison_date(revs, commit) < revs->max_age) continue; - if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) { + + if (revs->reflog_info) + try_to_simplify_commit(revs, commit); + else if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) { if (!revs->ignore_missing_links) die("Failed to traverse parents of commit %s", oid_to_hex(&commit->object.oid)); @@ -3146,8 +3165,7 @@ static struct commit *get_revision_1(struct rev_info *revs) track_linear(revs, commit); return commit; } - } while (revs->commits); - return NULL; + } } /* diff --git a/revision.h b/revision.h index f96e7f7f49..bc18487d6f 100644 --- a/revision.h +++ b/revision.h @@ -71,6 +71,13 @@ struct rev_info { const char *def; struct pathspec prune_data; + /* + * Whether the arguments parsed by setup_revisions() included any + * "input" revisions that might still have yielded an empty pending + * list (e.g., patterns like "--all" or "--glob"). + */ + int rev_input_given; + /* topo-sort */ enum rev_sort_order sort_order; diff --git a/run-command.c b/run-command.c index 9e36151bf9..b5e6eb37c0 100644 --- a/run-command.c +++ b/run-command.c @@ -1533,7 +1533,7 @@ static int pp_start_one(struct parallel_processes *pp) if (start_command(&pp->children[i].process)) { code = pp->start_failure(&pp->children[i].err, pp->data, - &pp->children[i].data); + pp->children[i].data); strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); strbuf_reset(&pp->children[i].err); if (code) @@ -1601,7 +1601,7 @@ static int pp_collect_finished(struct parallel_processes *pp) code = pp->task_finished(code, &pp->children[i].err, pp->data, - &pp->children[i].data); + pp->children[i].data); if (code) result = code; diff --git a/sequencer.c b/sequencer.c index 3010faf863..e0e66b987b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -127,6 +127,7 @@ static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") +static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1438,7 +1439,11 @@ static int populate_opts_cb(const char *key, const char *value, void *data) else if (!strcmp(key, "options.strategy-option")) { ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); opts->xopts[opts->xopts_nr++] = xstrdup(value); - } else + } else if (!strcmp(key, "options.allow-rerere-auto")) + opts->allow_rerere_auto = + git_config_bool_or_int(key, value, &error_flag) ? + RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE; + else return error(_("invalid key: %s"), key); if (!error_flag) @@ -1479,6 +1484,15 @@ static int read_populate_opts(struct replay_opts *opts) free(opts->gpg_sign); opts->gpg_sign = xstrdup(buf.buf + 2); } + strbuf_reset(&buf); + } + + if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) { + if (!strcmp(buf.buf, "--rerere-autoupdate")) + opts->allow_rerere_auto = RERERE_AUTOUPDATE; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_auto = RERERE_NOAUTOUPDATE; + strbuf_reset(&buf); } if (file_exists(rebase_path_verbose())) @@ -1742,6 +1756,10 @@ static int save_opts(struct replay_opts *opts) "options.strategy-option", opts->xopts[i], "^$", 0); } + if (opts->allow_rerere_auto) + res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto", + opts->allow_rerere_auto == RERERE_AUTOUPDATE ? + "true" : "false"); return res; } diff --git a/sha1_file.c b/sha1_file.c index b60ae15f70..b7bb38b445 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2542,8 +2542,8 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, error("bad packed object CRC for %s", sha1_to_hex(sha1)); mark_bad_packed_object(p, sha1); - unuse_pack(&w_curs); - return NULL; + data = NULL; + goto out; } } @@ -2681,6 +2681,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, if (final_size) *final_size = size; +out: unuse_pack(&w_curs); if (delta_stack != small_delta_stack) diff --git a/string-list.c b/string-list.c index c650500c6e..806b4c8723 100644 --- a/string-list.c +++ b/string-list.c @@ -43,9 +43,8 @@ static int add_entry(int insert_at, struct string_list *list, const char *string ALLOC_GROW(list->items, list->nr+1, list->alloc); if (index < list->nr) - memmove(list->items + index + 1, list->items + index, - (list->nr - index) - * sizeof(struct string_list_item)); + MOVE_ARRAY(list->items + index + 1, list->items + index, + list->nr - index); list->items[index].string = list->strdup_strings ? xstrdup(string) : (char *)string; list->items[index].util = NULL; @@ -77,8 +76,7 @@ void string_list_remove(struct string_list *list, const char *string, free(list->items[i].util); list->nr--; - memmove(list->items + i, list->items + i + 1, - (list->nr - i) * sizeof(struct string_list_item)); + MOVE_ARRAY(list->items + i, list->items + i + 1, list->nr - i); } } diff --git a/sub-process.c b/sub-process.c index a3cfab1a9d..fcc4832c14 100644 --- a/sub-process.c +++ b/sub-process.c @@ -105,3 +105,107 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co hashmap_add(hashmap, entry); return 0; } + +static int handshake_version(struct child_process *process, + const char *welcome_prefix, int *versions, + int *chosen_version) +{ + int version_scratch; + int i; + char *line; + const char *p; + + if (!chosen_version) + chosen_version = &version_scratch; + + if (packet_write_fmt_gently(process->in, "%s-client\n", + welcome_prefix)) + return error("Could not write client identification"); + for (i = 0; versions[i]; i++) { + if (packet_write_fmt_gently(process->in, "version=%d\n", + versions[i])) + return error("Could not write requested version"); + } + if (packet_flush_gently(process->in)) + return error("Could not write flush packet"); + + if (!(line = packet_read_line(process->out, NULL)) || + !skip_prefix(line, welcome_prefix, &p) || + strcmp(p, "-server")) + return error("Unexpected line '%s', expected %s-server", + line ? line : "<flush packet>", welcome_prefix); + if (!(line = packet_read_line(process->out, NULL)) || + !skip_prefix(line, "version=", &p) || + strtol_i(p, 10, chosen_version)) + return error("Unexpected line '%s', expected version", + line ? line : "<flush packet>"); + if ((line = packet_read_line(process->out, NULL))) + return error("Unexpected line '%s', expected flush", line); + + /* Check to make sure that the version received is supported */ + for (i = 0; versions[i]; i++) { + if (versions[i] == *chosen_version) + break; + } + if (!versions[i]) + return error("Version %d not supported", *chosen_version); + + return 0; +} + +static int handshake_capabilities(struct child_process *process, + struct subprocess_capability *capabilities, + unsigned int *supported_capabilities) +{ + int i; + char *line; + + for (i = 0; capabilities[i].name; i++) { + if (packet_write_fmt_gently(process->in, "capability=%s\n", + capabilities[i].name)) + return error("Could not write requested capability"); + } + if (packet_flush_gently(process->in)) + return error("Could not write flush packet"); + + while ((line = packet_read_line(process->out, NULL))) { + const char *p; + if (!skip_prefix(line, "capability=", &p)) + continue; + + for (i = 0; + capabilities[i].name && strcmp(p, capabilities[i].name); + i++) + ; + if (capabilities[i].name) { + if (supported_capabilities) + *supported_capabilities |= capabilities[i].flag; + } else { + warning("subprocess '%s' requested unsupported capability '%s'", + process->argv[0], p); + } + } + + return 0; +} + +int subprocess_handshake(struct subprocess_entry *entry, + const char *welcome_prefix, + int *versions, + int *chosen_version, + struct subprocess_capability *capabilities, + unsigned int *supported_capabilities) +{ + int retval; + struct child_process *process = &entry->process; + + sigchain_push(SIGPIPE, SIG_IGN); + + retval = handshake_version(process, welcome_prefix, versions, + chosen_version) || + handshake_capabilities(process, capabilities, + supported_capabilities); + + sigchain_pop(SIGPIPE); + return retval; +} diff --git a/sub-process.h b/sub-process.h index 96a2cca360..caa91a9b92 100644 --- a/sub-process.h +++ b/sub-process.h @@ -6,35 +6,66 @@ #include "run-command.h" /* - * Generic implementation of background process infrastructure. - * See: Documentation/technical/api-sub-process.txt + * The sub-process API makes it possible to run background sub-processes + * for the entire lifetime of a Git invocation. If Git needs to communicate + * with an external process multiple times, then this can reduces the process + * invocation overhead. Git and the sub-process communicate through stdin and + * stdout. + * + * The sub-processes are kept in a hashmap by command name and looked up + * via the subprocess_find_entry function. If an existing instance can not + * be found then a new process should be created and started. When the + * parent git command terminates, all sub-processes are also terminated. + * + * This API is based on the run-command API. */ /* data structures */ +/* Members should not be accessed directly. */ struct subprocess_entry { struct hashmap_entry ent; /* must be the first member! */ const char *cmd; struct child_process process; }; +struct subprocess_capability { + const char *name; + + /* + * subprocess_handshake will "|=" this value to supported_capabilities + * if the server reports that it supports this capability. + */ + unsigned int flag; +}; + /* subprocess functions */ +/* Function to test two subprocess hashmap entries for equality. */ extern int cmd2process_cmp(const void *unused_cmp_data, const struct subprocess_entry *e1, const struct subprocess_entry *e2, const void *unused_keydata); +/* + * User-supplied function to initialize the sub-process. This is + * typically used to negotiate the interface version and capabilities. + */ typedef int(*subprocess_start_fn)(struct subprocess_entry *entry); + +/* Start a subprocess and add it to the subprocess hashmap. */ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd, subprocess_start_fn startfn); +/* Kill a subprocess and remove it from the subprocess hashmap. */ void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry); +/* Find a subprocess in the subprocess hashmap. */ struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd); /* subprocess helper functions */ +/* Get the underlying `struct child_process` from a subprocess. */ static inline struct child_process *subprocess_get_child_process( struct subprocess_entry *entry) { @@ -42,6 +73,22 @@ static inline struct child_process *subprocess_get_child_process( } /* + * Perform the version and capability negotiation as described in the "Long + * Running Filter Process" section of the gitattributes documentation using the + * given requested versions and capabilities. The "versions" and "capabilities" + * parameters are arrays terminated by a 0 or blank struct. + * + * This function is typically called when a subprocess is started (as part of + * the "startfn" passed to subprocess_start). + */ +int subprocess_handshake(struct subprocess_entry *entry, + const char *welcome_prefix, + int *versions, + int *chosen_version, + struct subprocess_capability *capabilities, + unsigned int *supported_capabilities); + +/* * Helper function that will read packets looking for "status=<foo>" * key/value pairs and return the value from the last "status" packet */ diff --git a/submodule.c b/submodule.c index 6531c5d609..36f45f5a5a 100644 --- a/submodule.c +++ b/submodule.c @@ -1015,7 +1015,8 @@ static int push_submodule(const char *path, * Perform a check in the submodule to see if the remote and refspec work. * Die if the submodule can't be pushed. */ -static void submodule_push_check(const char *path, const struct remote *remote, +static void submodule_push_check(const char *path, const char *head, + const struct remote *remote, const char **refspec, int refspec_nr) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1023,6 +1024,7 @@ static void submodule_push_check(const char *path, const struct remote *remote, argv_array_push(&cp.args, "submodule--helper"); argv_array_push(&cp.args, "push-check"); + argv_array_push(&cp.args, head); argv_array_push(&cp.args, remote->name); for (i = 0; i < refspec_nr; i++) @@ -1061,10 +1063,20 @@ int push_unpushed_submodules(struct oid_array *commits, * won't be propagated due to the remote being unconfigured (e.g. a URL * instead of a remote name). */ - if (remote->origin != REMOTE_UNCONFIGURED) + if (remote->origin != REMOTE_UNCONFIGURED) { + char *head; + struct object_id head_oid; + + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + for (i = 0; i < needs_pushing.nr; i++) submodule_push_check(needs_pushing.items[i].string, - remote, refspec, refspec_nr); + head, remote, + refspec, refspec_nr); + free(head); + } /* Actually push the submodules */ for (i = 0; i < needs_pushing.nr; i++) { diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 1ebe0f750c..2b3c5092a1 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -38,6 +38,20 @@ struct test_data { const char *alternative; /* output: ... or this. */ }; +/* + * Compatibility wrappers for OpenBSD, whose basename(3) and dirname(3) + * have const parameters. + */ +static char *posix_basename(char *path) +{ + return basename(path); +} + +static char *posix_dirname(char *path) +{ + return dirname(path); +} + static int test_function(struct test_data *data, char *(*func)(char *input), const char *funcname) { @@ -251,10 +265,10 @@ int cmd_main(int argc, const char **argv) } if (argc == 2 && !strcmp(argv[1], "basename")) - return test_function(basename_data, basename, argv[1]); + return test_function(basename_data, posix_basename, argv[1]); if (argc == 2 && !strcmp(argv[1], "dirname")) - return test_function(dirname_data, dirname, argv[1]); + return test_function(dirname_data, posix_dirname, argv[1]); fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index ec2aa8f687..43679a4c64 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -31,6 +31,7 @@ then chmod 0700 ./gpghome && GNUPGHOME="$(pwd)/gpghome" && export GNUPGHOME && + (gpgconf --kill gpg-agent 2>&1 >/dev/null || : ) && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \ "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \ diff --git a/t/t0001-init.sh b/t/t0001-init.sh index c4814d248f..86c1a51654 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -315,18 +315,44 @@ test_expect_success 'init with separate gitdir' ' test_path_is_dir realgitdir/refs ' -test_expect_success 'init in long base path' ' +test_lazy_prereq GETCWD_IGNORES_PERMS ' + base=GETCWD_TEST_BASE_DIR && + mkdir -p $base/dir && + chmod 100 $base || + error "bug in test script: cannot prepare $base" + + (cd $base/dir && /bin/pwd -P) + status=$? + + chmod 700 $base && + rm -rf $base || + error "bug in test script: cannot clean $base" + return $status +' + +check_long_base_path () { # exceed initial buffer size of strbuf_getcwd() component=123456789abcdef && test_when_finished "chmod 0700 $component; rm -rf $component" && p31=$component/$component && p127=$p31/$p31/$p31/$p31 && mkdir -p $p127 && - chmod 0111 $component && + if test $# = 1 + then + chmod $1 $component + fi && ( cd $p127 && git init newdir ) +} + +test_expect_success 'init in long base path' ' + check_long_base_path +' + +test_expect_success GETCWD_IGNORES_PERMS 'init in long restricted base path' ' + check_long_base_path 0111 ' test_expect_success 're-init on .git file' ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 161f560446..46f8e583c3 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -28,7 +28,7 @@ file_size () { } filter_git () { - rm -f rot13-filter.log && + rm -f *.log && git "$@" } @@ -42,10 +42,10 @@ test_cmp_count () { for FILE in "$expect" "$actual" do sort "$FILE" | uniq -c | - sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp" && - mv "$FILE.tmp" "$FILE" || return + sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp" done && - test_cmp "$expect" "$actual" + test_cmp "$expect.tmp" "$actual.tmp" && + rm "$expect.tmp" "$actual.tmp" } # Compare two files but exclude all `clean` invocations because Git can @@ -56,10 +56,10 @@ test_cmp_exclude_clean () { actual=$2 for FILE in "$expect" "$actual" do - grep -v "IN: clean" "$FILE" >"$FILE.tmp" && - mv "$FILE.tmp" "$FILE" + grep -v "IN: clean" "$FILE" >"$FILE.tmp" done && - test_cmp "$expect" "$actual" + test_cmp "$expect.tmp" "$actual.tmp" && + rm "$expect.tmp" "$actual.tmp" } # Check that the contents of two files are equal and that their rot13 version @@ -342,7 +342,7 @@ test_expect_success 'diff does not reuse worktree files that need cleaning' ' ' test_expect_success PERL 'required process filter should filter data' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -375,7 +375,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: clean testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && git commit -m "test commit 2" && rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" && @@ -388,7 +388,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && filter_git checkout --quiet --no-progress empty-branch && cat >expected.log <<-EOF && @@ -397,7 +397,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && filter_git checkout --quiet --no-progress master && cat >expected.log <<-EOF && @@ -409,7 +409,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && @@ -419,7 +419,7 @@ test_expect_success PERL 'required process filter should filter data' ' test_expect_success PERL 'required process filter takes precedence' ' test_config_global filter.protocol.clean false && - test_config_global filter.protocol.process "rot13-filter.pl clean" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -439,12 +439,12 @@ test_expect_success PERL 'required process filter takes precedence' ' IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log + test_cmp_count expected.log debug.log ) ' test_expect_success PERL 'required process filter should be used only for "clean" operation only' ' - test_config_global filter.protocol.process "rot13-filter.pl clean" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && rm -rf repo && mkdir repo && ( @@ -462,7 +462,7 @@ test_expect_success PERL 'required process filter should be used only for "clean IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && rm test.r && @@ -474,12 +474,12 @@ test_expect_success PERL 'required process filter should be used only for "clean init handshake complete STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log + test_cmp_exclude_clean expected.log debug.log ) ' test_expect_success PERL 'required process filter should process multiple packets' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && @@ -514,7 +514,7 @@ test_expect_success PERL 'required process filter should process multiple packet IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && rm -f *.file && @@ -529,7 +529,7 @@ test_expect_success PERL 'required process filter should process multiple packet IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && for FILE in *.file do @@ -539,7 +539,7 @@ test_expect_success PERL 'required process filter should process multiple packet ' test_expect_success PERL 'required process filter with clean error should fail' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -558,7 +558,7 @@ test_expect_success PERL 'required process filter with clean error should fail' ' test_expect_success PERL 'process filter should restart after unexpected write failure' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -579,7 +579,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f git add . && rm -f *.r && - rm -f rot13-filter.log && + rm -f debug.log && git checkout --quiet --no-progress . 2>git-stderr.log && grep "smudge write error at" git-stderr.log && @@ -588,14 +588,14 @@ test_expect_success PERL 'process filter should restart after unexpected write f cat >expected.log <<-EOF && START init handshake complete - IN: smudge smudge-write-fail.r $SF [OK] -- OUT: $SF [WRITE FAIL] + IN: smudge smudge-write-fail.r $SF [OK] -- [WRITE FAIL] START init handshake complete IN: smudge test.r $S [OK] -- OUT: $S . [OK] IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && @@ -609,7 +609,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f ' test_expect_success PERL 'process filter should not be restarted if it signals an error' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -634,12 +634,12 @@ test_expect_success PERL 'process filter should not be restarted if it signals a cat >expected.log <<-EOF && START init handshake complete - IN: smudge error.r $SE [OK] -- OUT: 0 [ERROR] + IN: smudge error.r $SE [OK] -- [ERROR] IN: smudge test.r $S [OK] -- OUT: $S . [OK] IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && @@ -648,7 +648,7 @@ test_expect_success PERL 'process filter should not be restarted if it signals a ' test_expect_success PERL 'process filter abort stops processing of all further files' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -673,10 +673,10 @@ test_expect_success PERL 'process filter abort stops processing of all further f cat >expected.log <<-EOF && START init handshake complete - IN: smudge abort.r $SA [OK] -- OUT: 0 [ABORT] + IN: smudge abort.r $SA [OK] -- [ABORT] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && test_cmp "$TEST_ROOT/test.o" test.r && test_cmp "$TEST_ROOT/test2.o" test2.r && @@ -697,8 +697,124 @@ test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' cp "$TEST_ROOT/test.o" test.r && test_must_fail git add . 2>git-stderr.log && - grep "does not support filter protocol version" git-stderr.log + grep "expected git-filter-server" git-stderr.log ) ' +test_expect_success PERL 'delayed checkout in process filter' ' + test_config_global filter.a.process "rot13-filter.pl a.log clean smudge delay" && + test_config_global filter.a.required true && + test_config_global filter.b.process "rot13-filter.pl b.log clean smudge delay" && + test_config_global filter.b.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=a" >.gitattributes && + echo "*.b filter=b" >>.gitattributes && + cp "$TEST_ROOT/test.o" test.a && + cp "$TEST_ROOT/test.o" test-delay10.a && + cp "$TEST_ROOT/test.o" test-delay11.a && + cp "$TEST_ROOT/test.o" test-delay20.a && + cp "$TEST_ROOT/test.o" test-delay10.b && + git add . && + git commit -m "test commit" + ) && + + S=$(file_size "$TEST_ROOT/test.o") && + cat >a.exp <<-EOF && + START + init handshake complete + IN: smudge test.a $S [OK] -- OUT: $S . [OK] + IN: smudge test-delay10.a $S [OK] -- [DELAYED] + IN: smudge test-delay11.a $S [OK] -- [DELAYED] + IN: smudge test-delay20.a $S [OK] -- [DELAYED] + IN: list_available_blobs test-delay10.a test-delay11.a [OK] + IN: smudge test-delay10.a 0 [OK] -- OUT: $S . [OK] + IN: smudge test-delay11.a 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs test-delay20.a [OK] + IN: smudge test-delay20.a 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs [OK] + STOP + EOF + cat >b.exp <<-EOF && + START + init handshake complete + IN: smudge test-delay10.b $S [OK] -- [DELAYED] + IN: list_available_blobs test-delay10.b [OK] + IN: smudge test-delay10.b 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs [OK] + STOP + EOF + + rm -rf repo-cloned && + filter_git clone repo repo-cloned && + test_cmp_count a.exp repo-cloned/a.log && + test_cmp_count b.exp repo-cloned/b.log && + + ( + cd repo-cloned && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b && + + rm *.a *.b && + filter_git checkout . && + test_cmp_count ../a.exp a.log && + test_cmp_count ../b.exp b.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b + ) +' + +test_expect_success PERL 'missing file in delayed checkout' ' + test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && + test_config_global filter.bug.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=bug" >.gitattributes && + cp "$TEST_ROOT/test.o" missing-delay.a + git add . && + git commit -m "test commit" + ) && + + rm -rf repo-cloned && + test_must_fail git clone repo repo-cloned 2>git-stderr.log && + cat git-stderr.log && + grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log +' + +test_expect_success PERL 'invalid file in delayed checkout' ' + test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && + test_config_global filter.bug.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=bug" >.gitattributes && + cp "$TEST_ROOT/test.o" invalid-delay.a && + cp "$TEST_ROOT/test.o" unfiltered + git add . && + git commit -m "test commit" + ) && + + rm -rf repo-cloned && + test_must_fail git clone repo repo-cloned 2>git-stderr.log && + grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log +' + test_done diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl index 617f581e56..ad685d92f8 100644 --- a/t/t0021/rot13-filter.pl +++ b/t/t0021/rot13-filter.pl @@ -2,8 +2,9 @@ # Example implementation for the Git filter protocol version 2 # See Documentation/gitattributes.txt, section "Filter Protocol" # -# The script takes the list of supported protocol capabilities as -# arguments ("clean", "smudge", etc). +# The first argument defines a debug log file that the script write to. +# All remaining arguments define a list of supported protocol +# capabilities ("clean", "smudge", etc). # # This implementation supports special test cases: # (1) If data with the pathname "clean-write-fail.r" is processed with @@ -17,6 +18,16 @@ # operation then the filter signals that it cannot or does not want # to process the file and any file after that is processed with the # same command. +# (5) If data with a pathname that is a key in the DELAY hash is +# requested (e.g. "test-delay10.a") then the filter responds with +# a "delay" status and sets the "requested" field in the DELAY hash. +# The filter will signal the availability of this object after +# "count" (field in DELAY hash) "list_available_blobs" commands. +# (6) If data with the pathname "missing-delay.a" is processed that the +# filter will drop the path from the "list_available_blobs" response. +# (7) If data with the pathname "invalid-delay.a" is processed that the +# filter will add the path "unfiltered" which was not delayed before +# to the "list_available_blobs" response. # use strict; @@ -24,9 +35,19 @@ use warnings; use IO::File; my $MAX_PACKET_CONTENT_SIZE = 65516; +my $log_file = shift @ARGV; my @capabilities = @ARGV; -open my $debug, ">>", "rot13-filter.log" or die "cannot open log file: $!"; +open my $debug, ">>", $log_file or die "cannot open log file: $!"; + +my %DELAY = ( + 'test-delay10.a' => { "requested" => 0, "count" => 1 }, + 'test-delay11.a' => { "requested" => 0, "count" => 1 }, + 'test-delay20.a' => { "requested" => 0, "count" => 2 }, + 'test-delay10.b' => { "requested" => 0, "count" => 1 }, + 'missing-delay.a' => { "requested" => 0, "count" => 1 }, + 'invalid-delay.a' => { "requested" => 0, "count" => 1 }, +); sub rot13 { my $str = shift; @@ -64,7 +85,7 @@ sub packet_bin_read { sub packet_txt_read { my ( $res, $buf ) = packet_bin_read(); - unless ( $buf =~ s/\n$// ) { + unless ( $buf eq '' or $buf =~ s/\n$// ) { die "A non-binary line MUST be terminated by an LF."; } return ( $res, $buf ); @@ -99,6 +120,7 @@ packet_flush(); ( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability"; ( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability"; +( packet_txt_read() eq ( 0, "capability=delay" ) ) || die "bad capability"; ( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end"; foreach (@capabilities) { @@ -109,88 +131,142 @@ print $debug "init handshake complete\n"; $debug->flush(); while (1) { - my ($command) = packet_txt_read() =~ /^command=(.+)$/; + my ( $command ) = packet_txt_read() =~ /^command=(.+)$/; print $debug "IN: $command"; $debug->flush(); - my ($pathname) = packet_txt_read() =~ /^pathname=(.+)$/; - print $debug " $pathname"; - $debug->flush(); - - if ( $pathname eq "" ) { - die "bad pathname '$pathname'"; - } + if ( $command eq "list_available_blobs" ) { + # Flush + packet_bin_read(); - # Flush - packet_bin_read(); - - my $input = ""; - { - binmode(STDIN); - my $buffer; - my $done = 0; - while ( !$done ) { - ( $done, $buffer ) = packet_bin_read(); - $input .= $buffer; + foreach my $pathname ( sort keys %DELAY ) { + if ( $DELAY{$pathname}{"requested"} >= 1 ) { + $DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1; + if ( $pathname eq "invalid-delay.a" ) { + # Send Git a pathname that was not delayed earlier + packet_txt_write("pathname=unfiltered"); + } + if ( $pathname eq "missing-delay.a" ) { + # Do not signal Git that this file is available + } elsif ( $DELAY{$pathname}{"count"} == 0 ) { + print $debug " $pathname"; + packet_txt_write("pathname=$pathname"); + } + } } - print $debug " " . length($input) . " [OK] -- "; - $debug->flush(); - } - - my $output; - if ( $pathname eq "error.r" or $pathname eq "abort.r" ) { - $output = ""; - } - elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { - $output = rot13($input); - } - elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { - $output = rot13($input); - } - else { - die "bad command '$command'"; - } - print $debug "OUT: " . length($output) . " "; - $debug->flush(); - - if ( $pathname eq "error.r" ) { - print $debug "[ERROR]\n"; - $debug->flush(); - packet_txt_write("status=error"); packet_flush(); - } - elsif ( $pathname eq "abort.r" ) { - print $debug "[ABORT]\n"; + + print $debug " [OK]\n"; $debug->flush(); - packet_txt_write("status=abort"); + packet_txt_write("status=success"); packet_flush(); } else { - packet_txt_write("status=success"); - packet_flush(); + my ( $pathname ) = packet_txt_read() =~ /^pathname=(.+)$/; + print $debug " $pathname"; + $debug->flush(); - if ( $pathname eq "${command}-write-fail.r" ) { - print $debug "[WRITE FAIL]\n"; + if ( $pathname eq "" ) { + die "bad pathname '$pathname'"; + } + + # Read until flush + my ( $done, $buffer ) = packet_txt_read(); + while ( $buffer ne '' ) { + if ( $buffer eq "can-delay=1" ) { + if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) { + $DELAY{$pathname}{"requested"} = 1; + } + } else { + die "Unknown message '$buffer'"; + } + + ( $done, $buffer ) = packet_txt_read(); + } + + my $input = ""; + { + binmode(STDIN); + my $buffer; + my $done = 0; + while ( !$done ) { + ( $done, $buffer ) = packet_bin_read(); + $input .= $buffer; + } + print $debug " " . length($input) . " [OK] -- "; $debug->flush(); - die "${command} write error"; } - while ( length($output) > 0 ) { - my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); - packet_bin_write($packet); - # dots represent the number of packets - print $debug "."; - if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { - $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); + my $output; + if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) { + $output = $DELAY{$pathname}{"output"} + } + elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) { + $output = ""; + } + elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { + $output = rot13($input); + } + elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { + $output = rot13($input); + } + else { + die "bad command '$command'"; + } + + if ( $pathname eq "error.r" ) { + print $debug "[ERROR]\n"; + $debug->flush(); + packet_txt_write("status=error"); + packet_flush(); + } + elsif ( $pathname eq "abort.r" ) { + print $debug "[ABORT]\n"; + $debug->flush(); + packet_txt_write("status=abort"); + packet_flush(); + } + elsif ( $command eq "smudge" and + exists $DELAY{$pathname} and + $DELAY{$pathname}{"requested"} == 1 + ) { + print $debug "[DELAYED]\n"; + $debug->flush(); + packet_txt_write("status=delayed"); + packet_flush(); + $DELAY{$pathname}{"requested"} = 2; + $DELAY{$pathname}{"output"} = $output; + } + else { + packet_txt_write("status=success"); + packet_flush(); + + if ( $pathname eq "${command}-write-fail.r" ) { + print $debug "[WRITE FAIL]\n"; + $debug->flush(); + die "${command} write error"; } - else { - $output = ""; + + print $debug "OUT: " . length($output) . " "; + $debug->flush(); + + while ( length($output) > 0 ) { + my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); + packet_bin_write($packet); + # dots represent the number of packets + print $debug "."; + if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { + $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); + } + else { + $output = ""; + } } + packet_flush(); + print $debug " [OK]\n"; + $debug->flush(); + packet_flush(); } - packet_flush(); - print $debug " [OK]\n"; - $debug->flush(); - packet_flush(); } } diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index b9cb76654b..6ac7734d79 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -171,14 +171,4 @@ test_expect_success 'reflog exists works' ' ! git reflog exists refs/heads/nonexistent ' -# The behavior with two reflogs is buggy and the output is in flux; for now -# we're just checking that the program works at all without segfaulting. -test_expect_success 'showing multiple reflogs works' ' - git log -g HEAD HEAD >actual -' - -test_expect_success 'showing multiple reflogs with an old date' ' - git log -g HEAD@{1979-01-01} HEAD >actual -' - test_done diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh new file mode 100755 index 0000000000..feb1efd8ff --- /dev/null +++ b/t/t1414-reflog-walk.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +test_description='various tests of reflog walk (log -g) behavior' +. ./test-lib.sh + +test_expect_success 'set up some reflog entries' ' + test_commit one && + test_commit two && + git checkout -b side HEAD^ && + test_commit three && + git merge --no-commit master && + echo evil-merge-content >>one.t && + test_tick && + git commit --no-edit -a +' + +do_walk () { + git log -g --format="%gd %gs" "$@" +} + +sq="'" +test_expect_success 'set up expected reflog' ' + cat >expect.all <<-EOF + HEAD@{0} commit (merge): Merge branch ${sq}master${sq} into side + HEAD@{1} commit: three + HEAD@{2} checkout: moving from master to side + HEAD@{3} commit: two + HEAD@{4} commit (initial): one + EOF +' + +test_expect_success 'reflog walk shows expected logs' ' + do_walk >actual && + test_cmp expect.all actual +' + +test_expect_success 'reflog can limit with --no-merges' ' + grep -v merge expect.all >expect && + do_walk --no-merges >actual && + test_cmp expect actual +' + +test_expect_success 'reflog can limit with pathspecs' ' + grep two expect.all >expect && + do_walk -- two.t >actual && + test_cmp expect actual +' + +test_expect_success 'pathspec limiting handles merges' ' + # we pick up: + # - the initial commit of one + # - the checkout back to commit one + # - the evil merge which touched one + sed -n "1p;3p;5p" expect.all >expect && + do_walk -- one.t >actual && + test_cmp expect actual +' + +test_expect_success '--parents shows true parents' ' + # convert newlines to spaces + echo $(git rev-parse HEAD HEAD^1 HEAD^2) >expect && + git rev-list -g --parents -1 HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'walking multiple reflogs shows all' ' + # We expect to see all entries for all reflogs, but interleaved by + # date, with order on the command line breaking ties. We + # can use "sort" on the separate lists to generate this, + # but note two tricks: + # + # 1. We use "{" as the delimiter, which lets us skip to the reflog + # date specifier as our second field, and then our "-n" numeric + # sort ignores the bits after the timestamp. + # + # 2. POSIX leaves undefined whether this is a stable sort or not. So + # we use "-k 1" to ensure that we see HEAD before master before + # side when breaking ties. + { + do_walk --date=unix HEAD && + do_walk --date=unix side && + do_walk --date=unix master + } >expect.raw && + sort -t "{" -k 2nr -k 1 <expect.raw >expect && + do_walk --date=unix HEAD master side >actual && + test_cmp expect actual +' + +test_expect_success 'date-limiting does not interfere with other logs' ' + do_walk HEAD@{1979-01-01} HEAD >actual && + test_cmp expect.all actual +' + +test_expect_success 'min/max age uses entry date to limit' ' + # Flip between commits one and two so each ref update actually + # does something (and does not get optimized out). We know + # that the timestamps of those commits will be before our "min". + + git update-ref -m before refs/heads/minmax one && + + test_tick && + min=$test_tick && + git update-ref -m min refs/heads/minmax two && + + test_tick && + max=$test_tick && + git update-ref -m max refs/heads/minmax one && + + test_tick && + git update-ref -m after refs/heads/minmax two && + + cat >expect <<-\EOF && + max + min + EOF + git log -g --since=$min --until=$max --format=%gs minmax >actual && + test_cmp expect actual +' + +test_expect_success 'walk prefers reflog to ref tip' ' + head=$(git rev-parse HEAD) && + one=$(git rev-parse one) && + ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && + echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD && + + echo $one >expect && + git log -g --format=%H -1 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list -g complains when there are no reflogs' ' + test_must_fail git rev-list -g +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index bb89e1a5db..4087150db1 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -608,6 +608,22 @@ test_expect_success 'fsck errors in packed objects' ' ! grep corrupt out ' +test_expect_success 'fsck fails on corrupt packfile' ' + hsh=$(git commit-tree -m mycommit HEAD^{tree}) && + pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) && + + # Corrupt the first byte of the first object. (It contains 3 type bits, + # at least one of which is not zero, so setting the first byte to 0 is + # sufficient.) + chmod a+w .git/objects/pack/pack-$pack.pack && + printf '\0' | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 && + + test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && + remove_object $hsh && + test_must_fail git fsck 2>out && + test_i18ngrep "checksum mismatch" out +' + test_expect_success 'fsck finds problems in duplicate loose objects' ' rm -rf broken-duplicate && git init broken-duplicate && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index dd37ac47c5..9d707d2a40 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -166,10 +166,9 @@ test_expect_success 'resulting reflog can be shown by log -g' ' oid=$(git rev-parse HEAD) && cat >expect <<-EOF && HEAD@{0} $oid $msg - HEAD@{1} $oid $msg HEAD@{2} $oid checkout: moving from foo to baz EOF - git log -g --format="%gd %H %gs" -3 HEAD >actual && + git log -g --format="%gd %H %gs" -2 HEAD >actual && test_cmp expect actual ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index a428ae6703..d2aec0f38b 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -2,6 +2,7 @@ test_description='git branch display tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh test_expect_success 'make commits' ' echo content >file && @@ -239,4 +240,34 @@ test_expect_success 'git branch --format option' ' test_i18ncmp expect actual ' +test_expect_success "set up color tests" ' + echo "<RED>master<RESET>" >expect.color && + echo "master" >expect.bare && + color_args="--format=%(color:red)%(refname:short) --list master" +' + +test_expect_success '%(color) omitted without tty' ' + TERM=vt100 git branch $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.bare actual +' + +test_expect_success TTY '%(color) present with tty' ' + test_terminal env TERM=vt100 git branch $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success 'color.branch=always overrides auto-color' ' + git -c color.branch=always branch $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success '--color overrides auto-color' ' + git branch --color $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 4428b9086e..fcfdd197bd 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -40,25 +40,6 @@ test_expect_success 'non-interactive rebase --continue works with touched file' git rebase --continue ' -test_expect_success 'non-interactive rebase --continue with rerere enabled' ' - test_config rerere.enabled true && - test_when_finished "test_might_fail git rebase --abort" && - git reset --hard commit-new-file-F2-on-topic-branch && - git checkout master && - rm -fr .git/rebase-* && - - test_must_fail git rebase --onto master master topic && - echo "Resolved" >F2 && - git add F2 && - cp F2 F2.expected && - git rebase --continue && - - git reset --hard commit-new-file-F2-on-topic-branch && - git checkout master && - test_must_fail git rebase --onto master master topic && - test_cmp F2.expected F2 -' - test_expect_success 'rebase --continue can not be used with other options' ' test_must_fail git rebase -v --continue && test_must_fail git rebase --continue -v @@ -93,25 +74,75 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test -f funny.was.run ' -test_expect_success 'rebase --continue remembers --rerere-autoupdate' ' +test_expect_success 'setup rerere database' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && git checkout master && test_commit "commit-new-file-F3" F3 3 && - git config rerere.enabled true && + test_config rerere.enabled true && test_must_fail git rebase -m master topic && echo "Resolved" >F2 && + cp F2 expected-F2 && git add F2 && test_must_fail git rebase --continue && echo "Resolved" >F3 && + cp F3 expected-F3 && git add F3 && git rebase --continue && - git reset --hard topic@{1} && - test_must_fail git rebase -m --rerere-autoupdate master && - test "$(cat F2)" = "Resolved" && - test_must_fail git rebase --continue && - test "$(cat F3)" = "Resolved" && - git rebase --continue + git reset --hard topic@{1} ' +prepare () { + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + git checkout master && + test_config rerere.enabled true +} + +test_rerere_autoupdate () { + action=$1 && + test_expect_success "rebase $action --continue remembers --rerere-autoupdate" ' + prepare && + test_must_fail git rebase $action --rerere-autoupdate master topic && + test_cmp expected-F2 F2 && + git diff-files --quiet && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + git diff-files --quiet && + git rebase --continue + ' + + test_expect_success "rebase $action --continue honors rerere.autoUpdate" ' + prepare && + test_config rerere.autoupdate true && + test_must_fail git rebase $action master topic && + test_cmp expected-F2 F2 && + git diff-files --quiet && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + git diff-files --quiet && + git rebase --continue + ' + + test_expect_success "rebase $action --continue remembers --no-rerere-autoupdate" ' + prepare && + test_config rerere.autoupdate true && + test_must_fail git rebase $action --no-rerere-autoupdate master topic && + test_cmp expected-F2 F2 && + test_must_fail git diff-files --quiet && + git add F2 && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + test_must_fail git diff-files --quiet && + git add F3 && + git rebase --continue + ' +} + +test_rerere_autoupdate +test_rerere_autoupdate -m +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR +test_rerere_autoupdate -i +test_rerere_autoupdate --preserve-merges + test_done diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh index e6a64816ef..a267b2d144 100755 --- a/t/t3504-cherry-pick-rerere.sh +++ b/t/t3504-cherry-pick-rerere.sh @@ -5,14 +5,13 @@ test_description='cherry-pick should rerere for conflicts' . ./test-lib.sh test_expect_success setup ' - echo foo >foo && - git add foo && test_tick && git commit -q -m 1 && - echo foo-master >foo && - git add foo && test_tick && git commit -q -m 2 && - - git checkout -b dev HEAD^ && - echo foo-dev >foo && - git add foo && test_tick && git commit -q -m 3 && + test_commit foo && + test_commit foo-master foo && + test_commit bar-master bar && + + git checkout -b dev foo && + test_commit foo-dev foo && + test_commit bar-dev bar && git config rerere.enabled true ' @@ -21,23 +20,80 @@ test_expect_success 'conflicting merge' ' ' test_expect_success 'fixup' ' - echo foo-dev >foo && - git add foo && test_tick && git commit -q -m 4 && - git reset --hard HEAD^ && - echo foo-dev >expect + echo foo-resolved >foo && + echo bar-resolved >bar && + git commit -am resolved && + cp foo foo-expect && + cp bar bar-expect && + git reset --hard HEAD^ ' -test_expect_success 'cherry-pick conflict' ' - test_must_fail git cherry-pick master && - test_cmp expect foo +test_expect_success 'cherry-pick conflict with --rerere-autoupdate' ' + test_must_fail git cherry-pick --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + git diff-files --quiet && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick conflict repsects rerere.autoUpdate' ' + test_config rerere.autoUpdate true && + test_must_fail git cherry-pick foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + git diff-files --quiet && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick conflict with --no-rerere-autoupdate' ' + test_config rerere.autoUpdate true && + test_must_fail git cherry-pick --no-rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + test_must_fail git diff-files --quiet && + git add foo && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + test_must_fail git diff-files --quiet && + git add bar && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick --continue rejects --rerere-autoupdate' ' + test_must_fail git cherry-pick --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue --rerere-autoupdate >actual 2>&1 && + echo "fatal: cherry-pick: --rerere-autoupdate cannot be used with --continue" >expect && + test_i18ncmp expect actual && + test_must_fail git cherry-pick --continue --no-rerere-autoupdate >actual 2>&1 && + echo "fatal: cherry-pick: --no-rerere-autoupdate cannot be used with --continue" >expect && + test_i18ncmp expect actual && + git cherry-pick --abort ' -test_expect_success 'reconfigure' ' - git config rerere.enabled false && - git reset --hard +test_expect_success 'cherry-pick --rerere-autoupdate more than once' ' + test_must_fail git cherry-pick --rerere-autoupdate --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + git cherry-pick --abort && + test_must_fail git cherry-pick --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + git cherry-pick --abort && + test_must_fail git cherry-pick --rerere-autoupdate --no-rerere-autoupdate foo..bar-master && + test_must_fail git diff-files --quiet && + git cherry-pick --abort ' test_expect_success 'cherry-pick conflict without rerere' ' + test_config rerere.enabled false && test_must_fail git cherry-pick master && test_must_fail test_cmp expect foo ' diff --git a/t/t4062-diff-pickaxe.sh b/t/t4062-diff-pickaxe.sh index 7c4903f497..1130c8019b 100755 --- a/t/t4062-diff-pickaxe.sh +++ b/t/t4062-diff-pickaxe.sh @@ -14,8 +14,10 @@ test_expect_success setup ' test_tick && git commit -m "A 4k file" ' + +# OpenBSD only supports up to 255 repetitions, so repeat twice for 64*64=4096. test_expect_success '-G matches' ' - git diff --name-only -G "^0{4096}$" HEAD^ >out && + git diff --name-only -G "^(0{64}){64}$" HEAD^ >out && test 4096-zeroes.txt = "$(cat out)" ' diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 44807e218d..73b67b4280 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -40,6 +40,8 @@ test_expect_success 'setup: messages' ' dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + + Reported-by: A N Other <a.n.other@example.com> EOF cat >failmail <<-\EOF && @@ -93,7 +95,7 @@ test_expect_success setup ' echo world >>file && git add file && test_tick && - git commit -s -F msg && + git commit -F msg && git tag second && git format-patch --stdout first >patch1 && @@ -124,8 +126,6 @@ test_expect_success setup ' echo "Date: $GIT_AUTHOR_DATE" && echo && sed -e "1,2d" msg && - echo && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && echo "---" && git diff-tree --no-commit-id --stat -p second } >patch1-stgit.eml && @@ -144,8 +144,6 @@ test_expect_success setup ' echo "# Parent $_z40" && cat msg && echo && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && - echo && git diff-tree --no-commit-id -p second } >patch1-hg.eml && @@ -470,13 +468,15 @@ test_expect_success 'am --signoff adds Signed-off-by: line' ' git reset --hard && git checkout -b master2 first && git am --signoff <patch2 && - printf "%s\n" "$signoff" >expected && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected && - git cat-file commit HEAD^ | grep "Signed-off-by:" >actual && - test_cmp expected actual && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected && - git cat-file commit HEAD | grep "Signed-off-by:" >actual && - test_cmp expected actual + { + printf "third\n\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" + } >expected-log && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual ' test_expect_success 'am stays in branch' ' @@ -486,17 +486,60 @@ test_expect_success 'am stays in branch' ' ' test_expect_success 'am --signoff does not add Signed-off-by: line if already there' ' - git format-patch --stdout HEAD^ >patch3 && - sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 && - rm -fr .git/rebase-apply && - git reset --hard && - git checkout HEAD^ && - git am --signoff patch4 && - git cat-file commit HEAD >actual && - test $(grep -c "^Signed-off-by:" actual) -eq 1 + git format-patch --stdout first >patch3 && + git reset --hard first && + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual +' + +test_expect_success 'am --signoff adds Signed-off-by: if another author is preset' ' + NAME="A N Other" && + EMAIL="a.n.other@example.com" && + { + printf "third\n\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" + } >expected-log && + git reset --hard first && + GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \ + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual +' + +test_expect_success 'am --signoff duplicates Signed-off-by: if it is not the last one' ' + NAME="A N Other" && + EMAIL="a.n.other@example.com" && + { + printf "third\n\nSigned-off-by: %s <%s>\n\ +Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\ +Signed-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" + } >expected-log && + git format-patch --stdout first >patch3 && + git reset --hard first && + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual ' test_expect_success 'am without --keep removes Re: and [PATCH] stuff' ' + git format-patch --stdout HEAD^ >tmp && + sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 && + git reset --hard HEAD^ && + git am <patch4 && git rev-parse HEAD >expected && git rev-parse master2 >actual && test_cmp expected actual diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 3f3531f0a4..36d120c969 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1523,6 +1523,12 @@ test_expect_success 'log diagnoses bogus HEAD' ' test_i18ngrep broken stderr ' +test_expect_success 'log does not default to HEAD when rev input is given' ' + >expect && + git log --branches=does-not-exist >actual && + test_cmp expect actual +' + test_expect_success 'set up --source tests' ' git checkout --orphan source-a && test_commit one && diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index b972296f06..60f040cab8 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -7,11 +7,6 @@ test_description='Test for "git log --decorate" colors' . ./test-lib.sh -get_color () -{ - git config --get-color no.such.slot "$1" -} - test_expect_success setup ' git config diff.color.commit yellow && git config color.decorate.branch green && @@ -20,14 +15,14 @@ test_expect_success setup ' git config color.decorate.stash magenta && git config color.decorate.HEAD cyan && - c_reset=$(get_color reset) && + c_reset="<RESET>" && - c_commit=$(get_color yellow) && - c_branch=$(get_color green) && - c_remoteBranch=$(get_color red) && - c_tag=$(get_color "reverse bold yellow") && - c_stash=$(get_color magenta) && - c_HEAD=$(get_color cyan) && + c_commit="<YELLOW>" && + c_branch="<GREEN>" && + c_remoteBranch="<RED>" && + c_tag="<BOLD;REVERSE;YELLOW>" && + c_stash="<MAGENTA>" && + c_HEAD="<CYAN>" && test_commit A && git clone . other && @@ -59,7 +54,8 @@ EOF # to this test since it does not contain any decoration, hence --first-parent test_expect_success 'Commit Decorations Colored Correctly' ' git log --first-parent --abbrev=10 --all --decorate --oneline --color=always | - sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" >out && + sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" | + test_decode_color >out && test_cmp expected out ' diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index beff65b8ac..0f84a53146 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -533,7 +533,8 @@ test_expect_success 'push propagating refspec to a submodule' ' # Fails when refspec includes an object id test_must_fail git -C work push --recurse-submodules=on-demand origin \ "$(git -C work rev-parse branch2):refs/heads/branch2" && - # Fails when refspec includes 'HEAD' as it is unsupported at this time + # Fails when refspec includes HEAD and parent and submodule do not + # have the same named branch checked out test_must_fail git -C work push --recurse-submodules=on-demand origin \ HEAD:refs/heads/branch2 && @@ -548,4 +549,26 @@ test_expect_success 'push propagating refspec to a submodule' ' test_cmp expected_pub actual_pub ' +test_expect_success 'push propagating HEAD refspec to a submodule' ' + git -C work/gar/bage checkout branch2 && + > work/gar/bage/junk12 && + git -C work/gar/bage add junk12 && + git -C work/gar/bage commit -m "Twelfth junk" && + + git -C work checkout branch2 && + git -C work add gar/bage && + git -C work commit -m "updating gar/bage in branch2" && + + # Passes since the superproject and submodules HEAD are both on branch2 + git -C work push --recurse-submodules=on-demand origin \ + HEAD:refs/heads/branch2 && + + git -C submodule.git rev-parse branch2 >actual_submodule && + git -C pub.git rev-parse branch2 >actual_pub && + git -C work/gar/bage rev-parse branch2 >expected_submodule && + git -C work rev-parse branch2 >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index a1dcdb81d7..b326d550f3 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -59,10 +59,14 @@ test_format () { } # Feed to --format to provide predictable colored sequences. +BASIC_COLOR='%Credfoo%Creset' +COLOR='%C(red)foo%C(reset)' AUTO_COLOR='%C(auto,red)foo%C(auto,reset)' +ALWAYS_COLOR='%C(always,red)foo%C(always,reset)' has_color () { - printf '\033[31mfoo\033[m\n' >expect && - test_cmp expect "$1" + test_decode_color <"$1" >decoded && + echo "<RED>foo<RESET>" >expect && + test_cmp expect decoded } has_no_color () { @@ -170,62 +174,84 @@ $added EOF -test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<EOF -commit $head2 -[31mfoo[32mbar[34mbaz[mxyzzy -commit $head1 -[31mfoo[32mbar[34mbaz[mxyzzy -EOF - -test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<EOF -commit $head2 -[1;31;43mfoo[m -commit $head1 -[1;31;43mfoo[m -EOF - -test_expect_success '%C(auto,...) does not enable color by default' ' - git log --format=$AUTO_COLOR -1 >actual && - has_no_color actual +test_expect_success 'basic colors' ' + cat >expect <<-EOF && + commit $head2 + <RED>foo<GREEN>bar<BLUE>baz<RESET>xyzzy + EOF + format="%Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy" && + git rev-list --color --format="$format" -1 master >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual ' -test_expect_success '%C(auto,...) enables colors for color.diff' ' - git -c color.diff=always log --format=$AUTO_COLOR -1 >actual && - has_color actual +test_expect_success 'advanced colors' ' + cat >expect <<-EOF && + commit $head2 + <BOLD;RED;BYELLOW>foo<RESET> + EOF + format="%C(red yellow bold)foo%C(reset)" && + git rev-list --color --format="$format" -1 master >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual ' -test_expect_success '%C(auto,...) enables colors for color.ui' ' - git -c color.ui=always log --format=$AUTO_COLOR -1 >actual && - has_color actual -' +for spec in \ + "%Cred:$BASIC_COLOR" \ + "%C(...):$COLOR" \ + "%C(auto,...):$AUTO_COLOR" +do + desc=${spec%%:*} + color=${spec#*:} + test_expect_success "$desc does not enable color by default" ' + git log --format=$color -1 >actual && + has_no_color actual + ' -test_expect_success '%C(auto,...) respects --color' ' - git log --format=$AUTO_COLOR -1 --color >actual && - has_color actual -' + test_expect_success "$desc enables colors for color.diff" ' + git -c color.diff=always log --format=$color -1 >actual && + has_color actual + ' -test_expect_success '%C(auto,...) respects --no-color' ' - git -c color.ui=always log --format=$AUTO_COLOR -1 --no-color >actual && - has_no_color actual -' + test_expect_success "$desc enables colors for color.ui" ' + git -c color.ui=always log --format=$color -1 >actual && + has_color actual + ' -test_expect_success TTY '%C(auto,...) respects --color=auto (stdout is tty)' ' - test_terminal env TERM=vt100 \ - git log --format=$AUTO_COLOR -1 --color=auto >actual && - has_color actual -' + test_expect_success "$desc respects --color" ' + git log --format=$color -1 --color >actual && + has_color actual + ' -test_expect_success '%C(auto,...) respects --color=auto (stdout not tty)' ' - ( - TERM=vt100 && export TERM && - git log --format=$AUTO_COLOR -1 --color=auto >actual && + test_expect_success "$desc respects --no-color" ' + git -c color.ui=always log --format=$color -1 --no-color >actual && has_no_color actual - ) + ' + + test_expect_success TTY "$desc respects --color=auto (stdout is tty)" ' + test_terminal env TERM=vt100 \ + git log --format=$color -1 --color=auto >actual && + has_color actual + ' + + test_expect_success "$desc respects --color=auto (stdout not tty)" ' + ( + TERM=vt100 && export TERM && + git log --format=$color -1 --color=auto >actual && + has_no_color actual + ) + ' +done + +test_expect_success '%C(always,...) enables color even without tty' ' + git log --format=$ALWAYS_COLOR -1 >actual && + has_color actual ' test_expect_success '%C(auto) respects --color' ' - git log --color --format="%C(auto)%H" -1 >actual && - printf "\\033[33m%s\\033[m\\n" $(git rev-parse HEAD) >expect && + git log --color --format="%C(auto)%H" -1 >actual.raw && + test_decode_color <actual.raw >actual && + echo "<YELLOW>$(git rev-parse HEAD)<RESET>" >expect && test_cmp expect actual ' @@ -235,6 +261,17 @@ test_expect_success '%C(auto) respects --no-color' ' test_cmp expect actual ' +test_expect_success 'rev-list %C(auto,...) respects --color' ' + git rev-list --color --format="%C(auto,green)foo%C(auto,reset)" \ + -1 HEAD >actual.raw && + test_decode_color <actual.raw >actual && + cat >expect <<-EOF && + commit $(git rev-parse HEAD) + <GREEN>foo<RESET> + EOF + test_cmp expect actual +' + iconv -f utf-8 -t $test_encoding > commit-msg <<EOF Test printing of complex bodies diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index 381f35ed16..d3453c583c 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -255,27 +255,19 @@ test_expect_success 'rev-list accumulates multiple --exclude' ' compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches ' - -# "git rev-list<ENTER>" is likely to be a bug in the calling script and may -# deserve an error message, but do cases where set of refs programmatically -# given using globbing and/or --stdin need to fail with the same error, or -# are we better off reporting a success with no output? The following few -# tests document the current behaviour to remind us that we might want to -# think about this issue. - -test_expect_failure 'rev-list may want to succeed with empty output on no input (1)' ' +test_expect_failure 'rev-list should succeed with empty output on empty stdin' ' >expect && git rev-list --stdin <expect >actual && test_cmp expect actual ' -test_expect_failure 'rev-list may want to succeed with empty output on no input (2)' ' +test_expect_success 'rev-list should succeed with empty output with all refs excluded' ' >expect && git rev-list --exclude=* --all >actual && test_cmp expect actual ' -test_expect_failure 'rev-list may want to succeed with empty output on no input (3)' ' +test_expect_success 'rev-list should succeed with empty output with empty --all' ' ( test_create_repo empty && cd empty && @@ -285,6 +277,12 @@ test_expect_failure 'rev-list may want to succeed with empty output on no input ) ' +test_expect_success 'rev-list should succeed with empty output with empty glob' ' + >expect && + git rev-list --glob=does-not-match-anything >actual && + test_cmp expect actual +' + test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' compare shortlog "subspace/one subspace/two" --branches=subspace && diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 834a9ed168..2274a4b733 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -7,6 +7,7 @@ test_description='for-each-ref test' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh # Mon Jul 3 23:18:43 2006 +0000 datestamp=1151968723 @@ -412,21 +413,33 @@ test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' -get_color () -{ - git config --get-color no.such.slot "$1" -} +test_expect_success 'set up color tests' ' + cat >expected.color <<-EOF && + $(git rev-parse --short refs/heads/master) <GREEN>master<RESET> + $(git rev-parse --short refs/remotes/origin/master) <GREEN>origin/master<RESET> + $(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET> + $(git rev-parse --short refs/tags/two) <GREEN>two<RESET> + EOF + sed "s/<[^>]*>//g" <expected.color >expected.bare && + color_format="%(objectname:short) %(color:green)%(refname:short)" +' -cat >expected <<EOF -$(git rev-parse --short refs/heads/master) $(get_color green)master$(get_color reset) -$(git rev-parse --short refs/remotes/origin/master) $(get_color green)origin/master$(get_color reset) -$(git rev-parse --short refs/tags/testtag) $(get_color green)testtag$(get_color reset) -$(git rev-parse --short refs/tags/two) $(get_color green)two$(get_color reset) -EOF +test_expect_success TTY '%(color) shows color with a tty' ' + test_terminal env TERM=vt100 \ + git for-each-ref --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual +' -test_expect_success 'Check %(color:...) ' ' - git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual && - test_cmp expected actual +test_expect_success '%(color) does not show color without tty' ' + TERM=vt100 git for-each-ref --format="$color_format" >actual && + test_cmp expected.bare actual +' + +test_expect_success 'color.ui=always can override tty check' ' + git -c color.ui=always for-each-ref --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual ' cat >expected <<\EOF diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 0ef7b94394..dd5ba450ee 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -9,6 +9,7 @@ Tests for operations with tags.' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh # creating and listing lightweight tags: @@ -1900,6 +1901,30 @@ test_expect_success '--format should list tags as per format given' ' test_cmp expect actual ' +test_expect_success "set up color tests" ' + echo "<RED>v1.0<RESET>" >expect.color && + echo "v1.0" >expect.bare && + color_args="--format=%(color:red)%(refname:short) --list v1.0" +' + +test_expect_success '%(color) omitted without tty' ' + TERM=vt100 git tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.bare actual +' + +test_expect_success TTY '%(color) present with tty' ' + test_terminal env TERM=vt100 git tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success 'color.ui=always overrides auto-color' ' + git -c color.ui=always tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + test_expect_success 'setup --merged test tags' ' git tag mergetest-1 HEAD~2 && git tag mergetest-2 HEAD~1 && diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 20b4d83c28..9128ec5acd 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -134,6 +134,86 @@ test_expect_success TTY 'configuration can enable pager (from subdir)' ' } ' +test_expect_success TTY 'git tag -l defaults to paging' ' + rm -f paginated.out && + test_terminal git tag -l && + test -e paginated.out +' + +test_expect_success TTY 'git tag -l respects pager.tag' ' + rm -f paginated.out && + test_terminal git -c pager.tag=false tag -l && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -l respects --no-pager' ' + rm -f paginated.out && + test_terminal git -c pager.tag --no-pager tag -l && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag with no args defaults to paging' ' + # no args implies -l so this should page like -l + rm -f paginated.out && + test_terminal git tag && + test -e paginated.out +' + +test_expect_success TTY 'git tag with no args respects pager.tag' ' + # no args implies -l so this should page like -l + rm -f paginated.out && + test_terminal git -c pager.tag=false tag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag --contains defaults to paging' ' + # --contains implies -l so this should page like -l + rm -f paginated.out && + test_terminal git tag --contains && + test -e paginated.out +' + +test_expect_success TTY 'git tag --contains respects pager.tag' ' + # --contains implies -l so this should page like -l + rm -f paginated.out && + test_terminal git -c pager.tag=false tag --contains && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a defaults to not paging' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git tag -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a ignores pager.tag' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git -c pager.tag tag -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a respects --paginate' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git --paginate tag -am message newtag && + test -e paginated.out +' + +test_expect_success TTY 'git tag as alias ignores pager.tag with -a' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git -c pager.tag -c alias.t=tag t -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag as alias respects pager.tag with -l' ' + rm -f paginated.out && + test_terminal git -c pager.tag=false -c alias.t=tag t -l && + ! test -e paginated.out +' + # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index dcac364c5f..e9c3335b78 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -1289,4 +1289,10 @@ test_expect_success 'init properly sets the config' ' test_must_fail git -C multisuper_clone config --get submodule.sub1.active ' +test_expect_success 'recursive clone respects -q' ' + test_when_finished "rm -rf multisuper_clone" && + git clone -q --recurse-submodules multisuper multisuper_clone >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index f106387820..2a6679c2f5 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -374,6 +374,11 @@ test_expect_success 'grep -L -C' ' test_cmp expected actual ' +test_expect_success 'grep --files-without-match --quiet' ' + git grep --files-without-match --quiet nonexistent_string >actual && + test_cmp /dev/null actual +' + cat >expected <<EOF file:foo mmap bar_mmap EOF diff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh index 92c8e792d1..ae4b579d24 100755 --- a/t/t8008-blame-formats.sh +++ b/t/t8008-blame-formats.sh @@ -12,22 +12,25 @@ test_expect_success 'setup' ' echo c >>file && echo d >>file && test_tick && - git commit -a -m two + git commit -a -m two && + ID1=$(git rev-parse HEAD^) && + shortID1="^$(git rev-parse HEAD^ |cut -c 1-17)" && + ID2=$(git rev-parse HEAD) && + shortID2="$(git rev-parse HEAD |cut -c 1-18)" ' -cat >expect <<'EOF' -^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a -8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b -8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c -8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d +cat >expect <<EOF +$shortID1 (A U Thor 2005-04-07 15:13:13 -0700 1) a +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 2) b +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 3) c +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 4) d EOF test_expect_success 'normal blame output' ' - git blame file >actual && + git blame --abbrev=17 file >actual && test_cmp expect actual ' -ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc -COMMIT1='author A U Thor +COMMIT1="author A U Thor author-mail <author@example.com> author-time 1112911993 author-tz -0700 @@ -37,9 +40,8 @@ committer-time 1112911993 committer-tz -0700 summary one boundary -filename file' -ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec -COMMIT2='author A U Thor +filename file" +COMMIT2="author A U Thor author-mail <author@example.com> author-time 1112912053 author-tz -0700 @@ -48,8 +50,8 @@ committer-mail <committer@example.com> committer-time 1112912053 committer-tz -0700 summary two -previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file -filename file' +previous $ID1 file +filename file" cat >expect <<EOF $ID1 1 1 1 diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 50a9a1d1c4..1701fe2a06 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -42,6 +42,7 @@ test_decode_color () { function name(n) { if (n == 0) return "RESET"; if (n == 1) return "BOLD"; + if (n == 7) return "REVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; if (n == 32) return "GREEN"; diff --git a/unpack-trees.c b/unpack-trees.c index dd535bc849..862cfce661 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -380,6 +380,7 @@ static int check_updates(struct unpack_trees_options *o) if (should_update_submodules() && o->update && !o->dry_run) reload_gitmodules_file(index, &state); + enable_delayed_checkout(&state); for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; @@ -394,6 +395,7 @@ static int check_updates(struct unpack_trees_options *o) } } } + errs |= finish_delayed_checkout(&state); stop_progress(&progress); if (o->update) git_attr_set_direction(GIT_ATTR_CHECKIN, NULL); |