diff options
172 files changed, 2927 insertions, 1440 deletions
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt index ca267941f3..8d9e85566e 100644 --- a/Documentation/MyFirstObjectWalk.txt +++ b/Documentation/MyFirstObjectWalk.txt @@ -522,24 +522,25 @@ function shows that the all-object walk is being performed by `traverse_commit_list()` or `traverse_commit_list_filtered()`. Those two functions reside in `list-objects.c`; examining the source shows that, despite the name, these functions traverse all kinds of objects. Let's have a look at -the arguments to `traverse_commit_list_filtered()`, which are a superset of the -arguments to the unfiltered version. +the arguments to `traverse_commit_list()`. -- `struct list_objects_filter_options *filter_options`: This is a struct which - stores a filter-spec as outlined in `Documentation/rev-list-options.txt`. -- `struct rev_info *revs`: This is the `rev_info` used for the walk. +- `struct rev_info *revs`: This is the `rev_info` used for the walk. If + its `filter` member is not `NULL`, then `filter` contains information for + how to filter the object list. - `show_commit_fn show_commit`: A callback which will be used to handle each individual commit object. - `show_object_fn show_object`: A callback which will be used to handle each non-commit object (so each blob, tree, or tag). - `void *show_data`: A context buffer which is passed in turn to `show_commit` and `show_object`. + +In addition, `traverse_commit_list_filtered()` has an additional paramter: + - `struct oidset *omitted`: A linked-list of object IDs which the provided filter caused to be omitted. -It looks like this `traverse_commit_list_filtered()` uses callbacks we provide -instead of needing us to call it repeatedly ourselves. Cool! Let's add the -callbacks first. +It looks like these methods use callbacks we provide instead of needing us +to call it repeatedly ourselves. Cool! Let's add the callbacks first. For the sake of this tutorial, we'll simply keep track of how many of each kind of object we find. At file scope in `builtin/walken.c` add the following @@ -712,20 +713,9 @@ help understand. In our case, that means we omit trees and blobs not directly referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only `HEAD` in the `pending` list.) -First, we'll need to `#include "list-objects-filter-options.h"` and set up the -`struct list_objects_filter_options` at the top of the function. - ----- -static void walken_object_walk(struct rev_info *rev) -{ - struct list_objects_filter_options filter_options = { 0 }; - - ... ----- - For now, we are not going to track the omitted objects, so we'll replace those parameters with `NULL`. For the sake of simplicity, we'll add a simple -build-time branch to use our filter or not. Replace the line calling +build-time branch to use our filter or not. Preface the line calling `traverse_commit_list()` with the following, which will remind us which kind of walk we've just performed: @@ -733,19 +723,17 @@ walk we've just performed: if (0) { /* Unfiltered: */ trace_printf(_("Unfiltered object walk.\n")); - traverse_commit_list(rev, walken_show_commit, - walken_show_object, NULL); } else { trace_printf( _("Filtered object walk with filterspec 'tree:1'.\n")); - parse_list_objects_filter(&filter_options, "tree:1"); - - traverse_commit_list_filtered(&filter_options, rev, - walken_show_commit, walken_show_object, NULL, NULL); + CALLOC_ARRAY(rev->filter, 1); + parse_list_objects_filter(rev->filter, "tree:1"); } + traverse_commit_list(rev, walken_show_commit, + walken_show_object, NULL); ---- -`struct list_objects_filter_options` is usually built directly from a command +The `rev->filter` member is usually built directly from a command line argument, so the module provides an easy way to build one from a string. Even though we aren't taking user input right now, we can still build one with a hardcoded string using `parse_list_objects_filter()`. @@ -784,7 +772,7 @@ object: ---- ... - traverse_commit_list_filtered(&filter_options, rev, + traverse_commit_list_filtered(rev, walken_show_commit, walken_show_object, NULL, &omitted); ... diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt index 6b2c6bfcc7..f1449eb926 100644 --- a/Documentation/RelNotes/2.36.0.txt +++ b/Documentation/RelNotes/2.36.0.txt @@ -70,6 +70,14 @@ UI, Workflows & Features * The level of verbose output from the ort backend during inner merge has been aligned to that of the recursive backend. + * "git remote rename A B", depending on the number of remote-tracking + refs involved, takes long time renaming them. The command has been + taught to show progress bar while making the user wait. + + * Bundle file format gets extended to allow a partial bundle, + filtered by similar criteria you would give when making a + partial/lazy clone. + Performance, Internal Implementation, Development Support etc. @@ -122,6 +130,18 @@ Performance, Internal Implementation, Development Support etc. * Makefile refactoring with a bit of suffixes rule stripping to optimize the runtime overhead. + * "git stash drop" is reimplemented as an internal call to + reflog_delete() function, instead of invoking "git reflog delete" + via run_command() API. + + * Count string_list items in size_t, not "unsigned int". + + * The single-key interactive operation used by "git add -p" has been + made more robust. + + * Remove unneeded <meta http-equiv=content-type...> from gitweb + output. + Fixes since v2.35 ----------------- @@ -299,6 +319,21 @@ Fixes since v2.35 Adjustments have been made to accommodate these changes. (merge b0b70d54c4 fs/gpgsm-update later to maint). + * The untracked cache newly computed weren't written back to the + on-disk index file when there is no other change to the index, + which has been corrected. + + * "git config -h" did not describe the "--type" option correctly. + (merge 5445124fad mf/fix-type-in-config-h later to maint). + + * The way generation number v2 in the commit-graph files are + (not) handled has been corrected. + (merge 6dbf4b8172 ds/commit-graph-gen-v2-fixes later to maint). + + * The method to trigger malloc check used in our tests no longer work + with newer versions of glibc. + (merge baedc59543 ep/test-malloc-check-with-glibc-2.34 later to maint). + * Other code cleanup, docfix, build fix, etc. (merge cfc5cf428b jc/find-header later to maint). (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint). @@ -324,3 +359,4 @@ Fixes since v2.35 (merge 04bf052eef ab/grep-patterntype later to maint). (merge 6ee36364eb ab/diff-free-more later to maint). (merge 63a36017fe nj/read-tree-doc-reffix later to maint). + (merge eed36fce38 sm/no-git-in-upstream-of-pipe-in-tests later to maint). diff --git a/Documentation/config/repack.txt b/Documentation/config/repack.txt index 9c413e177e..41ac6953c8 100644 --- a/Documentation/config/repack.txt +++ b/Documentation/config/repack.txt @@ -25,3 +25,8 @@ repack.writeBitmaps:: space and extra time spent on the initial repack. This has no effect if multiple packfiles are created. Defaults to true on bare repos, false otherwise. + +repack.updateServerInfo:: + If set to false, linkgit:git-repack[1] will not run + linkgit:git-update-server-info[1]. Defaults to true. Can be overridden + when true by the `-n` option of linkgit:git-repack[1]. diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 72ab813905..ac4c4352aa 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -75,8 +75,11 @@ verify <file>:: cleanly to the current repository. This includes checks on the bundle format itself as well as checking that the prerequisite commits exist and are fully linked in the current repository. - 'git bundle' prints a list of missing commits, if any, and exits - with a non-zero status. + Information about additional capabilities, such as "object filter", + is printed. See "Capabilities" in link:technical/bundle-format.html + for more information. Finally, 'git bundle' prints a list of + missing commits, if any. The exit code is zero for success, but + will be nonzero if the bundle file is invalid. list-heads <file>:: Lists the references defined in the bundle. If followed by a diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 1f1e359225..4e71c256ec 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -122,6 +122,14 @@ This option cannot be used with --stdin. + include::object-format-disclaimer.txt[] +--promisor[=<message>]:: + Before committing the pack-index, create a .promisor file for this + pack. Particularly helpful when writing a promisor pack with --fix-thin + since the name of the pack is not final until the pack has been fully + written. If a `<message>` is provided, then that content will be + written to the .promisor file for future reference. See + link:technical/partial-clone.html[partial clone] for more information. + NOTES ----- diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index e2cfb68ab5..e56bad28c6 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -10,6 +10,8 @@ SYNOPSIS -------- [verse] 'git maintenance' run [<options>] +'git maintenance' start [--scheduler=<scheduler>] +'git maintenance' (stop|register|unregister) DESCRIPTION @@ -29,6 +31,24 @@ Git repository. SUBCOMMANDS ----------- +run:: + Run one or more maintenance tasks. If one or more `--task` options + are specified, then those tasks are run in that order. Otherwise, + the tasks are determined by which `maintenance.<task>.enabled` + config options are true. By default, only `maintenance.gc.enabled` + is true. + +start:: + Start running maintenance on the current repository. This performs + the same config updates as the `register` subcommand, then updates + the background scheduler to run `git maintenance run --scheduled` + on an hourly basis. + +stop:: + Halt the background maintenance schedule. The current repository + is not removed from the list of maintained repositories, in case + the background maintenance is restarted later. + register:: Initialize Git config values so any scheduled maintenance will start running on this repository. This adds the repository to the @@ -55,24 +75,6 @@ task: setting `maintenance.auto = false` in the current repository. This config setting will remain after a `git maintenance unregister` command. -run:: - Run one or more maintenance tasks. If one or more `--task` options - are specified, then those tasks are run in that order. Otherwise, - the tasks are determined by which `maintenance.<task>.enabled` - config options are true. By default, only `maintenance.gc.enabled` - is true. - -start:: - Start running maintenance on the current repository. This performs - the same config updates as the `register` subcommand, then updates - the background scheduler to run `git maintenance run --scheduled` - on an hourly basis. - -stop:: - Halt the background maintenance schedule. The current repository - is not removed from the list of maintained repositories, in case - the background maintenance is restarted later. - unregister:: Remove the current repository from background maintenance. This only removes the repository from the configured list. It does not diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 2bebc32566..cde9614e36 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git remote' [-v | --verbose] 'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL> -'git remote rename' <old> <new> +'git remote rename' [--[no-]progress] <old> <new> 'git remote remove' <name> 'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>) 'git remote set-branches' [--add] <name> <branch>... diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index a71dad2674..4b36d51beb 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -829,6 +829,8 @@ patterns are available: - `java` suitable for source code in the Java language. +- `kotlin` suitable for source code in the Kotlin language. + - `markdown` suitable for Markdown documents. - `matlab` suitable for source code in the MATLAB and Octave languages. diff --git a/Documentation/technical/bundle-format.txt b/Documentation/technical/bundle-format.txt index bac558d049..b9be8644cf 100644 --- a/Documentation/technical/bundle-format.txt +++ b/Documentation/technical/bundle-format.txt @@ -71,6 +71,11 @@ and the Git bundle v2 format cannot represent a shallow clone repository. == Capabilities Because there is no opportunity for negotiation, unknown capabilities cause 'git -bundle' to abort. The only known capability is `object-format`, which specifies -the hash algorithm in use, and can take the same values as the -`extensions.objectFormat` configuration value. +bundle' to abort. + +* `object-format` specifies the hash algorithm in use, and can take the same + values as the `extensions.objectFormat` configuration value. + +* `filter` specifies an object filter as in the `--filter` option in + linkgit:git-rev-list[1]. The resulting pack-file must be marked as a + `.promisor` pack-file after it is unbundled. diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index 87971c27dd..484b185ba9 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -93,7 +93,7 @@ CHUNK DATA: 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. - Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional] + Generation Data (ID: {'G', 'D', 'A', '2' }) (N * 4 bytes) [Optional] * This list of 4-byte values store corrected commit date offsets for the commits, arranged in the same order as commit data chunk. * If the corrected commit date offset cannot be stored within 31 bits, @@ -104,7 +104,7 @@ CHUNK DATA: by compatible versions of Git and in case of split commit-graph chains, the topmost layer also has Generation Data chunk. - Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + Generation Data Overflow (ID: {'G', 'D', 'O', '2' }) [Optional] * This list of 8-byte values stores the corrected commit date offsets for commits with corrected commit date offsets that cannot be stored within 31 bits. @@ -156,3 +156,11 @@ CHUNK DATA: TRAILER: H-byte HASH-checksum of all of the above. + +== Historical Notes: + +The Generation Data (GDA2) and Generation Data Overflow (GDO2) chunks have +the number '2' in their chunk IDs because a previous version of Git wrote +possibly erroneous data in these chunks with the IDs "GDAT" and "GDOV". By +changing the IDs, newer versions of Git will silently ignore those older +chunks and write the new information without trusting the incorrect data. @@ -1013,6 +1013,7 @@ LIB_OBJS += rebase-interactive.o LIB_OBJS += rebase.o LIB_OBJS += ref-filter.o LIB_OBJS += reflog-walk.o +LIB_OBJS += reflog.o LIB_OBJS += refs.o LIB_OBJS += refs/debug.o LIB_OBJS += refs/files-backend.o diff --git a/add-interactive.c b/add-interactive.c index e1ab39cce3..7247210301 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -70,6 +70,8 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) &s->interactive_diff_algorithm); git_config_get_bool("interactive.singlekey", &s->use_single_key); + if (s->use_single_key) + setbuf(stdin, NULL); } void clear_add_i_state(struct add_i_state *s) @@ -3164,7 +3164,7 @@ static int apply_binary(struct apply_state *state, * See if the old one matches what the patch * applies to. */ - hash_object_file(the_hash_algo, img->buf, img->len, blob_type, + hash_object_file(the_hash_algo, img->buf, img->len, OBJ_BLOB, &oid); if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix)) return error(_("the patch applies to '%s' (%s), " @@ -3210,7 +3210,7 @@ static int apply_binary(struct apply_state *state, name); /* verify that the result matches */ - hash_object_file(the_hash_algo, img->buf, img->len, blob_type, + hash_object_file(the_hash_algo, img->buf, img->len, OBJ_BLOB, &oid); if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix)) return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), @@ -3599,7 +3599,7 @@ static int try_threeway(struct apply_state *state, /* Preimage the patch was prepared for */ if (patch->is_new) - write_object_file("", 0, blob_type, &pre_oid); + write_object_file("", 0, OBJ_BLOB, &pre_oid); else if (get_oid(patch->old_oid_prefix, &pre_oid) || read_blob_object(&buf, &pre_oid, patch->old_mode)) return error(_("repository lacks the necessary blob to perform 3-way merge.")); @@ -3615,7 +3615,7 @@ static int try_threeway(struct apply_state *state, return -1; } /* post_oid is theirs */ - write_object_file(tmp_image.buf, tmp_image.len, blob_type, &post_oid); + write_object_file(tmp_image.buf, tmp_image.len, OBJ_BLOB, &post_oid); clear_image(&tmp_image); /* our_oid is ours */ @@ -3628,7 +3628,7 @@ static int try_threeway(struct apply_state *state, return error(_("cannot read the current contents of '%s'"), patch->old_name); } - write_object_file(tmp_image.buf, tmp_image.len, blob_type, &our_oid); + write_object_file(tmp_image.buf, tmp_image.len, OBJ_BLOB, &our_oid); clear_image(&tmp_image); /* in-core three-way merge between post and our using pre as base */ @@ -4328,7 +4328,7 @@ static int add_index_file(struct apply_state *state, } fill_stat_cache_info(state->repo->index, ce, &st); } - if (write_object_file(buf, size, blob_type, &ce->oid) < 0) { + if (write_object_file(buf, size, OBJ_BLOB, &ce->oid) < 0) { discard_cache_entry(ce); return error(_("unable to create backing store " "for newly created file %s"), path); @@ -14,7 +14,6 @@ #include "utf8.h" #include "quote.h" #include "thread-utils.h" -#include "dir.h" const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; @@ -121,7 +121,6 @@ struct git_attr; /* opaque structures used internally for attribute collection */ struct all_attrs_item; struct attr_stack; -struct index_state; /* * Given a string, return the gitattribute object that diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c index 1bb6e7c069..5974cd7dd3 100644 --- a/block-sha1/sha1.c +++ b/block-sha1/sha1.c @@ -11,27 +11,10 @@ #include "sha1.h" -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) - -/* - * Force usage of rol or ror by selecting the one with the smaller constant. - * It _can_ generate slightly smaller code (a constant of 1 is special), but - * perhaps more importantly it's possibly faster on any uarch that does a - * rotate with a loop. - */ - -#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }) -#define SHA_ROL(x,n) SHA_ASM("rol", x, n) -#define SHA_ROR(x,n) SHA_ASM("ror", x, n) - -#else - #define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) #define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) #define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) -#endif - /* * If you have 32 registers or more, the compiler can (and should) * try to change the array[] accesses into registers. However, on diff --git a/builtin/cat-file.c b/builtin/cat-file.c index e75e110302..50cf38999d 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -156,7 +156,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 0: - if (type_from_string(exp_type) == OBJ_BLOB) { + { + enum object_type exp_type_id = type_from_string(exp_type); + + if (exp_type_id == OBJ_BLOB) { struct object_id blob_oid; if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) { char *buffer = read_object_file(&oid, &type, @@ -178,10 +181,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(the_repository, - &oid, exp_type, &size, NULL); + buf = read_object_with_reference(the_repository, &oid, + exp_type_id, &size, NULL); break; - + } default: die("git cat-file: unknown option: %s", exp_type); } @@ -357,6 +360,13 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } } +static void print_default_format(struct strbuf *scratch, struct expand_data *data) +{ + strbuf_addf(scratch, "%s %s %"PRIuMAX"\n", oid_to_hex(&data->oid), + type_name(data->type), + (uintmax_t)data->size); +} + /* * If "pack" is non-NULL, then "offset" is the byte offset within the pack from * which the object may be accessed (though note that we may also rely on @@ -388,8 +398,14 @@ static void batch_object_write(const char *obj_name, } strbuf_reset(scratch); - strbuf_expand(scratch, opt->format, expand_format, data); - strbuf_addch(scratch, '\n'); + + if (!opt->format) { + print_default_format(scratch, data); + } else { + strbuf_expand(scratch, opt->format, expand_format, data); + strbuf_addch(scratch, '\n'); + } + batch_write(opt, scratch->buf, scratch->len); if (opt->batch_mode == BATCH_MODE_CONTENTS) { @@ -643,6 +659,8 @@ static void batch_objects_command(struct batch_options *opt, strbuf_release(&input); } +#define DEFAULT_FORMAT "%(objectname) %(objecttype) %(objectsize)" + static int batch_objects(struct batch_options *opt) { struct strbuf input = STRBUF_INIT; @@ -651,9 +669,6 @@ static int batch_objects(struct batch_options *opt) int save_warning; int retval = 0; - if (!opt->format) - opt->format = "%(objectname) %(objecttype) %(objectsize)"; - /* * Expand once with our special mark_query flag, which will prime the * object_info to be handed to oid_object_info_extended for each @@ -661,12 +676,17 @@ static int batch_objects(struct batch_options *opt) */ memset(&data, 0, sizeof(data)); data.mark_query = 1; - strbuf_expand(&output, opt->format, expand_format, &data); + strbuf_expand(&output, + opt->format ? opt->format : DEFAULT_FORMAT, + expand_format, + &data); data.mark_query = 0; strbuf_release(&output); if (opt->transform_mode) data.split_on_whitespace = 1; + if (opt->format && !strcmp(opt->format, DEFAULT_FORMAT)) + opt->format = NULL; /* * If we are printing out the object, then always fill in the type, * since we will want to decide whether or not to stream. diff --git a/builtin/checkout.c b/builtin/checkout.c index 1afc0773d5..797681481d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -303,7 +303,7 @@ static int checkout_merged(int pos, const struct checkout *state, * (it also writes the merge result to the object database even * when it may contain conflicts). */ - if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid)) + if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid)) die(_("Unable to add merge result for '%s'"), path); free(result_buf.ptr); ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool); diff --git a/builtin/clone.c b/builtin/clone.c index 0aea177660..5231656379 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -33,6 +33,7 @@ #include "packfile.h" #include "list-objects-filter-options.h" #include "hook.h" +#include "bundle.h" /* * Overall FIXMEs: @@ -1172,6 +1173,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) warning(_("--local is ignored")); transport->cloning = 1; + if (is_bundle) { + struct bundle_header header = BUNDLE_HEADER_INIT; + int fd = read_bundle_header(path, &header); + int has_filter = header.filter.choice != LOFC_DISABLED; + + if (fd > 0) + close(fd); + bundle_header_release(&header); + if (has_filter) + die(_("cannot clone from filtered bundle")); + } + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); if (reject_shallow) diff --git a/builtin/commit.c b/builtin/commit.c index 8b8bdad395..009a1de0a3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -726,11 +726,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; int merge_contains_scissors = 0; + int invoked_hook; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); - if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL)) + if (!no_verify && run_commit_hook(use_editor, index_file, &invoked_hook, + "pre-commit", NULL)) return 0; if (squash_message) { @@ -1053,10 +1055,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (!no_verify && hook_exists("pre-commit")) { + if (!no_verify && invoked_hook) { /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke + * Re-read the index as the pre-commit-commit hook was invoked + * and could have updated it. We must do this before we invoke * the editor and after we invoke run_status above. */ discard_cache(); @@ -1068,7 +1070,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (run_commit_hook(use_editor, index_file, "prepare-commit-msg", + if (run_commit_hook(use_editor, index_file, NULL, "prepare-commit-msg", git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL)) return 0; @@ -1085,7 +1087,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } if (!no_verify && - run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) { + run_commit_hook(use_editor, index_file, NULL, "commit-msg", + git_path_commit_editmsg(), NULL)) { return 0; } @@ -1841,7 +1844,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) repo_rerere(the_repository, 0); run_auto_maintenance(quiet); - run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); + run_commit_hook(use_editor, get_index_file(), NULL, "post-commit", + NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); } diff --git a/builtin/config.c b/builtin/config.c index ebec61868b..e7b88a9c08 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -151,7 +151,7 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), OPT_GROUP(N_("Type")), - OPT_CALLBACK('t', "type", &type, "", N_("value is given this type"), option_parse_type), + OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 510139e9b5..a7d72697fb 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -300,7 +300,7 @@ static void export_blob(const struct object_id *oid) if (!buf) die("could not read blob %s", oid_to_hex(oid)); if (check_object_signature(the_repository, oid, buf, size, - type_name(type), NULL) < 0) + type) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index b7105fcad9..ad045c7c2d 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -944,8 +944,8 @@ static int store_object( git_hash_ctx c; git_zstream s; - hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu", - type_name(type), (unsigned long)dat->len) + 1; + hdrlen = format_object_header((char *)hdr, sizeof(hdr), type, + dat->len); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, hdr, hdrlen); the_hash_algo->update_fn(&c, dat->buf, dat->len); @@ -1098,7 +1098,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) hashfile_checkpoint(pack_file, &checkpoint); offset = checkpoint.offset; - hdrlen = xsnprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; + hdrlen = format_object_header((char *)out_buf, out_sz, OBJ_BLOB, len); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, out_buf, hdrlen); @@ -2490,7 +2490,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa unsigned long size; char *buf = read_object_with_reference(the_repository, &commit_oid, - commit_type, &size, + OBJ_COMMIT, &size, &commit_oid); if (!buf || size < the_hash_algo->hexsz + 6) die("Not a valid commit: %s", p); @@ -2562,7 +2562,7 @@ static void parse_from_existing(struct branch *b) char *buf; buf = read_object_with_reference(the_repository, - &b->oid, commit_type, &size, + &b->oid, OBJ_COMMIT, &size, &b->oid); parse_from_commit(b, buf, size); free(buf); @@ -2658,7 +2658,7 @@ static struct hash_list *parse_merge(unsigned int *count) unsigned long size; char *buf = read_object_with_reference(the_repository, &n->oid, - commit_type, + OBJ_COMMIT, &size, &n->oid); if (!buf || size < the_hash_algo->hexsz + 6) die("Not a valid commit: %s", from); diff --git a/builtin/fetch.c b/builtin/fetch.c index e8305b6662..4d12c2fd4d 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1146,7 +1146,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, want_status <= FETCH_HEAD_IGNORE; want_status++) { for (rm = ref_map; rm; rm = rm->next) { - struct commit *commit = NULL; struct ref *ref = NULL; if (rm->status == REF_STATUS_REJECT_SHALLOW) { @@ -1157,21 +1156,34 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } /* - * References in "refs/tags/" are often going to point - * to annotated tags, which are not part of the - * commit-graph. We thus only try to look up refs in - * the graph which are not in that namespace to not - * regress performance in repositories with many - * annotated tags. + * When writing FETCH_HEAD we need to determine whether + * we already have the commit or not. If not, then the + * reference is not for merge and needs to be written + * to the reflog after other commits which we already + * have. We're not interested in this property though + * in case FETCH_HEAD is not to be updated, so we can + * skip the classification in that case. */ - if (!starts_with(rm->name, "refs/tags/")) - commit = lookup_commit_in_graph(the_repository, &rm->old_oid); - if (!commit) { - commit = lookup_commit_reference_gently(the_repository, - &rm->old_oid, - 1); - if (!commit) - rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + if (fetch_head->fp) { + struct commit *commit = NULL; + + /* + * References in "refs/tags/" are often going to point + * to annotated tags, which are not part of the + * commit-graph. We thus only try to look up refs in + * the graph which are not in that namespace to not + * regress performance in repositories with many + * annotated tags. + */ + if (!starts_with(rm->name, "refs/tags/")) + commit = lookup_commit_in_graph(the_repository, &rm->old_oid); + if (!commit) { + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, + 1); + if (!commit) + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + } } if (rm->fetch_head_status != want_status) diff --git a/builtin/gc.c b/builtin/gc.c index ffaf0daf5d..b335cffa33 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -30,7 +30,6 @@ #include "promisor-remote.h" #include "refs.h" #include "remote.h" -#include "object-store.h" #include "exec-cmd.h" #include "hook.h" diff --git a/builtin/grep.c b/builtin/grep.c index f1a924eade..bcb07ea7f7 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -484,7 +484,7 @@ static int grep_submodule(struct grep_opt *opt, object_type = oid_object_info(subrepo, oid, NULL); obj_read_unlock(); data = read_object_with_reference(subrepo, - oid, tree_type, + oid, OBJ_TREE, &size, NULL); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(oid)); @@ -653,7 +653,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, int hit, len; data = read_object_with_reference(opt->repo, - &obj->oid, tree_type, + &obj->oid, OBJ_TREE, &size, NULL); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 0837849288..fbae878c2b 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -25,7 +25,7 @@ static int hash_literally(struct object_id *oid, int fd, const char *type, unsig if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; else - ret = hash_object_file_literally(buf.buf, buf.len, type, oid, + ret = write_object_file_literally(buf.buf, buf.len, type, oid, flags); strbuf_release(&buf); return ret; diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 38fb4f4e55..8a5a644744 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -453,8 +453,7 @@ static void *unpack_entry_data(off_t offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX, - type_name(type),(uintmax_t)size) + 1; + hdrlen = format_object_header(hdr, sizeof(hdr), type, size); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, hdr, hdrlen); } else @@ -583,7 +582,7 @@ static void *unpack_data(struct object_entry *obj, if (!n) die(Q_("premature end of pack file, %"PRIuMAX" byte missing", "premature end of pack file, %"PRIuMAX" bytes missing", - (unsigned int)len), + len), (uintmax_t)len); from += n; len -= n; @@ -975,7 +974,7 @@ static struct base_data *resolve_delta(struct object_entry *delta_obj, if (!result_data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); hash_object_file(the_hash_algo, result_data, result_size, - type_name(delta_obj->real_type), &delta_obj->idx.oid); + delta_obj->real_type, &delta_obj->idx.oid); sha1_object(result_data, NULL, result_size, delta_obj->real_type, &delta_obj->idx.oid); @@ -1419,9 +1418,8 @@ static void fix_unresolved_deltas(struct hashfile *f) if (!data) continue; - if (check_object_signature(the_repository, &d->oid, - data, size, - type_name(type), NULL)) + if (check_object_signature(the_repository, &d->oid, data, size, + type) < 0) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); /* diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index a4bfd8fc51..b9acbf5d34 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -58,7 +58,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) "Ignoring %s.", "cannot handle more than %d bases. " "Ignoring %s.", - (int)ARRAY_SIZE(bases)-1), + ARRAY_SIZE(bases)-1), (int)ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "<head>" "<remote>" */ diff --git a/builtin/merge.c b/builtin/merge.c index a94a03384a..f178f5a3ee 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -845,15 +845,20 @@ static void prepare_to_commit(struct commit_list *remoteheads) struct strbuf msg = STRBUF_INIT; const char *index_file = get_index_file(); - if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL)) - abort_commit(remoteheads, NULL); - /* - * Re-read the index as pre-merge-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke - * the editor and after we invoke run_status above. - */ - if (hook_exists("pre-merge-commit")) - discard_cache(); + if (!no_verify) { + int invoked_hook; + + if (run_commit_hook(0 < option_edit, index_file, &invoked_hook, + "pre-merge-commit", NULL)) + abort_commit(remoteheads, NULL); + /* + * Re-read the index as pre-merge-commit hook could have updated it, + * and write it out as a tree. We must do this before we invoke + * the editor and after we invoke run_status above. + */ + if (invoked_hook) + discard_cache(); + } read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); if (squash) @@ -875,7 +880,8 @@ static void prepare_to_commit(struct commit_list *remoteheads) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); write_merge_heads(remoteheads); write_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len); - if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", + if (run_commit_hook(0 < option_edit, get_index_file(), NULL, + "prepare-commit-msg", git_path_merge_msg(the_repository), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { @@ -884,7 +890,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) } if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(), - "commit-msg", + NULL, "commit-msg", git_path_merge_msg(the_repository), NULL)) abort_commit(remoteheads, NULL); diff --git a/builtin/mktag.c b/builtin/mktag.c index c7b905c614..5d22909122 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -61,9 +61,8 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) type_name(*tagged_type), type_name(type)); repl = lookup_replace_object(the_repository, tagged_oid); - ret = check_object_signature(the_repository, repl, - buffer, size, type_name(*tagged_type), - NULL); + ret = check_object_signature(the_repository, repl, buffer, size, + *tagged_type); free(buffer); return ret; @@ -97,10 +96,10 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) &tagged_oid, &tagged_type)) die(_("tag on stdin did not pass our strict fsck check")); - if (verify_object_in_tag(&tagged_oid, &tagged_type)) + if (verify_object_in_tag(&tagged_oid, &tagged_type) < 0) die(_("tag on stdin did not refer to a valid object")); - if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) + if (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0) die(_("unable to write tag file")); strbuf_release(&buf); diff --git a/builtin/mktree.c b/builtin/mktree.c index 8bdaada922..902edba6d2 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -58,7 +58,7 @@ static void write_tree(struct object_id *oid) strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz); } - write_object_file(buf.buf, buf.len, tree_type, oid); + write_object_file(buf.buf, buf.len, OBJ_TREE, oid); strbuf_release(&buf); } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 929591269d..c59b5699fe 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -9,6 +9,7 @@ #include "prio-queue.h" #include "hash-lookup.h" #include "commit-slab.h" +#include "commit-graph.h" /* * One day. See the 'name a rev shortly after epoch' test in t6120 when @@ -26,9 +27,58 @@ struct rev_name { define_commit_slab(commit_rev_name, struct rev_name); +static timestamp_t generation_cutoff = GENERATION_NUMBER_INFINITY; static timestamp_t cutoff = TIME_MAX; static struct commit_rev_name rev_names; +/* Disable the cutoff checks entirely */ +static void disable_cutoff(void) +{ + generation_cutoff = 0; + cutoff = 0; +} + +/* Cutoff searching any commits older than this one */ +static void set_commit_cutoff(struct commit *commit) +{ + + if (cutoff > commit->date) + cutoff = commit->date; + + if (generation_cutoff) { + timestamp_t generation = commit_graph_generation(commit); + + if (generation_cutoff > generation) + generation_cutoff = generation; + } +} + +/* adjust the commit date cutoff with a slop to allow for slightly incorrect + * commit timestamps in case of clock skew. + */ +static void adjust_cutoff_timestamp_for_slop(void) +{ + if (cutoff) { + /* check for undeflow */ + if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) + cutoff = cutoff - CUTOFF_DATE_SLOP; + else + cutoff = TIME_MIN; + } +} + +/* Check if a commit is before the cutoff. Prioritize generation numbers + * first, but use the commit timestamp if we lack generation data. + */ +static int commit_is_before_cutoff(struct commit *commit) +{ + if (generation_cutoff < GENERATION_NUMBER_INFINITY) + return generation_cutoff && + commit_graph_generation(commit) < generation_cutoff; + + return commit->date < cutoff; +} + /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 @@ -151,7 +201,7 @@ static void name_rev(struct commit *start_commit, struct rev_name *start_name; parse_commit(start_commit); - if (start_commit->date < cutoff) + if (commit_is_before_cutoff(start_commit)) return; start_name = create_or_update_name(start_commit, taggerdate, 0, 0, @@ -181,7 +231,7 @@ static void name_rev(struct commit *start_commit, int generation, distance; parse_commit(parent); - if (parent->date < cutoff) + if (commit_is_before_cutoff(parent)) continue; if (parent_number > 1) { @@ -568,7 +618,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) usage_with_options(name_rev_usage, opts); } if (all || annotate_stdin) - cutoff = 0; + disable_cutoff(); for (; argc; argc--, argv++) { struct object_id oid; @@ -596,10 +646,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - if (commit) { - if (cutoff > commit->date) - cutoff = commit->date; - } + if (commit) + set_commit_cutoff(commit); if (peel_tag) { if (!commit) { @@ -612,13 +660,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array(object, *argv, &revs); } - if (cutoff) { - /* check for undeflow */ - if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) - cutoff = cutoff - CUTOFF_DATE_SLOP; - else - cutoff = TIME_MIN; - } + adjust_cutoff_timestamp_for_slop(); + for_each_ref(name_ref, &data); name_tips(); diff --git a/builtin/notes.c b/builtin/notes.c index f99593a185..a3d0d15a22 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -199,7 +199,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data * static void write_note_data(struct note_data *d, struct object_id *oid) { - if (write_object_file(d->buf.buf, d->buf.len, blob_type, oid)) { + if (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) { int status = die_message(_("unable to write note object")); if (d->edit_path) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 178e611f09..829ca359cf 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1802,7 +1802,7 @@ static void add_preferred_base(struct object_id *oid) return; data = read_object_with_reference(the_repository, oid, - tree_type, &size, &tree_oid); + OBJ_TREE, &size, &tree_oid); if (!data) return; @@ -3651,7 +3651,7 @@ static int pack_options_allow_reuse(void) static int get_object_list_from_bitmap(struct rev_info *revs) { - if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options, 0))) + if (!(bitmap_git = prepare_bitmap_walk(revs, 0))) return -1; if (pack_options_allow_reuse() && @@ -3727,6 +3727,7 @@ static void get_object_list(int ac, const char **av) repo_init_revisions(the_repository, &revs, NULL); save_commit_buffer = 0; setup_revisions(ac, av, &revs, &s_r_opt); + list_objects_filter_copy(&revs.filter, &filter_options); /* make sure shallows are read */ is_repository_shallow(the_repository); @@ -3777,9 +3778,9 @@ static void get_object_list(int ac, const char **av) if (!fn_show_object) fn_show_object = show_object; - traverse_commit_list_filtered(&filter_options, &revs, - show_commit, fn_show_object, NULL, - NULL); + traverse_commit_list(&revs, + show_commit, fn_show_object, + NULL); if (unpack_unreachable_expiration) { revs.ignore_missing_links = 1; diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 2109c4c9e5..9f1f33e954 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -160,15 +160,22 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, cmd_prefix, read_tree_options, read_tree_usage, 0); - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + /* Prefix should not start with a directory separator */ + if (opts.prefix && opts.prefix[0] == '/') + die("Invalid prefix, prefix cannot start with '/'"); + if (opts.reset) opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + /* * NEEDSWORK * @@ -210,6 +217,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (opts.merge && !opts.index_only) setup_work_tree(); + if (opts.skip_sparse_checkout) + ensure_full_index(&the_index); + if (opts.merge) { switch (stage - 1) { case 0: diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d10aeb7e78..9aabffa1af 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -749,7 +749,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) int bogs /* beginning_of_gpg_sig */; already_done = 1; - if (write_object_file(push_cert.buf, push_cert.len, "blob", + if (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB, &push_cert_oid)) oidclr(&push_cert_oid); @@ -813,13 +813,14 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, proc.trace2_hook_name = hook_name; if (feed_state->push_options) { - int i; + size_t i; for (i = 0; i < feed_state->push_options->nr; i++) strvec_pushf(&proc.env_array, - "GIT_PUSH_OPTION_%d=%s", i, + "GIT_PUSH_OPTION_%"PRIuMAX"=%s", + (uintmax_t)i, feed_state->push_options->items[i].string); - strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", - feed_state->push_options->nr); + strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"", + (uintmax_t)feed_state->push_options->nr); } else strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); @@ -1408,10 +1409,12 @@ static const char *push_to_deploy(unsigned char *sha1, static const char *push_to_checkout_hook = "push-to-checkout"; static const char *push_to_checkout(unsigned char *hash, + int *invoked_hook, struct strvec *env, const char *work_tree) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + opt.invoked_hook = invoked_hook; strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); strvec_pushv(&opt.env, env->v); @@ -1426,6 +1429,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w { const char *retval, *git_dir; struct strvec env = STRVEC_INIT; + int invoked_hook; if (!worktree || !worktree->path) BUG("worktree->path must be non-NULL"); @@ -1436,10 +1440,9 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); - if (!hook_exists(push_to_checkout_hook)) + retval = push_to_checkout(sha1, &invoked_hook, &env, worktree->path); + if (!invoked_hook) retval = push_to_deploy(sha1, &env, worktree->path); - else - retval = push_to_checkout(sha1, &env, worktree->path); strvec_clear(&env); return retval; diff --git a/builtin/reflog.c b/builtin/reflog.c index 016466852f..9407f835cb 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,16 +1,9 @@ #include "builtin.h" #include "config.h" -#include "lockfile.h" -#include "object-store.h" -#include "repository.h" -#include "commit.h" -#include "refs.h" -#include "dir.h" -#include "tree-walk.h" -#include "diff.h" #include "revision.h" #include "reachable.h" #include "worktree.h" +#include "reflog.h" static const char reflog_exists_usage[] = N_("git reflog exists <ref>"); @@ -18,404 +11,11 @@ N_("git reflog exists <ref>"); static timestamp_t default_reflog_expire; static timestamp_t default_reflog_expire_unreachable; -struct cmd_reflog_expire_cb { - int stalefix; - int explicit_expiry; - timestamp_t expire_total; - timestamp_t expire_unreachable; - int recno; -}; - -struct expire_reflog_policy_cb { - enum { - UE_NORMAL, - UE_ALWAYS, - UE_HEAD - } unreachable_expire_kind; - struct commit_list *mark_list; - unsigned long mark_limit; - struct cmd_reflog_expire_cb cmd; - struct commit *tip_commit; - struct commit_list *tips; - unsigned int dry_run:1; -}; - struct worktree_reflogs { struct worktree *worktree; struct string_list reflogs; }; -/* Remember to update object flag allocation in object.h */ -#define INCOMPLETE (1u<<10) -#define STUDYING (1u<<11) -#define REACHABLE (1u<<12) - -static int tree_is_complete(const struct object_id *oid) -{ - struct tree_desc desc; - struct name_entry entry; - int complete; - struct tree *tree; - - tree = lookup_tree(the_repository, oid); - if (!tree) - return 0; - if (tree->object.flags & SEEN) - return 1; - if (tree->object.flags & INCOMPLETE) - return 0; - - if (!tree->buffer) { - enum object_type type; - unsigned long size; - void *data = read_object_file(oid, &type, &size); - if (!data) { - tree->object.flags |= INCOMPLETE; - return 0; - } - tree->buffer = data; - tree->size = size; - } - init_tree_desc(&desc, tree->buffer, tree->size); - complete = 1; - while (tree_entry(&desc, &entry)) { - if (!has_object_file(&entry.oid) || - (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) { - tree->object.flags |= INCOMPLETE; - complete = 0; - } - } - free_tree_buffer(tree); - - if (complete) - tree->object.flags |= SEEN; - return complete; -} - -static int commit_is_complete(struct commit *commit) -{ - struct object_array study; - struct object_array found; - int is_incomplete = 0; - int i; - - /* early return */ - if (commit->object.flags & SEEN) - return 1; - if (commit->object.flags & INCOMPLETE) - return 0; - /* - * Find all commits that are reachable and are not marked as - * SEEN. Then make sure the trees and blobs contained are - * complete. After that, mark these commits also as SEEN. - * If some of the objects that are needed to complete this - * commit are missing, mark this commit as INCOMPLETE. - */ - memset(&study, 0, sizeof(study)); - memset(&found, 0, sizeof(found)); - add_object_array(&commit->object, NULL, &study); - add_object_array(&commit->object, NULL, &found); - commit->object.flags |= STUDYING; - while (study.nr) { - struct commit *c; - struct commit_list *parent; - - c = (struct commit *)object_array_pop(&study); - if (!c->object.parsed && !parse_object(the_repository, &c->object.oid)) - c->object.flags |= INCOMPLETE; - - if (c->object.flags & INCOMPLETE) { - is_incomplete = 1; - break; - } - else if (c->object.flags & SEEN) - continue; - for (parent = c->parents; parent; parent = parent->next) { - struct commit *p = parent->item; - if (p->object.flags & STUDYING) - continue; - p->object.flags |= STUDYING; - add_object_array(&p->object, NULL, &study); - add_object_array(&p->object, NULL, &found); - } - } - if (!is_incomplete) { - /* - * make sure all commits in "found" array have all the - * necessary objects. - */ - for (i = 0; i < found.nr; i++) { - struct commit *c = - (struct commit *)found.objects[i].item; - if (!tree_is_complete(get_commit_tree_oid(c))) { - is_incomplete = 1; - c->object.flags |= INCOMPLETE; - } - } - if (!is_incomplete) { - /* mark all found commits as complete, iow SEEN */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags |= SEEN; - } - } - /* clear flags from the objects we traversed */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags &= ~STUDYING; - if (is_incomplete) - commit->object.flags |= INCOMPLETE; - else { - /* - * If we come here, we have (1) traversed the ancestry chain - * from the "commit" until we reach SEEN commits (which are - * known to be complete), and (2) made sure that the commits - * encountered during the above traversal refer to trees that - * are complete. Which means that we know *all* the commits - * we have seen during this process are complete. - */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags |= SEEN; - } - /* free object arrays */ - object_array_clear(&study); - object_array_clear(&found); - return !is_incomplete; -} - -static int keep_entry(struct commit **it, struct object_id *oid) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return 1; - commit = lookup_commit_reference_gently(the_repository, oid, 1); - if (!commit) - return 0; - - /* - * Make sure everything in this commit exists. - * - * We have walked all the objects reachable from the refs - * and cache earlier. The commits reachable by this commit - * must meet SEEN commits -- and then we should mark them as - * SEEN as well. - */ - if (!commit_is_complete(commit)) - return 0; - *it = commit; - return 1; -} - -/* - * Starting from commits in the cb->mark_list, mark commits that are - * reachable from them. Stop the traversal at commits older than - * the expire_limit and queue them back, so that the caller can call - * us again to restart the traversal with longer expire_limit. - */ -static void mark_reachable(struct expire_reflog_policy_cb *cb) -{ - struct commit_list *pending; - timestamp_t expire_limit = cb->mark_limit; - struct commit_list *leftover = NULL; - - for (pending = cb->mark_list; pending; pending = pending->next) - pending->item->object.flags &= ~REACHABLE; - - pending = cb->mark_list; - while (pending) { - struct commit_list *parent; - struct commit *commit = pop_commit(&pending); - if (commit->object.flags & REACHABLE) - continue; - if (parse_commit(commit)) - continue; - commit->object.flags |= REACHABLE; - if (commit->date < expire_limit) { - commit_list_insert(commit, &leftover); - continue; - } - commit->object.flags |= REACHABLE; - parent = commit->parents; - while (parent) { - commit = parent->item; - parent = parent->next; - if (commit->object.flags & REACHABLE) - continue; - commit_list_insert(commit, &pending); - } - } - cb->mark_list = leftover; -} - -static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid) -{ - /* - * We may or may not have the commit yet - if not, look it - * up using the supplied sha1. - */ - if (!commit) { - if (is_null_oid(oid)) - return 0; - - commit = lookup_commit_reference_gently(the_repository, oid, - 1); - - /* Not a commit -- keep it */ - if (!commit) - return 0; - } - - /* Reachable from the current ref? Don't prune. */ - if (commit->object.flags & REACHABLE) - return 0; - - if (cb->mark_list && cb->mark_limit) { - cb->mark_limit = 0; /* dig down to the root */ - mark_reachable(cb); - } - - return !(commit->object.flags & REACHABLE); -} - -/* - * Return true iff the specified reflog entry should be expired. - */ -static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - struct commit *old_commit, *new_commit; - - if (timestamp < cb->cmd.expire_total) - return 1; - - old_commit = new_commit = NULL; - if (cb->cmd.stalefix && - (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid))) - return 1; - - if (timestamp < cb->cmd.expire_unreachable) { - switch (cb->unreachable_expire_kind) { - case UE_ALWAYS: - return 1; - case UE_NORMAL: - case UE_HEAD: - if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid)) - return 1; - break; - } - } - - if (cb->cmd.recno && --(cb->cmd.recno) == 0) - return 1; - - return 0; -} - -static int should_expire_reflog_ent_verbose(struct object_id *ooid, - struct object_id *noid, - const char *email, - timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - int expire; - - expire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz, - message, cb); - - if (!expire) - printf("keep %s", message); - else if (cb->dry_run) - printf("would prune %s", message); - else - printf("prune %s", message); - - return expire; -} - -static int push_tip_to_list(const char *refname, const struct object_id *oid, - int flags, void *cb_data) -{ - struct commit_list **list = cb_data; - struct commit *tip_commit; - if (flags & REF_ISSYMREF) - return 0; - tip_commit = lookup_commit_reference_gently(the_repository, oid, 1); - if (!tip_commit) - return 0; - commit_list_insert(tip_commit, list); - return 0; -} - -static int is_head(const char *refname) -{ - switch (ref_type(refname)) { - case REF_TYPE_OTHER_PSEUDOREF: - case REF_TYPE_MAIN_PSEUDOREF: - if (parse_worktree_ref(refname, NULL, NULL, &refname)) - BUG("not a worktree ref: %s", refname); - break; - default: - break; - } - return !strcmp(refname, "HEAD"); -} - -static void reflog_expiry_prepare(const char *refname, - const struct object_id *oid, - void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - struct commit_list *elem; - struct commit *commit = NULL; - - if (!cb->cmd.expire_unreachable || is_head(refname)) { - cb->unreachable_expire_kind = UE_HEAD; - } else { - commit = lookup_commit(the_repository, oid); - cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS; - } - - if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) - cb->unreachable_expire_kind = UE_ALWAYS; - - switch (cb->unreachable_expire_kind) { - case UE_ALWAYS: - return; - case UE_HEAD: - for_each_ref(push_tip_to_list, &cb->tips); - for (elem = cb->tips; elem; elem = elem->next) - commit_list_insert(elem->item, &cb->mark_list); - break; - case UE_NORMAL: - commit_list_insert(commit, &cb->mark_list); - /* For reflog_expiry_cleanup() below */ - cb->tip_commit = commit; - } - cb->mark_limit = cb->cmd.expire_total; - mark_reachable(cb); -} - -static void reflog_expiry_cleanup(void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - struct commit_list *elem; - - switch (cb->unreachable_expire_kind) { - case UE_ALWAYS: - return; - case UE_HEAD: - for (elem = cb->tips; elem; elem = elem->next) - clear_commit_marks(elem->item, REACHABLE); - free_commit_list(cb->tips); - break; - case UE_NORMAL: - clear_commit_marks(cb->tip_commit, REACHABLE); - break; - } -} - static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data) { struct worktree_reflogs *cb = cb_data; @@ -704,16 +304,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) return status; } -static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct cmd_reflog_expire_cb *cb = cb_data; - if (!cb->expire_total || timestamp < cb->expire_total) - cb->recno++; - return 0; -} - static const char * reflog_delete_usage[] = { N_("git reflog delete [--rewrite] [--updateref] " "[--dry-run | -n] [--verbose] <refs>..."), @@ -722,11 +312,10 @@ static const char * reflog_delete_usage[] = { static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cmd = { 0 }; int i, status = 0; unsigned int flags = 0; int verbose = 0; - reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; + const struct option options[] = { OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), EXPIRE_REFLOGS_DRY_RUN), @@ -742,48 +331,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0); - if (verbose) - should_prune_fn = should_expire_reflog_ent_verbose; - if (argc < 1) return error(_("no reflog specified to delete")); - for (i = 0; i < argc; i++) { - const char *spec = strstr(argv[i], "@{"); - char *ep, *ref; - int recno; - struct expire_reflog_policy_cb cb = { - .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), - }; - - if (!spec) { - status |= error(_("not a reflog: %s"), argv[i]); - continue; - } + for (i = 0; i < argc; i++) + status |= reflog_delete(argv[i], flags, verbose); - if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) { - status |= error(_("no reflog for '%s'"), argv[i]); - continue; - } - - recno = strtoul(spec + 2, &ep, 10); - if (*ep == '}') { - cmd.recno = -recno; - for_each_reflog_ent(ref, count_reflog_ent, &cmd); - } else { - cmd.expire_total = approxidate(spec + 2); - for_each_reflog_ent(ref, count_reflog_ent, &cmd); - cmd.expire_total = 0; - } - - cb.cmd = cmd; - status |= reflog_expire(ref, flags, - reflog_expiry_prepare, - should_prune_fn, - reflog_expiry_cleanup, - &cb); - free(ref); - } return status; } diff --git a/builtin/remote.c b/builtin/remote.c index 6f27ddc47b..5f4cde9d78 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -12,11 +12,12 @@ #include "object-store.h" #include "strvec.h" #include "commit-reach.h" +#include "progress.h" static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose]", N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"), - N_("git remote rename <old> <new>"), + N_("git remote rename [--[no-]progress] <old> <new>"), N_("git remote remove <name>"), N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"), N_("git remote [-v | --verbose] show [-n] <name>"), @@ -36,7 +37,7 @@ static const char * const builtin_remote_add_usage[] = { }; static const char * const builtin_remote_rename_usage[] = { - N_("git remote rename <old> <new>"), + N_("git remote rename [--[no-]progress] <old> <new>"), NULL }; @@ -571,6 +572,7 @@ struct rename_info { const char *old_name; const char *new_name; struct string_list *remote_branches; + uint32_t symrefs_nr; }; static int read_remote_branches(const char *refname, @@ -587,10 +589,12 @@ static int read_remote_branches(const char *refname, item = string_list_append(rename->remote_branches, refname); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, &flag); - if (symref && (flag & REF_ISSYMREF)) + if (symref && (flag & REF_ISSYMREF)) { item->util = xstrdup(symref); - else + rename->symrefs_nr++; + } else { item->util = NULL; + } } strbuf_release(&buf); @@ -674,7 +678,9 @@ static void handle_push_default(const char* old_name, const char* new_name) static int mv(int argc, const char **argv) { + int show_progress = isatty(2); struct option options[] = { + OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")), OPT_END() }; struct remote *oldremote, *newremote; @@ -682,14 +688,19 @@ static int mv(int argc, const char **argv) old_remote_context = STRBUF_INIT; struct string_list remote_branches = STRING_LIST_INIT_DUP; struct rename_info rename; - int i, refspec_updated = 0; + int i, refs_renamed_nr = 0, refspec_updated = 0; + struct progress *progress = NULL; + + argc = parse_options(argc, argv, NULL, options, + builtin_remote_rename_usage, 0); - if (argc != 3) + if (argc != 2) usage_with_options(builtin_remote_rename_usage, options); - rename.old_name = argv[1]; - rename.new_name = argv[2]; + rename.old_name = argv[0]; + rename.new_name = argv[1]; rename.remote_branches = &remote_branches; + rename.symrefs_nr = 0; oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) { @@ -764,15 +775,26 @@ static int mv(int argc, const char **argv) * the new symrefs. */ for_each_ref(read_remote_branches, &rename); + if (show_progress) { + /* + * Count symrefs twice, since "renaming" them is done by + * deleting and recreating them in two separate passes. + */ + progress = start_progress(_("Renaming remote references"), + rename.remote_branches->nr + rename.symrefs_nr); + } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; - int flag = 0; + struct strbuf referent = STRBUF_INIT; - read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag); - if (!(flag & REF_ISSYMREF)) + if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string, + &referent)) continue; if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) die(_("deleting '%s' failed"), item->string); + + strbuf_release(&referent); + display_progress(progress, ++refs_renamed_nr); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -788,6 +810,7 @@ static int mv(int argc, const char **argv) item->string, buf.buf); if (rename_ref(item->string, buf.buf, buf2.buf)) die(_("renaming '%s' failed"), item->string); + display_progress(progress, ++refs_renamed_nr); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -807,7 +830,9 @@ static int mv(int argc, const char **argv) item->string, buf.buf); if (create_symref(buf.buf, buf2.buf, buf3.buf)) die(_("creating '%s' failed"), buf.buf); + display_progress(progress, ++refs_renamed_nr); } + stop_progress(&progress); string_list_clear(&remote_branches, 1); handle_push_default(rename.old_name, rename.new_name); diff --git a/builtin/repack.c b/builtin/repack.c index da1e364a75..d1a563d5b6 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -22,6 +22,7 @@ static int delta_base_offset = 1; static int pack_kept_objects = -1; static int write_bitmaps = -1; static int use_delta_islands; +static int run_update_server_info = 1; static char *packdir, *packtmp_name, *packtmp; static const char *const git_repack_usage[] = { @@ -54,6 +55,10 @@ static int repack_config(const char *var, const char *value, void *cb) use_delta_islands = git_config_bool(var, value); return 0; } + if (strcmp(var, "repack.updateserverinfo") == 0) { + run_update_server_info = git_config_bool(var, value); + return 0; + } return git_default_config(var, value, cb); } @@ -620,7 +625,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) const char *unpack_unreachable = NULL; int keep_unreachable = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; - int no_update_server_info = 0; struct pack_objects_args po_args = {NULL}; int geometric_factor = 0; int write_midx = 0; @@ -637,8 +641,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("pass --no-reuse-delta to git-pack-objects")), OPT_BOOL('F', NULL, &po_args.no_reuse_object, N_("pass --no-reuse-object to git-pack-objects")), - OPT_BOOL('n', NULL, &no_update_server_info, - N_("do not run git-update-server-info")), + OPT_NEGBIT('n', NULL, &run_update_server_info, + N_("do not run git-update-server-info"), 1), OPT__QUIET(&po_args.quiet, N_("be quiet")), OPT_BOOL('l', "local", &po_args.local, N_("pass --local to git-pack-objects")), @@ -939,7 +943,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) prune_shallow(PRUNE_QUICK); } - if (!no_update_server_info) + if (run_update_server_info) update_server_info(0); remove_temporary_files(); diff --git a/builtin/replace.c b/builtin/replace.c index ac92337c0e..5068f4f0b2 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -409,7 +409,7 @@ static int check_one_mergetag(struct commit *commit, int i; hash_object_file(the_hash_algo, extra->value, extra->len, - type_name(OBJ_TAG), &tag_oid); + OBJ_TAG, &tag_oid); tag = lookup_tag(the_repository, &tag_oid); if (!tag) return error(_("bad mergetag in commit '%s'"), ref); @@ -474,7 +474,7 @@ static int create_graft(int argc, const char **argv, int force, int gentle) return -1; } - if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) { + if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) { strbuf_release(&buf); return error(_("could not write replacement commit for: '%s'"), old_ref); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 38528c7f15..572da1472e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -62,7 +62,6 @@ static const char rev_list_usage[] = static struct progress *progress; static unsigned progress_counter; -static struct list_objects_filter_options filter_options; static struct oidset omitted_objects; static int arg_print_omitted; /* print objects omitted by filter */ @@ -400,7 +399,6 @@ static inline int parse_missing_action_value(const char *value) } static int try_bitmap_count(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { uint32_t commit_count = 0, @@ -436,7 +434,7 @@ static int try_bitmap_count(struct rev_info *revs, */ max_count = revs->max_count; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -453,7 +451,6 @@ static int try_bitmap_count(struct rev_info *revs, } static int try_bitmap_traversal(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { struct bitmap_index *bitmap_git; @@ -465,7 +462,7 @@ static int try_bitmap_traversal(struct rev_info *revs, if (revs->max_count >= 0) return -1; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -475,7 +472,6 @@ static int try_bitmap_traversal(struct rev_info *revs, } static int try_bitmap_disk_usage(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { struct bitmap_index *bitmap_git; @@ -483,7 +479,7 @@ static int try_bitmap_disk_usage(struct rev_info *revs, if (!show_disk_usage) return -1; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -595,17 +591,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) show_progress = arg; continue; } - - if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { - parse_list_objects_filter(&filter_options, arg); - if (filter_options.choice && !revs.blob_objects) - die(_("object filtering requires --objects")); - continue; - } - if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { - list_objects_filter_set_no_filter(&filter_options); - continue; - } if (!strcmp(arg, "--filter-provided-objects")) { filter_provided_objects = 1; continue; @@ -688,11 +673,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) progress = start_delayed_progress(show_progress, 0); if (use_bitmap_index) { - if (!try_bitmap_count(&revs, &filter_options, filter_provided_objects)) + if (!try_bitmap_count(&revs, filter_provided_objects)) return 0; - if (!try_bitmap_disk_usage(&revs, &filter_options, filter_provided_objects)) + if (!try_bitmap_disk_usage(&revs, filter_provided_objects)) return 0; - if (!try_bitmap_traversal(&revs, &filter_options, filter_provided_objects)) + if (!try_bitmap_traversal(&revs, filter_provided_objects)) return 0; } @@ -733,7 +718,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE); traverse_commit_list_filtered( - &filter_options, &revs, show_commit, show_object, &info, + &revs, show_commit, show_object, &info, (arg_print_omitted ? &omitted_objects : NULL)); if (arg_print_omitted) { diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 228d782754..26c5c0cf93 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -435,7 +435,7 @@ static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s, void shortlog_output(struct shortlog *log) { - int i, j; + size_t i, j; struct strbuf sb = STRBUF_INIT; if (log->sort_by_number) @@ -448,10 +448,10 @@ void shortlog_output(struct shortlog *log) (int)UTIL_TO_INT(item), item->string); } else { struct string_list *onelines = item->util; - fprintf(log->file, "%s (%d):\n", - item->string, onelines->nr); - for (j = onelines->nr - 1; j >= 0; j--) { - const char *msg = onelines->items[j].string; + fprintf(log->file, "%s (%"PRIuMAX"):\n", + item->string, (uintmax_t)onelines->nr); + for (j = onelines->nr; j >= 1; j--) { + const char *msg = onelines->items[j - 1].string; if (log->wrap_lines) { strbuf_reset(&sb); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 7f02e4352a..0217d44c5b 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -8,7 +8,6 @@ #include "run-command.h" #include "strbuf.h" #include "string-list.h" -#include "cache.h" #include "cache-tree.h" #include "lockfile.h" #include "resolve-undo.h" diff --git a/builtin/stash.c b/builtin/stash.c index 3e8af210fd..ccdfdab44b 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -16,7 +16,7 @@ #include "log-tree.h" #include "diffcore.h" #include "exec-cmd.h" -#include "entry.h" +#include "reflog.h" #define INCLUDE_ALL_FILES 2 @@ -634,20 +634,9 @@ static int reflog_is_empty(const char *refname) static int do_drop_stash(struct stash_info *info, int quiet) { - int ret; - struct child_process cp_reflog = CHILD_PROCESS_INIT; - - /* - * reflog does not provide a simple function for deleting refs. One will - * need to be added to avoid implementing too much reflog code here - */ - - cp_reflog.git_cmd = 1; - strvec_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", - "--rewrite", NULL); - strvec_push(&cp_reflog.args, info->revision.buf); - ret = run_command(&cp_reflog); - if (!ret) { + if (!reflog_delete(info->revision.buf, + EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF, + 0)) { if (!quiet) printf_ln(_("Dropped %s (%s)"), info->revision.buf, oid_to_hex(&info->w_commit)); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2f7c58362b..5301612d24 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -31,11 +31,13 @@ typedef void (*each_submodule_fn)(const struct cache_entry *list_item, void *cb_data); -static char *get_default_remote(void) +static char *repo_get_default_remote(struct repository *repo) { char *dest = NULL, *ret; struct strbuf sb = STRBUF_INIT; - const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + struct ref_store *store = get_main_ref_store(repo); + const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL, + NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -48,7 +50,7 @@ static char *get_default_remote(void) die(_("Expecting a full ref name, got %s"), refname); strbuf_addf(&sb, "branch.%s.remote", refname); - if (git_config_get_string(sb.buf, &dest)) + if (repo_config_get_string(repo, sb.buf, &dest)) ret = xstrdup("origin"); else ret = dest; @@ -57,19 +59,17 @@ static char *get_default_remote(void) return ret; } -static int print_default_remote(int argc, const char **argv, const char *prefix) +static char *get_default_remote_submodule(const char *module_path) { - char *remote; - - if (argc != 1) - die(_("submodule--helper print-default-remote takes no arguments")); + struct repository subrepo; - remote = get_default_remote(); - if (remote) - printf("%s\n", remote); + repo_submodule_init(&subrepo, the_repository, module_path, null_oid()); + return repo_get_default_remote(&subrepo); +} - free(remote); - return 0; +static char *get_default_remote(void) +{ + return repo_get_default_remote(the_repository); } static int starts_with_dot_slash(const char *str) @@ -247,11 +247,10 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr return 0; } -/* the result should be freed by the caller. */ -static char *get_submodule_displaypath(const char *path, const char *prefix) +static char *do_get_submodule_displaypath(const char *path, + const char *prefix, + const char *super_prefix) { - const char *super_prefix = get_super_prefix(); - if (prefix && super_prefix) { BUG("cannot have prefix '%s' and superprefix '%s'", prefix, super_prefix); @@ -267,6 +266,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix) } } +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) +{ + const char *super_prefix = get_super_prefix(); + return do_get_submodule_displaypath(path, prefix, super_prefix); +} + static char *compute_rev_name(const char *sub_path, const char* object_id) { struct strbuf sb = STRBUF_INIT; @@ -588,18 +594,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix) struct init_cb { const char *prefix; + const char *superprefix; unsigned int flags; }; #define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, - unsigned int flags) + const char *superprefix, unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; char *upd = NULL, *url = NULL, *displaypath; - displaypath = get_submodule_displaypath(path, prefix); + /* try superprefix from the environment, if it is not passed explicitly */ + if (!superprefix) + superprefix = get_super_prefix(); + displaypath = do_get_submodule_displaypath(path, prefix, superprefix); sub = submodule_from_path(the_repository, null_oid(), path); @@ -673,7 +683,7 @@ static void init_submodule(const char *path, const char *prefix, static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct init_cb *info = cb_data; - init_submodule(list_item->name, info->prefix, info->flags); + init_submodule(list_item->name, info->prefix, info->superprefix, info->flags); } static int module_init(int argc, const char **argv, const char *prefix) @@ -1343,9 +1353,8 @@ static void sync_submodule(const char *path, const char *prefix, { const struct submodule *sub; char *remote_key = NULL; - char *sub_origin_url, *super_config_url, *displaypath; + char *sub_origin_url, *super_config_url, *displaypath, *default_remote; struct strbuf sb = STRBUF_INIT; - struct child_process cp = CHILD_PROCESS_INIT; char *sub_config_path = NULL; if (!is_submodule_active(the_repository, path)) @@ -1384,21 +1393,15 @@ static void sync_submodule(const char *path, const char *prefix, if (!is_submodule_populated_gently(path, NULL)) goto cleanup; - prepare_submodule_repo_env(&cp.env_array); - cp.git_cmd = 1; - cp.dir = path; - strvec_pushl(&cp.args, "submodule--helper", - "print-default-remote", NULL); - strbuf_reset(&sb); - if (capture_command(&cp, &sb, 0)) + default_remote = get_default_remote_submodule(path); + if (!default_remote) die(_("failed to get the default remote for submodule '%s'"), path); - strbuf_strip_suffix(&sb, "\n"); - remote_key = xstrfmt("remote.%s.url", sb.buf); + remote_key = xstrfmt("remote.%s.url", default_remote); + free(default_remote); - strbuf_reset(&sb); submodule_to_gitdir(&sb, path); strbuf_addstr(&sb, "/config"); @@ -1957,29 +1960,6 @@ static void determine_submodule_update_strategy(struct repository *r, free(key); } -static int module_update_module_mode(int argc, const char **argv, const char *prefix) -{ - const char *path, *update = NULL; - int just_cloned; - struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT }; - - if (argc < 3 || argc > 4) - die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]"); - - just_cloned = git_config_int("just_cloned", argv[1]); - path = argv[2]; - - if (argc == 4) - update = argv[3]; - - determine_submodule_update_strategy(the_repository, - just_cloned, path, update, - &update_strategy); - fputs(submodule_strategy_to_string(&update_strategy), stdout); - - return 0; -} - struct update_clone_data { const struct submodule *sub; struct object_id oid; @@ -2020,6 +2000,7 @@ struct submodule_update_clone { int failed_clones_nr, failed_clones_alloc; int max_jobs; + unsigned int init; }; #define SUBMODULE_UPDATE_CLONE_INIT { \ .list = MODULE_LIST_INIT, \ @@ -2038,10 +2019,11 @@ struct update_data { struct object_id suboid; struct submodule_update_strategy update_strategy; int depth; - unsigned int force: 1; - unsigned int quiet: 1; - unsigned int nofetch: 1; - unsigned int just_cloned: 1; + unsigned int force; + unsigned int quiet; + unsigned int nofetch; + unsigned int just_cloned; + unsigned int remote; }; #define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT } @@ -2475,6 +2457,16 @@ static int do_run_update_procedure(struct update_data *ud) return run_update_command(ud, subforce); } +/* + * NEEDSWORK: As we convert "git submodule update" to C, + * update_submodule2() will invoke more and more functions, making it + * difficult to preserve the function ordering without forward + * declarations. + * + * When the conversion is complete, this forward declaration will be + * unnecessary and should be removed. + */ +static int update_submodule2(struct update_data *update_data); static void update_submodule(struct update_clone_data *ucd) { fprintf(stdout, "dummy %s %d\t%s\n", @@ -2518,6 +2510,8 @@ static int update_clone(int argc, const char **argv, const char *prefix) int ret; struct option module_update_clone_options[] = { + OPT_BOOL(0, "init", &suc.init, + N_("initialize uninitialized submodules before update")), OPT_STRING(0, "prefix", &prefix, N_("path"), N_("path into the working tree")), @@ -2551,7 +2545,12 @@ static int update_clone(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"), + N_("git submodule [--quiet] update" + " [--init [--filter=<filter-spec>]] [--remote]" + " [-N|--no-fetch] [-f|--force]" + " [--checkout|--merge|--rebase]" + " [--[no-]recommend-shallow] [--reference <repository>]" + " [--recursive] [--[no-]single-branch] [--] [<path>...]"), NULL }; suc.prefix = prefix; @@ -2562,6 +2561,19 @@ static int update_clone(int argc, const char **argv, const char *prefix) memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_update_clone_options, git_submodule_helper_usage, 0); + + if (filter_options.choice && !suc.init) { + /* + * NEEDSWORK: Don't use usage_with_options() because the + * usage string is for "git submodule update", but the + * options are for "git submodule--helper update-clone". + * + * This will no longer be an issue when "update-clone" + * is replaced by "git submodule--helper update". + */ + usage(git_submodule_helper_usage[0]); + } + suc.filter_options = &filter_options; if (update) @@ -2576,6 +2588,29 @@ static int update_clone(int argc, const char **argv, const char *prefix) if (pathspec.nr) suc.warn_if_uninitialized = 1; + if (suc.init) { + struct module_list list = MODULE_LIST_INIT; + struct init_cb info = INIT_CB_INIT; + + if (module_list_compute(argc, argv, suc.prefix, + &pathspec, &list) < 0) + return 1; + + /* + * If there are no path args and submodule.active is set then, + * by default, only initialize 'active' modules. + */ + if (!argc && git_config_get_value_multi("submodule.active")) + module_list_active(&list); + + info.prefix = suc.prefix; + info.superprefix = suc.recursive_prefix; + if (suc.quiet) + info.flags |= OPT_QUIET; + + for_each_listed_submodule(&list, init_submodule_cb, &info); + } + ret = update_submodules(&suc); list_objects_filter_release(&filter_options); return ret; @@ -2583,16 +2618,17 @@ static int update_clone(int argc, const char **argv, const char *prefix) static int run_update_procedure(int argc, const char **argv, const char *prefix) { - int force = 0, quiet = 0, nofetch = 0, just_cloned = 0; char *prefixed_path, *update = NULL; struct update_data update_data = UPDATE_DATA_INIT; struct option options[] = { - OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")), - OPT__FORCE(&force, N_("force checkout updates"), 0), - OPT_BOOL('N', "no-fetch", &nofetch, + OPT__QUIET(&update_data.quiet, + N_("suppress output for update by rebase or merge")), + OPT__FORCE(&update_data.force, N_("force checkout updates"), + 0), + OPT_BOOL('N', "no-fetch", &update_data.nofetch, N_("don't fetch new objects from the remote site")), - OPT_BOOL(0, "just-cloned", &just_cloned, + OPT_BOOL(0, "just-cloned", &update_data.just_cloned, N_("overrides update mode in case the repository is a fresh clone")), OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")), OPT_STRING(0, "prefix", &prefix, @@ -2607,9 +2643,8 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"), N_("SHA1 expected by superproject"), PARSE_OPT_NONEG, parse_opt_object_id), - OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"), - N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG, - parse_opt_object_id), + OPT_BOOL(0, "remote", &update_data.remote, + N_("use SHA-1 of submodule's remote tracking branch")), OPT_END() }; @@ -2623,10 +2658,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix) if (argc != 1) usage_with_options(usage, options); - update_data.force = !!force; - update_data.quiet = !!quiet; - update_data.nofetch = !!nofetch; - update_data.just_cloned = !!just_cloned; update_data.sm_path = argv[0]; if (update_data.recursive_prefix) @@ -2641,11 +2672,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix) &update_data.update_strategy); free(prefixed_path); - - if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force) - return do_run_update_procedure(&update_data); - - return 3; + return update_submodule2(&update_data); } static int resolve_relative_path(int argc, const char **argv, const char *prefix) @@ -2697,23 +2724,6 @@ static const char *remote_submodule_branch(const char *path) return branch; } -static int resolve_remote_submodule_branch(int argc, const char **argv, - const char *prefix) -{ - const char *ret; - struct strbuf sb = STRBUF_INIT; - if (argc != 2) - die("submodule--helper remote-branch takes exactly one arguments, got %d", argc); - - ret = remote_submodule_branch(argv[1]); - if (!ret) - die("submodule %s doesn't exist", argv[1]); - - printf("%s", ret); - strbuf_release(&sb); - return 0; -} - static int push_check(int argc, const char **argv, const char *prefix) { struct remote *remote; @@ -2791,17 +2801,11 @@ static int push_check(int argc, const char **argv, const char *prefix) return 0; } -static int ensure_core_worktree(int argc, const char **argv, const char *prefix) +static void ensure_core_worktree(const char *path) { - const char *path; const char *cw; struct repository subrepo; - if (argc != 2) - BUG("submodule--helper ensure-core-worktree <path>"); - - path = argv[1]; - if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) die(_("could not get a repository handle for submodule '%s'"), path); @@ -2821,8 +2825,6 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) free(abs_path); strbuf_release(&sb); } - - return 0; } static int absorb_git_dirs(int argc, const char **argv, const char *prefix) @@ -3045,6 +3047,42 @@ static int module_create_branch(int argc, const char **argv, const char *prefix) force, reflog, quiet, track, dry_run); return 0; } + +/* NEEDSWORK: this is a temporary name until we delete update_submodule() */ +static int update_submodule2(struct update_data *update_data) +{ + ensure_core_worktree(update_data->sm_path); + if (update_data->just_cloned) + oidcpy(&update_data->suboid, null_oid()); + else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid)) + die(_("Unable to find current revision in submodule path '%s'"), + update_data->displaypath); + + if (update_data->remote) { + char *remote_name = get_default_remote_submodule(update_data->sm_path); + const char *branch = remote_submodule_branch(update_data->sm_path); + char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + + if (!update_data->nofetch) { + if (fetch_in_submodule(update_data->sm_path, update_data->depth, + 0, NULL)) + die(_("Unable to fetch in submodule path '%s'"), + update_data->sm_path); + } + + if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid)) + die(_("Unable to find %s revision in submodule path '%s'"), + remote_ref, update_data->sm_path); + + free(remote_ref); + } + + if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) + return do_run_update_procedure(update_data); + + return 3; +} + struct add_data { const char *prefix; const char *branch; @@ -3433,20 +3471,16 @@ static struct cmd_struct commands[] = { {"name", module_name, 0}, {"clone", module_clone, 0}, {"add", module_add, SUPPORT_SUPER_PREFIX}, - {"update-module-mode", module_update_module_mode, 0}, {"update-clone", update_clone, 0}, {"run-update-procedure", run_update_procedure, 0}, - {"ensure-core-worktree", ensure_core_worktree, 0}, {"relative-path", resolve_relative_path, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, {"init", module_init, SUPPORT_SUPER_PREFIX}, {"status", module_status, SUPPORT_SUPER_PREFIX}, - {"print-default-remote", print_default_remote, 0}, {"sync", module_sync, SUPPORT_SUPER_PREFIX}, {"deinit", module_deinit, 0}, {"summary", module_summary, SUPPORT_SUPER_PREFIX}, - {"remote-branch", resolve_remote_submodule_branch, 0}, {"push-check", push_check, 0}, {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"is-active", is_active, 0}, diff --git a/builtin/tag.c b/builtin/tag.c index 2479da0704..e5a8f85693 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -239,7 +239,7 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu { if (sign && do_sign(buf) < 0) return error(_("unable to sign the tag")); - if (write_object_file(buf->buf, buf->len, tag_type, result) < 0) + if (write_object_file(buf->buf, buf->len, OBJ_TAG, result) < 0) return error(_("unable to write tag file")); return 0; } diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 4a9466295b..dbeb0680a5 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -177,7 +177,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) struct object_id oid; if (write_object_file(obj_buf->buffer, obj_buf->size, - type_name(obj->type), &oid) < 0) + obj->type, &oid) < 0) die("failed to write object %s", oid_to_hex(&obj->oid)); obj->flags |= FLAG_WRITTEN; } @@ -243,7 +243,7 @@ static void write_object(unsigned nr, enum object_type type, void *buf, unsigned long size) { if (!strict) { - if (write_object_file(buf, size, type_name(type), + if (write_object_file(buf, size, type, &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); @@ -251,7 +251,7 @@ static void write_object(unsigned nr, enum object_type type, obj_list[nr].obj = NULL; } else if (type == OBJ_BLOB) { struct blob *blob; - if (write_object_file(buf, size, type_name(type), + if (write_object_file(buf, size, type, &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); @@ -266,7 +266,7 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_object_file(the_hash_algo, buf, size, type_name(type), + hash_object_file(the_hash_algo, buf, size, type, &obj_list[nr].oid); added_object(nr, type, buf, size); obj = parse_object_buffer(the_repository, &obj_list[nr].oid, diff --git a/bulk-checkin.c b/bulk-checkin.c index 8785b2ac80..85b3ebaf97 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -220,8 +220,8 @@ static int deflate_to_pack(struct bulk_checkin_state *state, if (seekback == (off_t) -1) return error("cannot find the current offset"); - header_len = xsnprintf((char *)obuf, sizeof(obuf), "%s %" PRIuMAX, - type_name(type), (uintmax_t)size) + 1; + header_len = format_object_header((char *)obuf, sizeof(obuf), + type, size); the_hash_algo->init_fn(&ctx); the_hash_algo->update_fn(&ctx, obuf, header_len); @@ -11,7 +11,7 @@ #include "run-command.h" #include "refs.h" #include "strvec.h" - +#include "list-objects-filter-options.h" static const char v2_bundle_signature[] = "# v2 git bundle\n"; static const char v3_bundle_signature[] = "# v3 git bundle\n"; @@ -33,6 +33,7 @@ void bundle_header_release(struct bundle_header *header) { string_list_clear(&header->prerequisites, 1); string_list_clear(&header->references, 1); + list_objects_filter_release(&header->filter); } static int parse_capability(struct bundle_header *header, const char *capability) @@ -45,6 +46,10 @@ static int parse_capability(struct bundle_header *header, const char *capability header->hash_algo = &hash_algos[algo]; return 0; } + if (skip_prefix(capability, "filter=", &arg)) { + parse_list_objects_filter(&header->filter, arg); + return 0; + } return error(_("unknown capability '%s'"), capability); } @@ -220,6 +225,8 @@ int verify_bundle(struct repository *r, req_nr = revs.pending.nr; setup_revisions(2, argv, &revs, NULL); + list_objects_filter_copy(&revs.filter, &header->filter); + if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -255,18 +262,24 @@ int verify_bundle(struct repository *r, r = &header->references; printf_ln(Q_("The bundle contains this ref:", - "The bundle contains these %d refs:", + "The bundle contains these %"PRIuMAX" refs:", r->nr), - r->nr); + (uintmax_t)r->nr); list_refs(r, 0, NULL); + + if (header->filter.choice) { + printf_ln("The bundle uses this filter: %s", + list_objects_filter_spec(&header->filter)); + } + r = &header->prerequisites; if (!r->nr) { printf_ln(_("The bundle records a complete history.")); } else { printf_ln(Q_("The bundle requires this ref:", - "The bundle requires these %d refs:", + "The bundle requires these %"PRIuMAX" refs:", r->nr), - r->nr); + (uintmax_t)r->nr); list_refs(r, 0, NULL); } } @@ -319,6 +332,9 @@ static int write_pack_data(int bundle_fd, struct rev_info *revs, struct strvec * "--stdout", "--thin", "--delta-base-offset", NULL); strvec_pushv(&pack_objects.args, pack_options->v); + if (revs->filter.choice) + strvec_pushf(&pack_objects.args, "--filter=%s", + list_objects_filter_spec(&revs->filter)); pack_objects.in = -1; pack_objects.out = bundle_fd; pack_objects.git_cmd = 1; @@ -486,10 +502,37 @@ int create_bundle(struct repository *r, const char *path, int bundle_to_stdout; int ref_count = 0; struct rev_info revs, revs_copy; - int min_version = the_hash_algo == &hash_algos[GIT_HASH_SHA1] ? 2 : 3; + int min_version = 2; struct bundle_prerequisites_info bpi; int i; + /* init revs to list objects for pack-objects later */ + save_commit_buffer = 0; + repo_init_revisions(r, &revs, NULL); + + /* + * Pre-initialize the '--objects' flag so we can parse a + * --filter option successfully. + */ + revs.tree_objects = revs.blob_objects = 1; + + argc = setup_revisions(argc, argv, &revs, NULL); + + /* + * Reasons to require version 3: + * + * 1. @object-format is required because our hash algorithm is not + * SHA1. + * 2. @filter is required because we parsed an object filter. + */ + if (the_hash_algo != &hash_algos[GIT_HASH_SHA1] || revs.filter.choice) + min_version = 3; + + if (argc > 1) { + error(_("unrecognized argument: %s"), argv[1]); + goto err; + } + bundle_to_stdout = !strcmp(path, "-"); if (bundle_to_stdout) bundle_fd = 1; @@ -512,17 +555,14 @@ int create_bundle(struct repository *r, const char *path, write_or_die(bundle_fd, capability, strlen(capability)); write_or_die(bundle_fd, the_hash_algo->name, strlen(the_hash_algo->name)); write_or_die(bundle_fd, "\n", 1); - } - /* init revs to list objects for pack-objects later */ - save_commit_buffer = 0; - repo_init_revisions(r, &revs, NULL); - - argc = setup_revisions(argc, argv, &revs, NULL); - - if (argc > 1) { - error(_("unrecognized argument: %s"), argv[1]); - goto err; + if (revs.filter.choice) { + const char *value = expand_list_objects_filter_spec(&revs.filter); + capability = "@filter="; + write_or_die(bundle_fd, capability, strlen(capability)); + write_or_die(bundle_fd, value, strlen(value)); + write_or_die(bundle_fd, "\n", 1); + } } /* save revs.pending in revs_copy for later use */ @@ -544,6 +584,12 @@ int create_bundle(struct repository *r, const char *path, die("revision walk setup failed"); bpi.fd = bundle_fd; bpi.pending = &revs_copy.pending; + + /* + * Remove any object walking here. We only care about commits and + * tags here. The revs_copy has the right instances of these values. + */ + revs.blob_objects = revs.tree_objects = 0; traverse_commit_list(&revs, write_bundle_prerequisites, NULL, &bpi); object_array_remove_duplicates(&revs_copy.pending); @@ -574,6 +620,10 @@ int unbundle(struct repository *r, struct bundle_header *header, struct child_process ip = CHILD_PROCESS_INIT; strvec_pushl(&ip.args, "index-pack", "--fix-thin", "--stdin", NULL); + /* If there is a filter, then we need to create the promisor pack. */ + if (header->filter.choice) + strvec_push(&ip.args, "--promisor=from-bundle"); + if (extra_index_pack_args) { strvec_pushv(&ip.args, extra_index_pack_args->v); strvec_clear(extra_index_pack_args); @@ -4,12 +4,14 @@ #include "strvec.h" #include "cache.h" #include "string-list.h" +#include "list-objects-filter-options.h" struct bundle_header { unsigned version; struct string_list prerequisites; struct string_list references; const struct git_hash_algo *hash_algo; + struct list_objects_filter_options filter; }; #define BUNDLE_HEADER_INIT \ diff --git a/cache-tree.c b/cache-tree.c index 65ca993361..6752f69d51 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -432,15 +432,15 @@ static int update_one(struct cache_tree *it, if (repair) { struct object_id oid; hash_object_file(the_hash_algo, buffer.buf, buffer.len, - tree_type, &oid); + OBJ_TREE, &oid); if (has_object_file_with_flags(&oid, OBJECT_INFO_SKIP_FETCH_OBJECT)) oidcpy(&it->oid, &oid); else to_invalidate = 1; } else if (dryrun) { hash_object_file(the_hash_algo, buffer.buf, buffer.len, - tree_type, &it->oid); - } else if (write_object_file_flags(buffer.buf, buffer.len, tree_type, + OBJ_TREE, &it->oid); + } else if (write_object_file_flags(buffer.buf, buffer.len, OBJ_TREE, &it->oid, flags & WRITE_TREE_SILENT ? HASH_SILENT : 0)) { strbuf_release(&buffer); @@ -948,7 +948,7 @@ static int verify_one(struct repository *r, strbuf_addf(&tree_buf, "%o %.*s%c", mode, entlen, name, '\0'); strbuf_add(&tree_buf, oid->hash, r->hash_algo->rawsz); } - hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, tree_type, + hash_object_file(r->hash_algo, tree_buf.buf, tree_buf.len, OBJ_TREE, &new_oid); if (!oideq(&new_oid, &it->oid)) BUG("cache-tree for path %.*s does not match. " @@ -1320,9 +1320,23 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, struct object_info; int parse_loose_header(const char *hdr, struct object_info *oi); +/** + * With in-core object data in "buf", rehash it to make sure the + * object name actually matches "oid" to detect object corruption. + * + * A negative value indicates an error, usually that the OID is not + * what we expected, but it might also indicate another error. + */ int check_object_signature(struct repository *r, const struct object_id *oid, - void *buf, unsigned long size, const char *type, - struct object_id *real_oidp); + void *map, unsigned long size, + enum object_type type); + +/** + * A streaming version of check_object_signature(). + * Try reading the object named with "oid" using + * the streaming interface and rehash it to do the same. + */ +int stream_object_signature(struct repository *r, const struct object_id *oid); int finalize_object_file(const char *tmpfile, const char *filename); @@ -1549,7 +1563,7 @@ int cache_name_stage_compare(const char *name1, int len1, int stage1, const char void *read_object_with_reference(struct repository *r, const struct object_id *oid, - const char *required_type, + enum object_type required_type, unsigned long *size, struct object_id *oid_ret); diff --git a/commit-graph.c b/commit-graph.c index b8cde7ea27..adffd020dd 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -39,8 +39,8 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */ #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */ -#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154 /* "GDAT" */ -#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56 /* "GDOV" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444132 /* "GDA2" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f32 /* "GDO2" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */ @@ -407,6 +407,9 @@ struct commit_graph *parse_commit_graph(struct repository *r, &graph->chunk_generation_data); pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, &graph->chunk_generation_data_overflow); + + if (graph->chunk_generation_data) + graph->read_generation_data = 1; } if (r->settings.commit_graph_read_changed_paths) { @@ -803,7 +806,7 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, die(_("commit-graph requires overflow generation data but has none")); offset_pos = offset ^ CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW; - graph_data->generation = get_be64(g->chunk_generation_data_overflow + 8 * offset_pos); + graph_data->generation = item->date + get_be64(g->chunk_generation_data_overflow + 8 * offset_pos); } else graph_data->generation = item->date + offset; } else @@ -1556,12 +1559,16 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) if (current->date && current->date > max_corrected_commit_date) max_corrected_commit_date = current->date - 1; commit_graph_data_at(current)->generation = max_corrected_commit_date + 1; - - if (commit_graph_data_at(current)->generation - current->date > GENERATION_NUMBER_V2_OFFSET_MAX) - ctx->num_generation_data_overflows++; } } } + + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset = commit_graph_data_at(c)->generation - c->date; + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) + ctx->num_generation_data_overflows++; + } stop_progress(&ctx->progress); } @@ -1691,10 +1698,10 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx, dirlen = packname.len; if (ctx->report_progress) { strbuf_addf(&progress_title, - Q_("Finding commits for commit graph in %d pack", - "Finding commits for commit graph in %d packs", + Q_("Finding commits for commit graph in %"PRIuMAX" pack", + "Finding commits for commit graph in %"PRIuMAX" packs", pack_indexes->nr), - pack_indexes->nr); + (uintmax_t)pack_indexes->nr); ctx->progress = start_delayed_progress(progress_title.buf, 0); ctx->progress_done = 0; } @@ -1568,7 +1568,7 @@ int commit_tree_extended(const char *msg, size_t msg_len, goto out; } - result = write_object_file(buffer.buf, buffer.len, commit_type, ret); + result = write_object_file(buffer.buf, buffer.len, OBJ_COMMIT, ret); out: strbuf_release(&buffer); return result; @@ -1713,7 +1713,7 @@ size_t ignore_non_trailer(const char *buf, size_t len) } int run_commit_hook(int editor_is_used, const char *index_file, - const char *name, ...) + int *invoked_hook, const char *name, ...) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; va_list args; @@ -369,7 +369,8 @@ int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused) int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused); LAST_ARG_MUST_BE_NULL -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...); +int run_commit_hook(int editor_is_used, const char *index_file, + int *invoked_hook, const char *name, ...); /* Sign a commit or tag buffer, storing the result in a header. */ int sign_with_header(struct strbuf *buf, const char *keyid); diff --git a/compat/mingw.c b/compat/mingw.c index 03af369b2b..58f347d6ae 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -961,9 +961,11 @@ static inline void time_t_to_filetime(time_t t, FILETIME *ft) int mingw_utime (const char *file_name, const struct utimbuf *times) { FILETIME mft, aft; - int fh, rc; + int rc; DWORD attrs; wchar_t wfilename[MAX_PATH]; + HANDLE osfilehandle; + if (xutftowcs_path(wfilename, file_name) < 0) return -1; @@ -975,7 +977,17 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY); } - if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) { + osfilehandle = CreateFileW(wfilename, + FILE_WRITE_ATTRIBUTES, + 0 /*FileShare.None*/, + NULL, + OPEN_EXISTING, + (attrs != INVALID_FILE_ATTRIBUTES && + (attrs & FILE_ATTRIBUTE_DIRECTORY)) ? + FILE_FLAG_BACKUP_SEMANTICS : 0, + NULL); + if (osfilehandle == INVALID_HANDLE_VALUE) { + errno = err_win_to_posix(GetLastError()); rc = -1; goto revert_attrs; } @@ -987,12 +999,15 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) GetSystemTimeAsFileTime(&mft); aft = mft; } - if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &aft, &mft)) { + + if (!SetFileTime(osfilehandle, NULL, &aft, &mft)) { errno = EINVAL; rc = -1; } else rc = 0; - close(fh); + + if (osfilehandle != INVALID_HANDLE_VALUE) + CloseHandle(osfilehandle); revert_attrs: if (attrs != INVALID_FILE_ATTRIBUTES && diff --git a/compat/terminal.c b/compat/terminal.c index 5b903e7c7e..3620184e79 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -11,7 +11,7 @@ static void restore_term_on_signal(int sig) { restore_term(); - sigchain_pop(sig); + /* restore_term calls sigchain_pop_common */ raise(sig); } @@ -31,14 +31,20 @@ void restore_term(void) tcsetattr(term_fd, TCSAFLUSH, &old_term); close(term_fd); term_fd = -1; + sigchain_pop_common(); } int save_term(int full_duplex) { if (term_fd < 0) term_fd = open("/dev/tty", O_RDWR); + if (term_fd < 0) + return -1; + if (tcgetattr(term_fd, &old_term) < 0) + return -1; + sigchain_push_common(restore_term_on_signal); - return (term_fd < 0) ? -1 : tcgetattr(term_fd, &old_term); + return 0; } static int disable_bits(tcflag_t bits) @@ -49,12 +55,16 @@ static int disable_bits(tcflag_t bits) goto error; t = old_term; - sigchain_push_common(restore_term_on_signal); t.c_lflag &= ~bits; + if (bits & ICANON) { + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + } if (!tcsetattr(term_fd, TCSAFLUSH, &t)) return 0; + sigchain_pop_common(); error: close(term_fd); term_fd = -1; @@ -100,6 +110,8 @@ void restore_term(void) return; } + sigchain_pop_common(); + if (hconin == INVALID_HANDLE_VALUE) return; @@ -134,6 +146,7 @@ int save_term(int full_duplex) GetConsoleMode(hconin, &cmode_in); use_stty = 0; + sigchain_push_common(restore_term_on_signal); return 0; error: CloseHandle(hconin); @@ -150,7 +163,11 @@ static int disable_bits(DWORD bits) if (bits & ENABLE_LINE_INPUT) { string_list_append(&stty_restore, "icanon"); - strvec_push(&cp.args, "-icanon"); + /* + * POSIX allows VMIN and VTIME to overlap with VEOF and + * VEOL - let's hope that is not the case on windows. + */ + strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL); } if (bits & ENABLE_ECHO_INPUT) { @@ -177,10 +194,10 @@ static int disable_bits(DWORD bits) if (save_term(0) < 0) return -1; - sigchain_push_common(restore_term_on_signal); if (!SetConsoleMode(hconin, cmode_in & ~bits)) { CloseHandle(hconin); hconin = INVALID_HANDLE_VALUE; + sigchain_pop_common(); return -1; } @@ -385,7 +402,7 @@ int read_key_without_echo(struct strbuf *buf) ch = getchar(); if (ch == EOF) - return 0; + break; strbuf_addch(buf, ch); } } diff --git a/compat/terminal.h b/compat/terminal.h index e1770c575b..0fb9fa147c 100644 --- a/compat/terminal.h +++ b/compat/terminal.h @@ -1,7 +1,15 @@ #ifndef COMPAT_TERMINAL_H #define COMPAT_TERMINAL_H +/* + * Save the terminal attributes so they can be restored later by a + * call to restore_term(). Note that every successful call to + * save_term() must be matched by a call to restore_term() even if the + * attributes have not been changed. Returns 0 on success, -1 on + * failure. + */ int save_term(int full_duplex); +/* Restore the terminal attributes that were saved with save_term() */ void restore_term(void); char *git_terminal_prompt(const char *prompt, int echo); @@ -1159,7 +1159,7 @@ static int ident_to_worktree(const char *src, size_t len, /* are we "faking" in place editing ? */ if (src == buf->buf) to_free = strbuf_detach(buf, NULL); - hash_object_file(the_hash_algo, src, len, "blob", &oid); + hash_object_file(the_hash_algo, src, len, OBJ_BLOB, &oid); strbuf_grow(buf, len + cnt * (the_hash_algo->hexsz + 3)); for (;;) { diff --git a/diffcore-rename.c b/diffcore-rename.c index bebd4ed6a4..c0422d9e70 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -261,7 +261,7 @@ static unsigned int hash_filespec(struct repository *r, if (diff_populate_filespec(r, filespec, NULL)) return 0; hash_object_file(r->hash_algo, filespec->data, filespec->size, - "blob", &filespec->oid); + OBJ_BLOB, &filespec->oid); } return oidhash(&filespec->oid); } @@ -1113,7 +1113,7 @@ static int add_patterns(const char *fname, const char *base, int baselen, &istate->cache[pos]->oid); else hash_object_file(the_hash_algo, buf, size, - "blob", &oid_stat->oid); + OBJ_BLOB, &oid_stat->oid); fill_stat_data(&oid_stat->stat, &st); oid_stat->valid = 1; } @@ -1463,10 +1463,11 @@ static int path_in_sparse_checkout_1(const char *path, const char *end, *slash; /* - * We default to accepting a path if there are no patterns or - * they are of the wrong type. + * We default to accepting a path if the path is empty, there are no + * patterns, or the patterns are of the wrong type. */ - if (init_sparse_checkout_patterns(istate) || + if (!*path || + init_sparse_checkout_patterns(istate) || (require_cone_mode && !istate->sparse_checkout_patterns->use_cone_patterns)) return 1; @@ -2781,7 +2782,8 @@ void remove_untracked_cache(struct index_state *istate) static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir, int base_len, - const struct pathspec *pathspec) + const struct pathspec *pathspec, + struct index_state *istate) { struct untracked_cache_dir *root; static int untracked_cache_disabled = -1; @@ -2845,8 +2847,11 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return NULL; } - if (!dir->untracked->root) + if (!dir->untracked->root) { + /* Untracked cache existed but is not initialized; fix that */ FLEX_ALLOC_STR(dir->untracked->root, name, ""); + istate->cache_changed |= UNTRACKED_CHANGED; + } /* Validate $GIT_DIR/info/exclude and core.excludesfile */ root = dir->untracked->root; @@ -2916,7 +2921,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate, return dir->nr; } - untracked = validate_untracked_cache(dir, len, pathspec); + untracked = validate_untracked_cache(dir, len, pathspec, istate); if (!untracked) /* * make sure untracked cache code path is disabled, diff --git a/git-submodule.sh b/git-submodule.sh index 87772ac891..aa8bdfca9d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -247,20 +247,6 @@ cmd_deinit() git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" } -# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>] -# Because arguments are positional, use an empty string to omit <depth> -# but include <sha1>. -fetch_in_submodule () ( - sanitize_submodule_env && - cd "$1" && - if test $# -eq 3 - then - echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"} - else - git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"} - fi -) - # # Update each submodule path to correct revision, using clone and checkout as needed # @@ -370,19 +356,11 @@ cmd_update() shift done - if test -n "$filter" && test "$init" != "1" - then - usage - fi - - if test -n "$init" - then - cmd_init "--" "$@" || return - fi - { - git submodule--helper update-clone ${GIT_QUIET:+--quiet} \ + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \ + ${GIT_QUIET:+--quiet} \ ${progress:+"--progress"} \ + ${init:+--init} \ ${wt_prefix:+--prefix "$wt_prefix"} \ ${prefix:+--recursive-prefix "$prefix"} \ ${update:+--update "$update"} \ @@ -402,33 +380,11 @@ cmd_update() do die_if_unmatched "$quickabort" "$sha1" - git submodule--helper ensure-core-worktree "$sm_path" || exit 1 - displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") - if test $just_cloned -eq 1 + if test $just_cloned -eq 0 then - subsha1= - else just_cloned= - subsha1=$(sanitize_submodule_env; cd "$sm_path" && - git rev-parse --verify HEAD) || - die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")" - fi - - if test -n "$remote" - then - branch=$(git submodule--helper remote-branch "$sm_path") - if test -z "$nofetch" - then - # Fetch remote before determining tracking $sha1 - fetch_in_submodule "$sm_path" $depth || - die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")" - fi - remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote) - sha1=$(sanitize_submodule_env; cd "$sm_path" && - git rev-parse --verify "${remote_name}/${branch}") || - die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")" fi out=$(git submodule--helper run-update-procedure \ @@ -441,7 +397,7 @@ cmd_update() ${update:+--update "$update"} \ ${prefix:+--recursive-prefix "$prefix"} \ ${sha1:+--oid "$sha1"} \ - ${subsha1:+--suboid "$subsha1"} \ + ${remote:+--remote} \ "--" \ "$sm_path") diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fbd1c20a23..606b50104c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4213,8 +4213,7 @@ sub git_header_html { my %opts = @_; my $title = get_page_title(); - my $content_type = get_content_type_html(); - print $cgi->header(-type=>$content_type, -charset => 'utf-8', + print $cgi->header(-type=>get_content_type_html(), -charset => 'utf-8', -status=> $status, -expires => $expires) unless ($opts{'-no_http_header'}); my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; @@ -4225,7 +4224,6 @@ sub git_header_html { <!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> <!-- git core binaries version $git_version --> <head> -<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> <meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/> <meta name="robots" content="index, nofollow"/> <title>$title</title> @@ -96,9 +96,13 @@ static int notify_hook_finished(int result, void *pp_task_cb) { struct hook_cb_data *hook_cb = pp_cb; + struct run_hooks_opt *opt = hook_cb->options; hook_cb->rc |= result; + if (opt->invoked_hook) + *opt->invoked_hook = 1; + return 0; } @@ -123,6 +127,9 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options) if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); + if (options->invoked_hook) + *options->invoked_hook = 0; + if (!hook_path && !options->error_if_missing) goto cleanup; @@ -18,6 +18,18 @@ struct run_hooks_opt * translates to "struct child_process"'s "dir" member. */ const char *dir; + + /** + * A pointer which if provided will be set to 1 or 0 depending + * on if a hook was started, regardless of whether or not that + * was successful. I.e. if the underlying start_command() was + * successful this will be set to 1. + * + * Used for avoiding TOCTOU races in code that would otherwise + * call hook_exist() after a "maybe hook run" to see if a hook + * was invoked. + */ + int *invoked_hook; }; #define RUN_HOOKS_OPT_INIT { \ diff --git a/http-push.c b/http-push.c index 3309aaf004..f0c044dcf7 100644 --- a/http-push.c +++ b/http-push.c @@ -363,7 +363,7 @@ static void start_put(struct transfer_request *request) git_zstream stream; unpacked = read_object_file(&request->obj->oid, &type, &len); - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX , type_name(type), (uintmax_t)len) + 1; + hdrlen = format_object_header(hdr, sizeof(hdr), type, len); /* Set it up */ git_deflate_init(&stream, zlib_compression_level); diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index fd8d59f653..f02d8df142 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -40,22 +40,7 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c) BUG("list_object_filter_config_name: invalid argument '%d'", c); } -/* - * Parse value of the argument to the "filter" keyword. - * On the command line this looks like: - * --filter=<arg> - * and in the pack protocol as: - * "filter" SP <arg> - * - * The filter keyword will be used by many commands. - * See Documentation/rev-list-options.txt for allowed values for <arg>. - * - * Capture the given arg as the "filter_spec". This can be forwarded to - * subordinate commands when necessary (although it's better to pass it through - * expand_list_objects_filter_spec() first). We also "intern" the arg for the - * convenience of the current command. - */ -static int gently_parse_list_objects_filter( +int gently_parse_list_objects_filter( struct list_objects_filter_options *filter_options, const char *arg, struct strbuf *errbuf) @@ -415,3 +400,22 @@ void partial_clone_get_default_filter_spec( &errbuf); strbuf_release(&errbuf); } + +void list_objects_filter_copy( + struct list_objects_filter_options *dest, + const struct list_objects_filter_options *src) +{ + int i; + struct string_list_item *item; + + /* Copy everything. We will overwrite the pointers shortly. */ + memcpy(dest, src, sizeof(struct list_objects_filter_options)); + + string_list_init_dup(&dest->filter_spec); + for_each_string_list_item(item, &src->filter_spec) + string_list_append(&dest->filter_spec, item->string); + + ALLOC_ARRAY(dest->sub, dest->sub_alloc); + for (i = 0; i < src->sub_nr; i++) + list_objects_filter_copy(&dest->sub[i], &src->sub[i]); +} diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h index da5b6737e2..2eb6c98394 100644 --- a/list-objects-filter-options.h +++ b/list-objects-filter-options.h @@ -72,6 +72,26 @@ struct list_objects_filter_options { /* Normalized command line arguments */ #define CL_ARG__FILTER "filter" +/* + * Parse value of the argument to the "filter" keyword. + * On the command line this looks like: + * --filter=<arg> + * and in the pack protocol as: + * "filter" SP <arg> + * + * The filter keyword will be used by many commands. + * See Documentation/rev-list-options.txt for allowed values for <arg>. + * + * Capture the given arg as the "filter_spec". This can be forwarded to + * subordinate commands when necessary (although it's better to pass it through + * expand_list_objects_filter_spec() first). We also "intern" the arg for the + * convenience of the current command. + */ +int gently_parse_list_objects_filter( + struct list_objects_filter_options *filter_options, + const char *arg, + struct strbuf *errbuf); + void list_objects_filter_die_if_populated( struct list_objects_filter_options *filter_options); @@ -132,4 +152,8 @@ void partial_clone_get_default_filter_spec( struct list_objects_filter_options *filter_options, const char *remote); +void list_objects_filter_copy( + struct list_objects_filter_options *dest, + const struct list_objects_filter_options *src); + #endif /* LIST_OBJECTS_FILTER_OPTIONS_H */ diff --git a/list-objects.c b/list-objects.c index 2f623f8211..250d9de41c 100644 --- a/list-objects.c +++ b/list-objects.c @@ -21,6 +21,23 @@ struct traversal_context { struct filter *filter; }; +static void show_commit(struct traversal_context *ctx, + struct commit *commit) +{ + if (!ctx->show_commit) + return; + ctx->show_commit(commit, ctx->show_data); +} + +static void show_object(struct traversal_context *ctx, + struct object *object, + const char *name) +{ + if (!ctx->show_object) + return; + ctx->show_object(object, name, ctx->show_data); +} + static void process_blob(struct traversal_context *ctx, struct blob *blob, struct strbuf *path, @@ -60,7 +77,7 @@ static void process_blob(struct traversal_context *ctx, if (r & LOFR_MARK_SEEN) obj->flags |= SEEN; if (r & LOFR_DO_SHOW) - ctx->show_object(obj, path->buf, ctx->show_data); + show_object(ctx, obj, path->buf); strbuf_setlen(path, pathlen); } @@ -194,7 +211,7 @@ static void process_tree(struct traversal_context *ctx, if (r & LOFR_MARK_SEEN) obj->flags |= SEEN; if (r & LOFR_DO_SHOW) - ctx->show_object(obj, base->buf, ctx->show_data); + show_object(ctx, obj, base->buf); if (base->len) strbuf_addch(base, '/'); @@ -210,7 +227,7 @@ static void process_tree(struct traversal_context *ctx, if (r & LOFR_MARK_SEEN) obj->flags |= SEEN; if (r & LOFR_DO_SHOW) - ctx->show_object(obj, base->buf, ctx->show_data); + show_object(ctx, obj, base->buf); strbuf_setlen(base, baselen); free_tree_buffer(tree); @@ -228,7 +245,7 @@ static void process_tag(struct traversal_context *ctx, if (r & LOFR_MARK_SEEN) tag->object.flags |= SEEN; if (r & LOFR_DO_SHOW) - ctx->show_object(&tag->object, name, ctx->show_data); + show_object(ctx, &tag->object, name); } static void mark_edge_parents_uninteresting(struct commit *commit, @@ -402,7 +419,7 @@ static void do_traverse(struct traversal_context *ctx) if (r & LOFR_MARK_SEEN) commit->object.flags |= SEEN; if (r & LOFR_DO_SHOW) - ctx->show_commit(commit, ctx->show_data); + show_commit(ctx, commit); if (ctx->revs->tree_blobs_in_commit_order) /* @@ -416,35 +433,25 @@ static void do_traverse(struct traversal_context *ctx) strbuf_release(&csp); } -void traverse_commit_list(struct rev_info *revs, - show_commit_fn show_commit, - show_object_fn show_object, - void *show_data) -{ - struct traversal_context ctx; - ctx.revs = revs; - ctx.show_commit = show_commit; - ctx.show_object = show_object; - ctx.show_data = show_data; - ctx.filter = NULL; - do_traverse(&ctx); -} - void traverse_commit_list_filtered( - struct list_objects_filter_options *filter_options, struct rev_info *revs, show_commit_fn show_commit, show_object_fn show_object, void *show_data, struct oidset *omitted) { - struct traversal_context ctx; + struct traversal_context ctx = { + .revs = revs, + .show_object = show_object, + .show_commit = show_commit, + .show_data = show_data, + }; + + if (revs->filter.choice) + ctx.filter = list_objects_filter__init(omitted, &revs->filter); - ctx.revs = revs; - ctx.show_object = show_object; - ctx.show_commit = show_commit; - ctx.show_data = show_data; - ctx.filter = list_objects_filter__init(omitted, filter_options); do_traverse(&ctx); - list_objects_filter__free(ctx.filter); + + if (ctx.filter) + list_objects_filter__free(ctx.filter); } diff --git a/list-objects.h b/list-objects.h index a952680e46..9eaf4de844 100644 --- a/list-objects.h +++ b/list-objects.h @@ -7,7 +7,6 @@ struct rev_info; typedef void (*show_commit_fn)(struct commit *, void *); typedef void (*show_object_fn)(struct object *, const char *, void *); -void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *); typedef void (*show_edge_fn)(struct commit *); void mark_edges_uninteresting(struct rev_info *revs, @@ -18,11 +17,20 @@ struct oidset; struct list_objects_filter_options; void traverse_commit_list_filtered( - struct list_objects_filter_options *filter_options, struct rev_info *revs, show_commit_fn show_commit, show_object_fn show_object, void *show_data, struct oidset *omitted); +static inline void traverse_commit_list( + struct rev_info *revs, + show_commit_fn show_commit, + show_object_fn show_object, + void *show_data) +{ + traverse_commit_list_filtered(revs, show_commit, + show_object, show_data, NULL); +} + #endif /* LIST_OBJECTS_H */ diff --git a/log-tree.c b/log-tree.c index 25165e2a91..38e5cccc1a 100644 --- a/log-tree.c +++ b/log-tree.c @@ -565,7 +565,7 @@ static int show_one_mergetag(struct commit *commit, struct strbuf signature = STRBUF_INIT; hash_object_file(the_hash_algo, extra->value, extra->len, - type_name(OBJ_TAG), &oid); + OBJ_TAG, &oid); tag = lookup_tag(the_repository, &oid); if (!tag) return -1; /* error message already given */ @@ -43,8 +43,8 @@ static void free_mailmap_info(void *p, const char *s) static void free_mailmap_entry(void *p, const char *s) { struct mailmap_entry *me = (struct mailmap_entry *)p; - debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", - s, me->namemap.nr); + debug_mm("mailmap: removing entries for <%s>, with %"PRIuMAX" sub-entries\n", + s, (uintmax_t)me->namemap.nr); debug_mm("mailmap: - simple: '%s' <%s>\n", debug_str(me->name), debug_str(me->email)); @@ -250,7 +250,8 @@ int read_mailmap(struct string_list *map) void clear_mailmap(struct string_list *map) { - debug_mm("mailmap: clearing %d entries...\n", map->nr); + debug_mm("mailmap: clearing %"PRIuMAX" entries...\n", + (uintmax_t)map->nr); map->strdup_strings = 1; string_list_clear_func(map, free_mailmap_entry); debug_mm("mailmap: cleared\n"); diff --git a/match-trees.c b/match-trees.c index df413989fa..49398e599f 100644 --- a/match-trees.c +++ b/match-trees.c @@ -235,7 +235,7 @@ static int splice_tree(const struct object_id *oid1, const char *prefix, rewrite_with = oid2; } hashcpy(rewrite_here, rewrite_with->hash); - status = write_object_file(buf, sz, tree_type, result); + status = write_object_file(buf, sz, OBJ_TREE, result); free(buf); return status; } diff --git a/merge-ort.c b/merge-ort.c index 97da477c7e..8545354daf 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -1938,7 +1938,7 @@ static int handle_content_merge(struct merge_options *opt, if (!ret && write_object_file(result_buf.ptr, result_buf.size, - blob_type, &result->oid)) + OBJ_BLOB, &result->oid)) ret = err(opt, _("Unable to add %s to database"), path); @@ -3392,7 +3392,7 @@ static void write_tree(struct object_id *result_oid, } /* Write this object file out, and record in result_oid */ - write_object_file(buf.buf, buf.len, tree_type, result_oid); + write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid); strbuf_release(&buf); } @@ -4064,8 +4064,8 @@ static void process_entries(struct merge_options *opt, trace2_region_enter("merge", "process_entries cleanup", opt->repo); if (dir_metadata.offsets.nr != 1 || (uintptr_t)dir_metadata.offsets.items[0].util != 0) { - printf("dir_metadata.offsets.nr = %d (should be 1)\n", - dir_metadata.offsets.nr); + printf("dir_metadata.offsets.nr = %"PRIuMAX" (should be 1)\n", + (uintmax_t)dir_metadata.offsets.nr); printf("dir_metadata.offsets.items[0].util = %u (should be 0)\n", (unsigned)(uintptr_t)dir_metadata.offsets.items[0].util); fflush(stdout); diff --git a/merge-recursive.c b/merge-recursive.c index 9ec1e6d043..1ee6364e8b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1376,7 +1376,7 @@ static int merge_mode_and_contents(struct merge_options *opt, if (!ret && write_object_file(result_buf.ptr, result_buf.size, - blob_type, &result->blob.oid)) + OBJ_BLOB, &result->blob.oid)) ret = err(opt, _("Unable to add %s to database"), a->path); diff --git a/notes-cache.c b/notes-cache.c index 2473314d68..9dfd251a81 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -92,7 +92,7 @@ int notes_cache_put(struct notes_cache *c, struct object_id *key_oid, { struct object_id value_oid; - if (write_object_file(data, size, "blob", &value_oid) < 0) + if (write_object_file(data, size, OBJ_BLOB, &value_oid) < 0) return -1; return add_note(&c->tree, key_oid, &value_oid, NULL); } @@ -675,7 +675,7 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws) ret = tree_write_stack_finish_subtree(n); if (ret) return ret; - ret = write_object_file(n->buf.buf, n->buf.len, tree_type, &s); + ret = write_object_file(n->buf.buf, n->buf.len, OBJ_TREE, &s); if (ret) return ret; strbuf_release(&n->buf); @@ -836,7 +836,7 @@ int combine_notes_concatenate(struct object_id *cur_oid, free(new_msg); /* create a new blob object from buf */ - ret = write_object_file(buf, buf_len, blob_type, cur_oid); + ret = write_object_file(buf, buf_len, OBJ_BLOB, cur_oid); free(buf); return ret; } @@ -916,7 +916,7 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid, string_list_join_lines_helper, &buf)) goto out; - ret = write_object_file(buf.buf, buf.len, blob_type, cur_oid); + ret = write_object_file(buf.buf, buf.len, OBJ_BLOB, cur_oid); out: strbuf_release(&buf); @@ -1192,7 +1192,7 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result) ret = for_each_note(t, flags, write_each_note, &cb_data) || write_each_non_note_until(NULL, &cb_data) || tree_write_stack_finish_subtree(&root) || - write_object_file(root.buf.buf, root.buf.len, tree_type, result); + write_object_file(root.buf.buf, root.buf.len, OBJ_TREE, result); strbuf_release(&root.buf); return ret; } diff --git a/object-file.c b/object-file.c index 03bd6a3baf..bdc5cbdd38 100644 --- a/object-file.c +++ b/object-file.c @@ -1049,35 +1049,50 @@ void *xmmap(void *start, size_t length, return ret; } -/* - * With an in-core object data in "map", rehash it to make sure the - * object name actually matches "oid" to detect object corruption. - * With "map" == NULL, try reading the object named with "oid" using - * the streaming interface and rehash it to do the same. - */ +static int format_object_header_literally(char *str, size_t size, + const char *type, size_t objsize) +{ + return xsnprintf(str, size, "%s %"PRIuMAX, type, (uintmax_t)objsize) + 1; +} + +int format_object_header(char *str, size_t size, enum object_type type, + size_t objsize) +{ + const char *name = type_name(type); + + if (!name) + BUG("could not get a type name for 'enum object_type' value %d", type); + + return format_object_header_literally(str, size, name, objsize); +} + int check_object_signature(struct repository *r, const struct object_id *oid, - void *map, unsigned long size, const char *type, - struct object_id *real_oidp) + void *buf, unsigned long size, + enum object_type type) { - struct object_id tmp; - struct object_id *real_oid = real_oidp ? real_oidp : &tmp; + struct object_id real_oid; + + hash_object_file(r->hash_algo, buf, size, type, &real_oid); + + return !oideq(oid, &real_oid) ? -1 : 0; +} + +int stream_object_signature(struct repository *r, const struct object_id *oid) +{ + struct object_id real_oid; + unsigned long size; enum object_type obj_type; struct git_istream *st; git_hash_ctx c; char hdr[MAX_HEADER_LEN]; int hdrlen; - if (map) { - hash_object_file(r->hash_algo, map, size, type, real_oid); - return !oideq(oid, real_oid) ? -1 : 0; - } - st = open_istream(r, oid, &obj_type, &size, NULL); if (!st) return -1; /* Generate the header */ - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX , type_name(obj_type), (uintmax_t)size) + 1; + hdrlen = format_object_header(hdr, sizeof(hdr), obj_type, size); /* Sha1.. */ r->hash_algo->init_fn(&c); @@ -1094,9 +1109,9 @@ int check_object_signature(struct repository *r, const struct object_id *oid, break; r->hash_algo->update_fn(&c, buf, readlen); } - r->hash_algo->final_oid_fn(real_oid, &c); + r->hash_algo->final_oid_fn(&real_oid, &c); close_istream(st); - return !oideq(oid, real_oid) ? -1 : 0; + return !oideq(oid, &real_oid) ? -1 : 0; } int git_open_cloexec(const char *name, int flags) @@ -1662,7 +1677,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type, { struct cached_object *co; - hash_object_file(the_hash_algo, buf, len, type_name(type), oid); + hash_object_file(the_hash_algo, buf, len, type, oid); if (has_object_file_with_flags(oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) || find_cached_object(oid)) return 0; @@ -1722,16 +1737,15 @@ void *read_object_file_extended(struct repository *r, void *read_object_with_reference(struct repository *r, const struct object_id *oid, - const char *required_type_name, + enum object_type required_type, unsigned long *size, struct object_id *actual_oid_return) { - enum object_type type, required_type; + enum object_type type; void *buffer; unsigned long isize; struct object_id actual_oid; - required_type = type_from_string(required_type_name); oidcpy(&actual_oid, oid); while (1) { int ref_length = -1; @@ -1769,21 +1783,40 @@ void *read_object_with_reference(struct repository *r, } } +static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c, + const void *buf, unsigned long len, + struct object_id *oid, + char *hdr, int *hdrlen) +{ + algo->init_fn(c); + algo->update_fn(c, hdr, *hdrlen); + algo->update_fn(c, buf, len); + algo->final_oid_fn(oid, c); +} + static void write_object_file_prepare(const struct git_hash_algo *algo, const void *buf, unsigned long len, - const char *type, struct object_id *oid, + enum object_type type, struct object_id *oid, char *hdr, int *hdrlen) { git_hash_ctx c; /* Generate the header */ - *hdrlen = xsnprintf(hdr, *hdrlen, "%s %"PRIuMAX , type, (uintmax_t)len)+1; + *hdrlen = format_object_header(hdr, *hdrlen, type, len); /* Sha1.. */ - algo->init_fn(&c); - algo->update_fn(&c, hdr, *hdrlen); - algo->update_fn(&c, buf, len); - algo->final_oid_fn(oid, &c); + hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); +} + +static void write_object_file_prepare_literally(const struct git_hash_algo *algo, + const void *buf, unsigned long len, + const char *type, struct object_id *oid, + char *hdr, int *hdrlen) +{ + git_hash_ctx c; + + *hdrlen = format_object_header_literally(hdr, *hdrlen, type, len); + hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); } /* @@ -1836,14 +1869,21 @@ static int write_buffer(int fd, const void *buf, size_t len) return 0; } -int hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, const char *type, - struct object_id *oid) +static void hash_object_file_literally(const struct git_hash_algo *algo, + const void *buf, unsigned long len, + const char *type, struct object_id *oid) { char hdr[MAX_HEADER_LEN]; int hdrlen = sizeof(hdr); - write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); - return 0; + + write_object_file_prepare_literally(algo, buf, len, type, oid, hdr, &hdrlen); +} + +void hash_object_file(const struct git_hash_algo *algo, const void *buf, + unsigned long len, enum object_type type, + struct object_id *oid) +{ + hash_object_file_literally(algo, buf, len, type_name(type), oid); } /* Finalize a file on disk, and close it. */ @@ -1998,7 +2038,7 @@ static int freshen_packed_object(const struct object_id *oid) } int write_object_file_flags(const void *buf, unsigned long len, - const char *type, struct object_id *oid, + enum object_type type, struct object_id *oid, unsigned flags) { char hdr[MAX_HEADER_LEN]; @@ -2014,9 +2054,9 @@ int write_object_file_flags(const void *buf, unsigned long len, return write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags); } -int hash_object_file_literally(const void *buf, unsigned long len, - const char *type, struct object_id *oid, - unsigned flags) +int write_object_file_literally(const void *buf, unsigned long len, + const char *type, struct object_id *oid, + unsigned flags) { char *header; int hdrlen, status = 0; @@ -2024,8 +2064,8 @@ int hash_object_file_literally(const void *buf, unsigned long len, /* type string, SP, %lu of the length plus NUL must fit this */ hdrlen = strlen(type) + MAX_HEADER_LEN; header = xmalloc(hdrlen); - write_object_file_prepare(the_hash_algo, buf, len, type, oid, header, - &hdrlen); + write_object_file_prepare_literally(the_hash_algo, buf, len, type, + oid, header, &hdrlen); if (!(flags & HASH_WRITE_OBJECT)) goto cleanup; @@ -2052,7 +2092,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime) buf = read_object(the_repository, oid, &type, &len); if (!buf) return error(_("cannot read object for %s"), oid_to_hex(oid)); - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX , type_name(type), (uintmax_t)len) + 1; + hdrlen = format_object_header(hdr, sizeof(hdr), type, len); ret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0); free(buf); @@ -2118,7 +2158,8 @@ static int index_mem(struct index_state *istate, enum object_type type, const char *path, unsigned flags) { - int ret, re_allocated = 0; + int ret = 0; + int re_allocated = 0; int write_object = flags & HASH_WRITE_OBJECT; if (!type) @@ -2145,10 +2186,9 @@ static int index_mem(struct index_state *istate, } if (write_object) - ret = write_object_file(buf, size, type_name(type), oid); + ret = write_object_file(buf, size, type, oid); else - ret = hash_object_file(the_hash_algo, buf, size, - type_name(type), oid); + hash_object_file(the_hash_algo, buf, size, type, oid); if (re_allocated) free(buf); return ret; @@ -2160,7 +2200,7 @@ static int index_stream_convert_blob(struct index_state *istate, const char *path, unsigned flags) { - int ret; + int ret = 0; const int write_object = flags & HASH_WRITE_OBJECT; struct strbuf sbuf = STRBUF_INIT; @@ -2171,11 +2211,11 @@ static int index_stream_convert_blob(struct index_state *istate, get_conv_flags(flags)); if (write_object) - ret = write_object_file(sbuf.buf, sbuf.len, type_name(OBJ_BLOB), + ret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB, oid); else - ret = hash_object_file(the_hash_algo, sbuf.buf, sbuf.len, - type_name(OBJ_BLOB), oid); + hash_object_file(the_hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB, + oid); strbuf_release(&sbuf); return ret; } @@ -2294,8 +2334,8 @@ int index_path(struct index_state *istate, struct object_id *oid, return error_errno("readlink(\"%s\")", path); if (!(flags & HASH_WRITE_OBJECT)) hash_object_file(the_hash_algo, sb.buf, sb.len, - blob_type, oid); - else if (write_object_file(sb.buf, sb.len, blob_type, oid)) + OBJ_BLOB, oid); + else if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid)) rc = error(_("%s: failed to insert into database"), path); strbuf_release(&sb); break; @@ -2599,9 +2639,10 @@ int read_loose_object(const char *path, git_inflate_end(&stream); goto out; } - if (check_object_signature(the_repository, expected_oid, + hash_object_file_literally(the_repository->hash_algo, *contents, *size, - oi->type_name->buf, real_oid)) + oi->type_name->buf, real_oid); + if (!oideq(expected_oid, real_oid)) goto out; } diff --git a/object-store.h b/object-store.h index 6f89482df0..bd2322ed8c 100644 --- a/object-store.h +++ b/object-store.h @@ -245,22 +245,22 @@ static inline void *repo_read_object_file(struct repository *r, /* Read and unpack an object file into memory, write memory to an object file */ int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); -int hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, const char *type, - struct object_id *oid); +void hash_object_file(const struct git_hash_algo *algo, const void *buf, + unsigned long len, enum object_type type, + struct object_id *oid); int write_object_file_flags(const void *buf, unsigned long len, - const char *type, struct object_id *oid, + enum object_type type, struct object_id *oid, unsigned flags); static inline int write_object_file(const void *buf, unsigned long len, - const char *type, struct object_id *oid) + enum object_type type, struct object_id *oid) { return write_object_file_flags(buf, len, type, oid, 0); } -int hash_object_file_literally(const void *buf, unsigned long len, - const char *type, struct object_id *oid, - unsigned flags); +int write_object_file_literally(const void *buf, unsigned long len, + const char *type, struct object_id *oid, + unsigned flags); /* * Add an object file to the in-memory object store, without writing it @@ -331,6 +331,14 @@ int repo_has_object_file_with_flags(struct repository *r, */ int has_loose_object_nonlocal(const struct object_id *); +/** + * format_object_header() is a thin wrapper around s xsnprintf() that + * writes the initial "<type> <obj-len>" part of the loose object + * header. It returns the size that snprintf() returns + 1. + */ +int format_object_header(char *str, size_t size, enum object_type type, + size_t objsize); + void assert_oid_type(const struct object_id *oid, enum object_type expect); /* @@ -279,7 +279,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) if ((obj && obj->type == OBJ_BLOB && repo_has_object_file(r, oid)) || (!obj && repo_has_object_file(r, oid) && oid_object_info(r, oid, NULL) == OBJ_BLOB)) { - if (check_object_signature(r, repl, NULL, 0, NULL, NULL) < 0) { + if (stream_object_signature(r, repl) < 0) { error(_("hash mismatch %s"), oid_to_hex(oid)); return NULL; } @@ -289,8 +289,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) buffer = repo_read_object_file(r, oid, &type, &size); if (buffer) { - if (check_object_signature(r, repl, buffer, size, - type_name(type), NULL) < 0) { + if (check_object_signature(r, repl, buffer, size, type) < 0) { free(buffer); error(_("hash mismatch %s"), oid_to_hex(repl)); return NULL; @@ -75,7 +75,7 @@ struct object_array { * builtin/fsck.c: 0--3 * builtin/gc.c: 0 * builtin/index-pack.c: 2021 - * builtin/reflog.c: 10--12 + * reflog.c: 10--12 * builtin/show-branch.c: 0-------------------------------------------26 * builtin/unpack-objects.c: 2021 */ diff --git a/pack-bitmap.c b/pack-bitmap.c index 9c666cdb8b..97909d48da 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -739,8 +739,7 @@ static int add_commit_to_bitmap(struct bitmap_index *bitmap_git, static struct bitmap *find_objects(struct bitmap_index *bitmap_git, struct rev_info *revs, struct object_list *roots, - struct bitmap *seen, - struct list_objects_filter_options *filter) + struct bitmap *seen) { struct bitmap *base = NULL; int needs_walk = 0; @@ -823,9 +822,9 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git, show_data.bitmap_git = bitmap_git; show_data.base = base; - traverse_commit_list_filtered(filter, revs, - show_commit, show_object, - &show_data, NULL); + traverse_commit_list(revs, + show_commit, show_object, + &show_data); revs->include_check = NULL; revs->include_check_obj = NULL; @@ -1219,7 +1218,6 @@ static int can_filter_bitmap(struct list_objects_filter_options *filter) } struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { unsigned int i; @@ -1240,7 +1238,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, if (revs->prune) return NULL; - if (!can_filter_bitmap(filter)) + if (!can_filter_bitmap(&revs->filter)) return NULL; /* try to open a bitmapped pack, but don't parse it yet @@ -1297,8 +1295,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, if (haves) { revs->ignore_missing_links = 1; - haves_bitmap = find_objects(bitmap_git, revs, haves, NULL, - filter); + haves_bitmap = find_objects(bitmap_git, revs, haves, NULL); reset_revision_walk(); revs->ignore_missing_links = 0; @@ -1306,8 +1303,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, BUG("failed to perform bitmap walk"); } - wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap, - filter); + wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap); if (!wants_bitmap) BUG("failed to perform bitmap walk"); @@ -1315,8 +1311,10 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, if (haves_bitmap) bitmap_and_not(wants_bitmap, haves_bitmap); - filter_bitmap(bitmap_git, (filter && filter_provided_objects) ? NULL : wants, - wants_bitmap, filter); + filter_bitmap(bitmap_git, + (revs->filter.choice && filter_provided_objects) ? NULL : wants, + wants_bitmap, + &revs->filter); bitmap_git->result = wants_bitmap; bitmap_git->haves = haves_bitmap; diff --git a/pack-bitmap.h b/pack-bitmap.h index 19a63fa1ab..3d3ddd7734 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -10,7 +10,6 @@ struct commit; struct repository; struct rev_info; -struct list_objects_filter_options; static const char BITMAP_IDX_SIGNATURE[] = {'B', 'I', 'T', 'M'}; @@ -54,7 +53,6 @@ void test_bitmap_walk(struct rev_info *revs); int test_bitmap_commits(struct repository *r); int test_bitmap_hashes(struct repository *r); struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects); uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git); int reuse_partial_packfile_from_bitmap(struct bitmap_index *, diff --git a/pack-check.c b/pack-check.c index 3f418e3a6a..bfb593ba72 100644 --- a/pack-check.c +++ b/pack-check.c @@ -127,7 +127,7 @@ static int verify_packfile(struct repository *r, if (type == OBJ_BLOB && big_file_threshold <= size) { /* - * Let check_object_signature() check it with + * Let stream_object_signature() check it with * the streaming interface; no point slurping * the data in-core only to discard. */ @@ -142,8 +142,11 @@ static int verify_packfile(struct repository *r, err = error("cannot unpack %s from %s at offset %"PRIuMAX"", oid_to_hex(&oid), p->pack_name, (uintmax_t)entries[i].offset); - else if (check_object_signature(r, &oid, data, size, - type_name(type), NULL)) + else if (data && check_object_signature(r, &oid, data, size, + type) < 0) + err = error("packed %s from %s is corrupt", + oid_to_hex(&oid), p->pack_name); + else if (!data && stream_object_signature(r, &oid) < 0) err = error("packed %s from %s is corrupt", oid_to_hex(&oid), p->pack_name); else if (fn) { diff --git a/reachable.c b/reachable.c index 84e3d0d75e..b9f4ad886e 100644 --- a/reachable.c +++ b/reachable.c @@ -205,7 +205,7 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog, cp.progress = progress; cp.count = 0; - bitmap_git = prepare_bitmap_walk(revs, NULL, 0); + bitmap_git = prepare_bitmap_walk(revs, 0); if (bitmap_git) { traverse_bitmap_commit_list(bitmap_git, revs, mark_object_seen); free_bitmap_index(bitmap_git); diff --git a/read-cache.c b/read-cache.c index 79b9b99ebf..1ad56d02e1 100644 --- a/read-cache.c +++ b/read-cache.c @@ -736,7 +736,7 @@ static struct cache_entry *create_alias_ce(struct index_state *istate, void set_object_name_for_intent_to_add_entry(struct cache_entry *ce) { struct object_id oid; - if (write_object_file("", 0, blob_type, &oid)) + if (write_object_file("", 0, OBJ_BLOB, &oid)) die(_("cannot create an empty blob in the object database")); oidcpy(&ce->oid, &oid); } diff --git a/reflog.c b/reflog.c new file mode 100644 index 0000000000..47ba8620c5 --- /dev/null +++ b/reflog.c @@ -0,0 +1,434 @@ +#include "cache.h" +#include "object-store.h" +#include "reflog.h" +#include "refs.h" +#include "revision.h" +#include "worktree.h" + +/* Remember to update object flag allocation in object.h */ +#define INCOMPLETE (1u<<10) +#define STUDYING (1u<<11) +#define REACHABLE (1u<<12) + +static int tree_is_complete(const struct object_id *oid) +{ + struct tree_desc desc; + struct name_entry entry; + int complete; + struct tree *tree; + + tree = lookup_tree(the_repository, oid); + if (!tree) + return 0; + if (tree->object.flags & SEEN) + return 1; + if (tree->object.flags & INCOMPLETE) + return 0; + + if (!tree->buffer) { + enum object_type type; + unsigned long size; + void *data = read_object_file(oid, &type, &size); + if (!data) { + tree->object.flags |= INCOMPLETE; + return 0; + } + tree->buffer = data; + tree->size = size; + } + init_tree_desc(&desc, tree->buffer, tree->size); + complete = 1; + while (tree_entry(&desc, &entry)) { + if (!has_object_file(&entry.oid) || + (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) { + tree->object.flags |= INCOMPLETE; + complete = 0; + } + } + free_tree_buffer(tree); + + if (complete) + tree->object.flags |= SEEN; + return complete; +} + +static int commit_is_complete(struct commit *commit) +{ + struct object_array study; + struct object_array found; + int is_incomplete = 0; + int i; + + /* early return */ + if (commit->object.flags & SEEN) + return 1; + if (commit->object.flags & INCOMPLETE) + return 0; + /* + * Find all commits that are reachable and are not marked as + * SEEN. Then make sure the trees and blobs contained are + * complete. After that, mark these commits also as SEEN. + * If some of the objects that are needed to complete this + * commit are missing, mark this commit as INCOMPLETE. + */ + memset(&study, 0, sizeof(study)); + memset(&found, 0, sizeof(found)); + add_object_array(&commit->object, NULL, &study); + add_object_array(&commit->object, NULL, &found); + commit->object.flags |= STUDYING; + while (study.nr) { + struct commit *c; + struct commit_list *parent; + + c = (struct commit *)object_array_pop(&study); + if (!c->object.parsed && !parse_object(the_repository, &c->object.oid)) + c->object.flags |= INCOMPLETE; + + if (c->object.flags & INCOMPLETE) { + is_incomplete = 1; + break; + } + else if (c->object.flags & SEEN) + continue; + for (parent = c->parents; parent; parent = parent->next) { + struct commit *p = parent->item; + if (p->object.flags & STUDYING) + continue; + p->object.flags |= STUDYING; + add_object_array(&p->object, NULL, &study); + add_object_array(&p->object, NULL, &found); + } + } + if (!is_incomplete) { + /* + * make sure all commits in "found" array have all the + * necessary objects. + */ + for (i = 0; i < found.nr; i++) { + struct commit *c = + (struct commit *)found.objects[i].item; + if (!tree_is_complete(get_commit_tree_oid(c))) { + is_incomplete = 1; + c->object.flags |= INCOMPLETE; + } + } + if (!is_incomplete) { + /* mark all found commits as complete, iow SEEN */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags |= SEEN; + } + } + /* clear flags from the objects we traversed */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags &= ~STUDYING; + if (is_incomplete) + commit->object.flags |= INCOMPLETE; + else { + /* + * If we come here, we have (1) traversed the ancestry chain + * from the "commit" until we reach SEEN commits (which are + * known to be complete), and (2) made sure that the commits + * encountered during the above traversal refer to trees that + * are complete. Which means that we know *all* the commits + * we have seen during this process are complete. + */ + for (i = 0; i < found.nr; i++) + found.objects[i].item->flags |= SEEN; + } + /* free object arrays */ + object_array_clear(&study); + object_array_clear(&found); + return !is_incomplete; +} + +static int keep_entry(struct commit **it, struct object_id *oid) +{ + struct commit *commit; + + if (is_null_oid(oid)) + return 1; + commit = lookup_commit_reference_gently(the_repository, oid, 1); + if (!commit) + return 0; + + /* + * Make sure everything in this commit exists. + * + * We have walked all the objects reachable from the refs + * and cache earlier. The commits reachable by this commit + * must meet SEEN commits -- and then we should mark them as + * SEEN as well. + */ + if (!commit_is_complete(commit)) + return 0; + *it = commit; + return 1; +} + +/* + * Starting from commits in the cb->mark_list, mark commits that are + * reachable from them. Stop the traversal at commits older than + * the expire_limit and queue them back, so that the caller can call + * us again to restart the traversal with longer expire_limit. + */ +static void mark_reachable(struct expire_reflog_policy_cb *cb) +{ + struct commit_list *pending; + timestamp_t expire_limit = cb->mark_limit; + struct commit_list *leftover = NULL; + + for (pending = cb->mark_list; pending; pending = pending->next) + pending->item->object.flags &= ~REACHABLE; + + pending = cb->mark_list; + while (pending) { + struct commit_list *parent; + struct commit *commit = pop_commit(&pending); + if (commit->object.flags & REACHABLE) + continue; + if (parse_commit(commit)) + continue; + commit->object.flags |= REACHABLE; + if (commit->date < expire_limit) { + commit_list_insert(commit, &leftover); + continue; + } + commit->object.flags |= REACHABLE; + parent = commit->parents; + while (parent) { + commit = parent->item; + parent = parent->next; + if (commit->object.flags & REACHABLE) + continue; + commit_list_insert(commit, &pending); + } + } + cb->mark_list = leftover; +} + +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid) +{ + /* + * We may or may not have the commit yet - if not, look it + * up using the supplied sha1. + */ + if (!commit) { + if (is_null_oid(oid)) + return 0; + + commit = lookup_commit_reference_gently(the_repository, oid, + 1); + + /* Not a commit -- keep it */ + if (!commit) + return 0; + } + + /* Reachable from the current ref? Don't prune. */ + if (commit->object.flags & REACHABLE) + return 0; + + if (cb->mark_list && cb->mark_limit) { + cb->mark_limit = 0; /* dig down to the root */ + mark_reachable(cb); + } + + return !(commit->object.flags & REACHABLE); +} + +/* + * Return true iff the specified reflog entry should be expired. + */ +int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; + struct commit *old_commit, *new_commit; + + if (timestamp < cb->cmd.expire_total) + return 1; + + old_commit = new_commit = NULL; + if (cb->cmd.stalefix && + (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid))) + return 1; + + if (timestamp < cb->cmd.expire_unreachable) { + switch (cb->unreachable_expire_kind) { + case UE_ALWAYS: + return 1; + case UE_NORMAL: + case UE_HEAD: + if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid)) + return 1; + break; + } + } + + if (cb->cmd.recno && --(cb->cmd.recno) == 0) + return 1; + + return 0; +} + +int should_expire_reflog_ent_verbose(struct object_id *ooid, + struct object_id *noid, + const char *email, + timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; + int expire; + + expire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz, + message, cb); + + if (!expire) + printf("keep %s", message); + else if (cb->dry_run) + printf("would prune %s", message); + else + printf("prune %s", message); + + return expire; +} + +static int push_tip_to_list(const char *refname, const struct object_id *oid, + int flags, void *cb_data) +{ + struct commit_list **list = cb_data; + struct commit *tip_commit; + if (flags & REF_ISSYMREF) + return 0; + tip_commit = lookup_commit_reference_gently(the_repository, oid, 1); + if (!tip_commit) + return 0; + commit_list_insert(tip_commit, list); + return 0; +} + +static int is_head(const char *refname) +{ + switch (ref_type(refname)) { + case REF_TYPE_OTHER_PSEUDOREF: + case REF_TYPE_MAIN_PSEUDOREF: + if (parse_worktree_ref(refname, NULL, NULL, &refname)) + BUG("not a worktree ref: %s", refname); + break; + default: + break; + } + return !strcmp(refname, "HEAD"); +} + +void reflog_expiry_prepare(const char *refname, + const struct object_id *oid, + void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; + struct commit_list *elem; + struct commit *commit = NULL; + + if (!cb->cmd.expire_unreachable || is_head(refname)) { + cb->unreachable_expire_kind = UE_HEAD; + } else { + commit = lookup_commit(the_repository, oid); + if (commit && is_null_oid(&commit->object.oid)) + commit = NULL; + cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS; + } + + if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) + cb->unreachable_expire_kind = UE_ALWAYS; + + switch (cb->unreachable_expire_kind) { + case UE_ALWAYS: + return; + case UE_HEAD: + for_each_ref(push_tip_to_list, &cb->tips); + for (elem = cb->tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb->mark_list); + break; + case UE_NORMAL: + commit_list_insert(commit, &cb->mark_list); + /* For reflog_expiry_cleanup() below */ + cb->tip_commit = commit; + } + cb->mark_limit = cb->cmd.expire_total; + mark_reachable(cb); +} + +void reflog_expiry_cleanup(void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; + struct commit_list *elem; + + switch (cb->unreachable_expire_kind) { + case UE_ALWAYS: + return; + case UE_HEAD: + for (elem = cb->tips; elem; elem = elem->next) + clear_commit_marks(elem->item, REACHABLE); + free_commit_list(cb->tips); + break; + case UE_NORMAL: + clear_commit_marks(cb->tip_commit, REACHABLE); + break; + } +} + +int count_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + struct cmd_reflog_expire_cb *cb = cb_data; + if (!cb->expire_total || timestamp < cb->expire_total) + cb->recno++; + return 0; +} + +int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose) +{ + struct cmd_reflog_expire_cb cmd = { 0 }; + int status = 0; + reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; + const char *spec = strstr(rev, "@{"); + char *ep, *ref; + int recno; + struct expire_reflog_policy_cb cb = { + .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), + }; + + if (verbose) + should_prune_fn = should_expire_reflog_ent_verbose; + + if (!spec) + return error(_("not a reflog: %s"), rev); + + if (!dwim_log(rev, spec - rev, NULL, &ref)) { + status |= error(_("no reflog for '%s'"), rev); + goto cleanup; + } + + recno = strtoul(spec + 2, &ep, 10); + if (*ep == '}') { + cmd.recno = -recno; + for_each_reflog_ent(ref, count_reflog_ent, &cmd); + } else { + cmd.expire_total = approxidate(spec + 2); + for_each_reflog_ent(ref, count_reflog_ent, &cmd); + cmd.expire_total = 0; + } + + cb.cmd = cmd; + status |= reflog_expire(ref, flags, + reflog_expiry_prepare, + should_prune_fn, + reflog_expiry_cleanup, + &cb); + + cleanup: + free(ref); + return status; +} diff --git a/reflog.h b/reflog.h new file mode 100644 index 0000000000..d2906fb9f8 --- /dev/null +++ b/reflog.h @@ -0,0 +1,43 @@ +#ifndef REFLOG_H +#define REFLOG_H +#include "refs.h" + +struct cmd_reflog_expire_cb { + int stalefix; + int explicit_expiry; + timestamp_t expire_total; + timestamp_t expire_unreachable; + int recno; +}; + +struct expire_reflog_policy_cb { + enum { + UE_NORMAL, + UE_ALWAYS, + UE_HEAD + } unreachable_expire_kind; + struct commit_list *mark_list; + unsigned long mark_limit; + struct cmd_reflog_expire_cb cmd; + struct commit *tip_commit; + struct commit_list *tips; + unsigned int dry_run:1; +}; + +int reflog_delete(const char *rev, enum expire_reflog_flags flags, + int verbose); +void reflog_expiry_cleanup(void *cb_data); +void reflog_expiry_prepare(const char *refname, const struct object_id *oid, + void *cb_data); +int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data); +int count_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data); +int should_expire_reflog_ent_verbose(struct object_id *ooid, + struct object_id *noid, + const char *email, + timestamp_t timestamp, int tz, + const char *message, void *cb_data); +#endif /* REFLOG_H */ @@ -1673,6 +1673,23 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, type, failure_errno); } +int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname, + struct strbuf *referent) +{ + struct object_id oid; + int ret, failure_errno = 0; + unsigned int type = 0; + + if (ref_store->be->read_symbolic_ref) + return ref_store->be->read_symbolic_ref(ref_store, refname, referent); + + ret = refs_read_raw_ref(ref_store, refname, &oid, referent, &type, &failure_errno); + if (ret || !(type & REF_ISSYMREF)) + return -1; + + return 0; +} + const char *refs_resolve_ref_unsafe(struct ref_store *refs, const char *refname, int resolve_flags, @@ -82,6 +82,9 @@ int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags); int read_ref(const char *refname, struct object_id *oid); +int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname, + struct strbuf *referent); + /* * Return 0 if a reference named refname could be created without * conflicting with the name of an existing reference. Otherwise, diff --git a/refs/debug.c b/refs/debug.c index 2b0771ca53..c590d37720 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -435,6 +435,7 @@ struct ref_storage_be refs_be_debug = { debug_ref_iterator_begin, debug_read_raw_ref, + NULL, debug_reflog_iterator_begin, debug_for_each_reflog_ent, diff --git a/refs/files-backend.c b/refs/files-backend.c index f59589d6cc..0457ecdb42 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -338,9 +338,9 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs) return refs->loose; } -static int files_read_raw_ref(struct ref_store *ref_store, const char *refname, - struct object_id *oid, struct strbuf *referent, - unsigned int *type, int *failure_errno) +static int read_ref_internal(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno, int skip_packed_refs) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); @@ -381,7 +381,7 @@ stat_ref: if (lstat(path, &st) < 0) { int ignore_errno; myerr = errno; - if (myerr != ENOENT) + if (myerr != ENOENT || skip_packed_refs) goto out; if (refs_read_raw_ref(refs->packed_ref_store, refname, oid, referent, type, &ignore_errno)) { @@ -425,7 +425,8 @@ stat_ref: * ref is supposed to be, there could still be a * packed ref: */ - if (refs_read_raw_ref(refs->packed_ref_store, refname, oid, + if (skip_packed_refs || + refs_read_raw_ref(refs->packed_ref_store, refname, oid, referent, type, &ignore_errno)) { myerr = EISDIR; goto out; @@ -470,6 +471,27 @@ out: return ret; } +static int files_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno) +{ + return read_ref_internal(ref_store, refname, oid, referent, type, failure_errno, 0); +} + +static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refname, + struct strbuf *referent) +{ + struct object_id oid; + int failure_errno, ret; + unsigned int type; + + ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1); + if (ret) + return ret; + + return !(type & REF_ISSYMREF); +} + int parse_loose_ref_contents(const char *buf, struct object_id *oid, struct strbuf *referent, unsigned int *type, int *failure_errno) @@ -3286,6 +3308,7 @@ struct ref_storage_be refs_be_files = { files_ref_iterator_begin, files_read_raw_ref, + files_read_symbolic_ref, files_reflog_iterator_begin, files_for_each_reflog_ent, diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 27dd8c3922..f56e2cc635 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1684,6 +1684,7 @@ struct ref_storage_be refs_be_packed = { packed_ref_iterator_begin, packed_read_raw_ref, + NULL, packed_reflog_iterator_begin, packed_for_each_reflog_ent, diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 6e15db3ca4..001ef15835 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -649,6 +649,21 @@ typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname, struct object_id *oid, struct strbuf *referent, unsigned int *type, int *failure_errno); +/* + * Read a symbolic reference from the specified reference store. This function + * is optional: if not implemented by a backend, then `read_raw_ref_fn` is used + * to read the symbolcic reference instead. It is intended to be implemented + * only in case the backend can optimize the reading of symbolic references. + * + * Return 0 on success, or -1 on failure. `referent` will be set to the target + * of the symbolic reference on success. This function explicitly does not + * distinguish between error cases and the reference not being a symbolic + * reference to allow backends to optimize this operation in case symbolic and + * non-symbolic references are treated differently. + */ +typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refname, + struct strbuf *referent); + struct ref_storage_be { struct ref_storage_be *next; const char *name; @@ -668,6 +683,7 @@ struct ref_storage_be { ref_iterator_begin_fn *iterator_begin; read_raw_ref_fn *read_raw_ref; + read_symbolic_ref_fn *read_symbolic_ref; reflog_iterator_begin_fn *reflog_iterator_begin; for_each_reflog_ent_fn *for_each_reflog_ent; @@ -1945,13 +1945,9 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err) return branch->push_tracking_ref; } -static int ignore_symref_update(const char *refname) +static int ignore_symref_update(const char *refname, struct strbuf *scratch) { - int flag; - - if (!resolve_ref_unsafe(refname, 0, NULL, &flag)) - return 0; /* non-existing refs are OK */ - return (flag & REF_ISSYMREF); + return !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch); } /* @@ -1964,6 +1960,7 @@ static int ignore_symref_update(const char *refname) static struct ref *get_expanded_map(const struct ref *remote_refs, const struct refspec_item *refspec) { + struct strbuf scratch = STRBUF_INIT; const struct ref *ref; struct ref *ret = NULL; struct ref **tail = &ret; @@ -1971,11 +1968,13 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, for (ref = remote_refs; ref; ref = ref->next) { char *expn_name = NULL; + strbuf_reset(&scratch); + if (strchr(ref->name, '^')) continue; /* a dereference item */ if (match_name_with_pattern(refspec->src, ref->name, refspec->dst, &expn_name) && - !ignore_symref_update(expn_name)) { + !ignore_symref_update(expn_name, &scratch)) { struct ref *cpy = copy_ref(ref); cpy->peer_ref = alloc_ref(expn_name); @@ -1987,6 +1986,7 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, free(expn_name); } + strbuf_release(&scratch); return ret; } diff --git a/revision.c b/revision.c index 2bb913f2f3..2646b78990 100644 --- a/revision.c +++ b/revision.c @@ -32,6 +32,7 @@ #include "utf8.h" #include "bloom.h" #include "json-writer.h" +#include "list-objects-filter-options.h" volatile show_early_output_fn_t show_early_output; @@ -2690,6 +2691,10 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, revs->no_walk = 0; } else if (!strcmp(arg, "--single-worktree")) { revs->single_worktree = 1; + } else if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { + parse_list_objects_filter(&revs->filter, arg); + } else if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { + list_objects_filter_set_no_filter(&revs->filter); } else { return 0; } @@ -2892,6 +2897,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s die("cannot combine --walk-reflogs with history-limiting options"); if (revs->rewrite_parents && revs->children.name) die(_("options '%s' and '%s' cannot be used together"), "--parents", "--children"); + if (revs->filter.choice && !revs->blob_objects) + die(_("object filtering requires --objects")); /* * Limitations on the graph functionality diff --git a/revision.h b/revision.h index b9c2421687..5bc59c7bfe 100644 --- a/revision.h +++ b/revision.h @@ -8,6 +8,7 @@ #include "pretty.h" #include "diff.h" #include "commit-slab-decl.h" +#include "list-objects-filter-options.h" /** * The revision walking API offers functions to build a list of revisions @@ -94,6 +95,12 @@ struct rev_info { /* The end-points specified by the end user */ struct rev_cmdline_info cmdline; + /* + * Object filter options. No filtering is specified + * if and only if filter.choice is zero. + */ + struct list_objects_filter_options filter; + /* excluding from --branches, --refs, etc. expansion */ struct string_list *ref_excludes; diff --git a/sequencer.c b/sequencer.c index 35006c0cea..a1bb39383d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1220,7 +1220,7 @@ static int run_prepare_commit_msg_hook(struct repository *r, } else { arg1 = "message"; } - if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name, + if (run_commit_hook(0, r->index_file, NULL, "prepare-commit-msg", name, arg1, arg2, NULL)) ret = error(_("'prepare-commit-msg' hook failed")); @@ -1552,7 +1552,7 @@ static int try_to_commit(struct repository *r, goto out; } - run_commit_hook(0, r->index_file, "post-commit", NULL); + run_commit_hook(0, r->index_file, NULL, "post-commit", NULL); if (flags & AMEND_MSG) commit_post_rewrite(r, current_head, oid); @@ -3749,7 +3749,7 @@ static int do_merge(struct repository *r, int run_commit_flags = 0; struct strbuf ref_name = STRBUF_INIT; struct commit *head_commit, *merge_commit, *i; - struct commit_list *bases, *j, *reversed = NULL; + struct commit_list *bases, *j; struct commit_list *to_merge = NULL, **tail = &to_merge; const char *strategy = !opts->xopts_nr && (!opts->strategy || @@ -3984,9 +3984,7 @@ static int do_merge(struct repository *r, git_path_merge_head(r), 0); write_message("no-ff", 5, git_path_merge_mode(r), 0); - for (j = bases; j; j = j->next) - commit_list_insert(j->item, &reversed); - free_commit_list(bases); + bases = reverse_commit_list(bases); repo_read_index(r); init_merge_options(&o, r); @@ -4002,10 +4000,10 @@ static int do_merge(struct repository *r, * update the index and working copy immediately. */ ret = merge_ort_recursive(&o, - head_commit, merge_commit, reversed, + head_commit, merge_commit, bases, &i); } else { - ret = merge_recursive(&o, head_commit, merge_commit, reversed, + ret = merge_recursive(&o, head_commit, merge_commit, bases, &i); } if (ret <= 0) @@ -875,9 +875,9 @@ static void strbuf_humanise(struct strbuf *buf, off_t bytes, strbuf_addf(buf, humanise_rate == 0 ? /* TRANSLATORS: IEC 80000-13:2008 byte */ - Q_("%u byte", "%u bytes", (unsigned)bytes) : + Q_("%u byte", "%u bytes", bytes) : /* TRANSLATORS: IEC 80000-13:2008 byte/second */ - Q_("%u byte/s", "%u bytes/s", (unsigned)bytes), + Q_("%u byte/s", "%u bytes/s", bytes), (unsigned)bytes); } } diff --git a/string-list.h b/string-list.h index 267d6e5769..d5a744e143 100644 --- a/string-list.h +++ b/string-list.h @@ -86,7 +86,8 @@ typedef int (*compare_strings_fn)(const char *, const char *); */ struct string_list { struct string_list_item *items; - unsigned int nr, alloc; + size_t nr; + size_t alloc; unsigned int strdup_strings:1; compare_strings_fn cmp; /* NULL uses strcmp() */ }; diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c index 75927b2c81..98b73bb8f2 100644 --- a/t/helper/test-read-graph.c +++ b/t/helper/test-read-graph.c @@ -3,6 +3,7 @@ #include "commit-graph.h" #include "repository.h" #include "object-store.h" +#include "bloom.h" int cmd__read_graph(int argc, const char **argv) { @@ -45,6 +46,18 @@ int cmd__read_graph(int argc, const char **argv) printf(" bloom_data"); printf("\n"); + printf("options:"); + if (graph->bloom_filter_settings) + printf(" bloom(%"PRIu32",%"PRIu32",%"PRIu32")", + graph->bloom_filter_settings->hash_version, + graph->bloom_filter_settings->bits_per_entry, + graph->bloom_filter_settings->num_hashes); + if (graph->read_generation_data) + printf(" read_generation_data"); + if (graph->topo_levels) + printf(" topo_levels"); + printf("\n"); + UNLEAK(graph); return 0; diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index 8f370cd89f..f3b90aa834 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -19,7 +19,6 @@ #include "thread-utils.h" #include "wildmatch.h" #include "gettext.h" -#include "parse-options.h" static int number_callbacks; static int parallel_next(struct child_process *cp, @@ -180,15 +179,16 @@ static int testsuite(int argc, const char **argv) if (max_jobs > suite.tests.nr) max_jobs = suite.tests.nr; - fprintf(stderr, "Running %d tests (%d at a time)\n", - suite.tests.nr, max_jobs); + fprintf(stderr, "Running %"PRIuMAX" tests (%d at a time)\n", + (uintmax_t)suite.tests.nr, max_jobs); ret = run_processes_parallel(max_jobs, next_test, test_failed, test_finished, &suite); if (suite.failed.nr > 0) { ret = 1; - fprintf(stderr, "%d tests failed:\n\n", suite.failed.nr); + fprintf(stderr, "%"PRIuMAX" tests failed:\n\n", + (uintmax_t)suite.failed.nr); for (i = 0; i < suite.failed.nr; i++) fprintf(stderr, "\t%s\n", suite.failed.items[i].string); } diff --git a/t/lib-commit-graph.sh b/t/lib-commit-graph.sh new file mode 100755 index 0000000000..5d79e1a4e9 --- /dev/null +++ b/t/lib-commit-graph.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +# Helper functions for testing commit-graphs. + +# Initialize OID cache with oid_version +test_oid_cache <<-EOF +oid_version sha1:1 +oid_version sha256:2 +EOF + +graph_git_two_modes() { + git -c core.commitGraph=true $1 >output && + git -c core.commitGraph=false $1 >expect && + test_cmp expect output +} + +graph_git_behavior() { + MSG=$1 + DIR=$2 + BRANCH=$3 + COMPARE=$4 + test_expect_success "check normal git operations: $MSG" ' + cd "$TRASH_DIRECTORY/$DIR" && + graph_git_two_modes "log --oneline $BRANCH" && + graph_git_two_modes "log --topo-order $BRANCH" && + graph_git_two_modes "log --graph $COMPARE..$BRANCH" && + graph_git_two_modes "branch -vv" && + graph_git_two_modes "merge-base -a $BRANCH $COMPARE" + ' +} + +graph_read_expect() { + OPTIONAL="" + NUM_CHUNKS=3 + if test -n "$2" + then + OPTIONAL=" $2" + NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) + fi + GENERATION_VERSION=2 + if test -n "$3" + then + GENERATION_VERSION=$3 + fi + OPTIONS= + if test $GENERATION_VERSION -gt 1 + then + OPTIONS=" read_generation_data" + fi + cat >expect <<- EOF + header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 + num_commits: $1 + chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL + options:$OPTIONS + EOF + test-tool read-graph >output && + test_cmp expect output +} diff --git a/t/perf/p1006-cat-file.sh b/t/perf/p1006-cat-file.sh new file mode 100755 index 0000000000..dcd8015379 --- /dev/null +++ b/t/perf/p1006-cat-file.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +test_description='Tests listing object info performance' +. ./perf-lib.sh + +test_perf_large_repo + +test_perf 'cat-file --batch-check' ' + git cat-file --batch-all-objects --batch-check +' + +test_done diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index 2a7106b949..382716cfca 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -117,6 +117,7 @@ test_perf_on_all git diff test_perf_on_all git diff --cached test_perf_on_all git blame $SPARSE_CONE/a test_perf_on_all git blame $SPARSE_CONE/f3/a +test_perf_on_all git read-tree -mu HEAD test_perf_on_all git checkout-index -f --all test_perf_on_all git update-index --add --remove $SPARSE_CONE/a diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index b007f0efef..9dcbf518a7 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -1089,7 +1089,8 @@ test_expect_success 'update-index D/F conflict' ' mv path2 path0 && mv tmp path2 && git update-index --add --replace path2 path0/file2 && - numpath0=$(git ls-files path0 | wc -l) && + git ls-files path0 >tmp && + numpath0=$(wc -l <tmp) && test $numpath0 = 1 ' @@ -1103,13 +1104,14 @@ test_expect_success 'very long name in the index handled sanely' ' >path4 && git update-index --add path4 && + git ls-files -s path4 >tmp && ( - git ls-files -s path4 | - sed -e "s/ .*/ /" | + sed -e "s/ .*/ /" tmp | tr -d "\012" && echo "$a" ) | git update-index --index-info && - len=$(git ls-files "a*" | wc -c) && + git ls-files "a*" >tmp && + len=$(wc -c <tmp) && test $len = 4098 ' diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 76052cb562..f6356db183 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -65,9 +65,11 @@ test_expect_success 'check commit-tree' ' test_path_is_file "$REAL/objects/$(objpath $SHA)" ' -test_expect_success 'check rev-list' ' +test_expect_success !SANITIZE_LEAK 'check rev-list' ' git update-ref "HEAD" "$SHA" && - test "$SHA" = "$(git rev-list HEAD)" + git rev-list HEAD >actual && + echo $SHA >expected && + test_cmp expected actual ' test_expect_success 'setup_git_dir twice in subdir' ' diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index b9ed612af1..143f100517 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -205,15 +205,18 @@ test_expect_success 'attribute test: read paths from stdin' ' test_expect_success 'attribute test: --all option' ' grep -v unspecified <expect-all | sort >specified-all && sed -e "s/:.*//" <expect-all | uniq >stdin-all && - git check-attr --stdin --all <stdin-all | sort >actual && + git check-attr --stdin --all <stdin-all >tmp && + sort tmp >actual && test_cmp specified-all actual ' test_expect_success 'attribute test: --cached option' ' - git check-attr --cached --stdin --all <stdin-all | sort >actual && + git check-attr --cached --stdin --all <stdin-all >tmp && + sort tmp >actual && test_must_be_empty actual && git add .gitattributes a/.gitattributes a/b/.gitattributes && - git check-attr --cached --stdin --all <stdin-all | sort >actual && + git check-attr --cached --stdin --all <stdin-all >tmp && + sort tmp >actual && test_cmp specified-all actual ' diff --git a/t/t0022-crlf-rename.sh b/t/t0022-crlf-rename.sh index c1a331e9e9..9fe9891251 100755 --- a/t/t0022-crlf-rename.sh +++ b/t/t0022-crlf-rename.sh @@ -24,8 +24,8 @@ test_expect_success setup ' test_expect_success 'diff -M' ' - git diff-tree -M -r --name-status HEAD^ HEAD | - sed -e "s/R[0-9]*/RNUM/" >actual && + git diff-tree -M -r --name-status HEAD^ HEAD >tmp && + sed -e "s/R[0-9]*/RNUM/" tmp >actual && echo "RNUM sample elpmas" >expect && test_cmp expect actual diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh index 81447978b7..f7202c192e 100755 --- a/t/t0025-crlf-renormalize.sh +++ b/t/t0025-crlf-renormalize.sh @@ -22,8 +22,8 @@ test_expect_success 'renormalize CRLF in repo' ' i/lf w/lf attr/text=auto LF.txt i/lf w/mixed attr/text=auto CRLF_mix_LF.txt EOF - git ls-files --eol | - sed -e "s/ / /g" -e "s/ */ /g" | + git ls-files --eol >tmp && + sed -e "s/ / /g" -e "s/ */ /g" tmp | sort >actual && test_cmp expect actual ' diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index c5f7ac63b0..0feb41a23f 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -311,8 +311,8 @@ checkout_files () { i/-text w/$(stats_ascii $crlfnul) attr/$(attr_ascii $attr $aeol) crlf_false_attr__CRLF_nul.txt i/-text w/$(stats_ascii $crlfnul) attr/$(attr_ascii $attr $aeol) crlf_false_attr__LF_nul.txt EOF - git ls-files --eol crlf_false_attr__* | - sed -e "s/ / /g" -e "s/ */ /g" | + git ls-files --eol crlf_false_attr__* >tmp && + sed -e "s/ / /g" -e "s/ */ /g" tmp | sort >actual && test_cmp expect actual ' @@ -359,12 +359,12 @@ test_expect_success 'ls-files --eol -o Text/Binary' ' i/ w/crlf TeBi_126_CL i/ w/-text TeBi_126_CLC EOF - git ls-files --eol -o | + git ls-files --eol -o >tmp && sed -n -e "/TeBi_/{s!attr/[ ]*!!g s! ! !g s! *! !g p - }" | sort >actual && + }" tmp | sort >actual && test_cmp expect actual ' @@ -617,8 +617,8 @@ test_expect_success 'ls-files --eol -d -z' ' i/lf w/ crlf_false_attr__LF.txt i/mixed w/ crlf_false_attr__CRLF_mix_LF.txt EOF - git ls-files --eol -d | - sed -e "s!attr/[^ ]*!!g" -e "s/ / /g" -e "s/ */ /g" | + git ls-files --eol -d >tmp && + sed -e "s!attr/[^ ]*!!g" -e "s/ / /g" -e "s/ */ /g" tmp | sort >actual && test_cmp expect actual ' diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index ae1ca380c1..0a5713c524 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -13,6 +13,10 @@ s40=' ' sss="$s40$s40$s40$s40$s40$s40$s40$s40$s40$s40" # 400 ttt="$t40$t40$t40$t40$t40$t40$t40$t40$t40$t40" # 400 +printf_git_stripspace () { + printf "$1" | git stripspace +} + test_expect_success \ 'long lines without spaces should be unchanged' ' echo "$ttt" >expect && @@ -225,32 +229,38 @@ test_expect_success \ test_expect_success \ 'text without newline at end should end with newline' ' - test $(printf "$ttt" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0 + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt$ttt" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt$ttt$ttt" ' # text plus spaces at the end: test_expect_success \ 'text plus spaces without newline at end should end with newline' ' - test $(printf "$ttt$sss" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$sss$sss" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$ttt$sss$sss" | git stripspace | wc -l) -gt 0 && - test $(printf "$ttt$sss$sss$sss" | git stripspace | wc -l) -gt 0 + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$sss" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt$sss" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt$ttt$sss" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$sss$sss" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$ttt$sss$sss" && + test_stdout_line_count -gt 0 printf_git_stripspace "$ttt$sss$sss$sss" ' test_expect_success \ 'text plus spaces without newline at end should not show spaces' ' - ! (printf "$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$ttt$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$ttt$ttt$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$ttt$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$ttt$ttt$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$ttt$sss$sss$sss" | git stripspace | grep " " >/dev/null) + printf "$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$ttt$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$ttt$ttt$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$ttt$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$ttt$ttt$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$ttt$sss$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null ' test_expect_success \ @@ -282,12 +292,18 @@ test_expect_success \ test_expect_success \ 'text plus spaces at end should not show spaces' ' - ! (echo "$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (echo "$ttt$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (echo "$ttt$ttt$ttt$sss" | git stripspace | grep " " >/dev/null) && - ! (echo "$ttt$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (echo "$ttt$ttt$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (echo "$ttt$sss$sss$sss" | git stripspace | grep " " >/dev/null) + echo "$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + echo "$ttt$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + echo "$ttt$ttt$ttt$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + echo "$ttt$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + echo "$ttt$ttt$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + echo "$ttt$sss$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null ' test_expect_success \ @@ -339,11 +355,16 @@ test_expect_success \ test_expect_success \ 'spaces without newline at end should not show spaces' ' - ! (printf "" | git stripspace | grep " " >/dev/null) && - ! (printf "$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$sss$sss$sss" | git stripspace | grep " " >/dev/null) && - ! (printf "$sss$sss$sss$sss" | git stripspace | grep " " >/dev/null) + printf "" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$sss$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null && + printf "$sss$sss$sss$sss" | git stripspace >tmp && + ! grep " " tmp >/dev/null ' test_expect_success \ diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh index afc343cf9b..5c9dc90d0b 100755 --- a/t/t0050-filesystem.sh +++ b/t/t0050-filesystem.sh @@ -104,7 +104,8 @@ test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' ' rm camelcase && echo 1 >CamelCase && git add CamelCase && - camel=$(git ls-files | grep -i camelcase) && + git ls-files >tmp && + camel=$(grep -i camelcase tmp) && test $(echo "$camel" | wc -l) = 1 && test "z$(git cat-file blob :$camel)" = z1 ' diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index f17abd298c..1e864cf317 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -618,6 +618,25 @@ test_expect_success 'do not fetch when checking existence of tree we construct o git -C repo cherry-pick side1 ' +test_expect_success 'exact rename does not need to fetch the blob lazily' ' + rm -rf repo partial.git && + test_create_repo repo && + content="some dummy content" && + test_commit -C repo create-a-file file.txt "$content" && + git -C repo mv file.txt new-file.txt && + git -C repo commit -m rename-the-file && + FILE_HASH=$(git -C repo rev-parse HEAD:new-file.txt) && + test_config -C repo uploadpack.allowfilter 1 && + test_config -C repo uploadpack.allowanysha1inwant 1 && + + git clone --filter=blob:none --bare "file://$(pwd)/repo" partial.git && + git -C partial.git rev-list --objects --missing=print HEAD >out && + grep "[?]$FILE_HASH" out && + git -C partial.git log --follow -- new-file.txt && + git -C partial.git rev-list --objects --missing=print HEAD >out && + grep "[?]$FILE_HASH" out +' + test_expect_success 'lazy-fetch when accessing object not in the_repository' ' rm -rf full partial.git && test_create_repo full && diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index d1115528cb..0710b1fb1e 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -21,7 +21,6 @@ In the test, these paths are used: yomin - not in H or M ' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-read-tree.sh @@ -38,11 +37,12 @@ compare_change () { } check_cache_at () { - clean_if_empty=$(git diff-files -- "$1") + git diff-files -- "$1" >out && + clean_if_empty=$(cat out) && case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; - esac + esac && case "$2,$clean_if_empty" in clean,) : ;; clean,?*) false ;; diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index ca5c5510c7..46cbd5514a 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -9,7 +9,6 @@ This is identical to t1001, but uses -u to update the work tree as well. ' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-read-tree.sh @@ -23,11 +22,12 @@ compare_change () { } check_cache_at () { - clean_if_empty=$(git diff-files -- "$1") + git diff-files -- "$1" >out && + clean_if_empty=$(cat out) && case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; - esac + esac && case "$2,$clean_if_empty" in clean,) : ;; clean,?*) false ;; diff --git a/t/t1003-read-tree-prefix.sh b/t/t1003-read-tree-prefix.sh index e0db2066f3..c860c08ecb 100755 --- a/t/t1003-read-tree-prefix.sh +++ b/t/t1003-read-tree-prefix.sh @@ -25,4 +25,14 @@ test_expect_success 'read-tree --prefix' ' cmp expect actual ' +test_expect_success 'read-tree --prefix with leading slash exits with error' ' + git rm -rf . && + test_must_fail git read-tree --prefix=/two/ $tree && + git read-tree --prefix=two/ $tree && + + git rm -rf . && + test_must_fail git read-tree --prefix=/ $tree && + git read-tree --prefix= $tree +' + test_done diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 2a04b532f9..dcc0a30d4a 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -244,6 +244,24 @@ test_expect_success 'expanded in-memory index matches full index' ' test_sparse_match git ls-files --stage ' +test_expect_success 'root directory cannot be sparse' ' + init_repos && + + # Remove all in-cone files and directories from the index, collapse index + # with `git sparse-checkout reapply` + git -C sparse-index rm -r . && + git -C sparse-index sparse-checkout reapply && + + # Verify sparse directories still present, root directory is not sparse + cat >expect <<-EOF && + folder1/ + folder2/ + x/ + EOF + git -C sparse-index ls-files --sparse >actual && + test_cmp expect actual +' + test_expect_success 'status with options' ' init_repos && test_sparse_match ls && @@ -260,6 +278,13 @@ test_expect_success 'status with options' ' test_all_match git status --porcelain=v2 -uno ' +test_expect_success 'status with diff in unexpanded sparse directory' ' + init_repos && + test_all_match git checkout rename-base && + test_all_match git reset --soft rename-out-to-out && + test_all_match git status --porcelain=v2 +' + test_expect_success 'status reports sparse-checkout' ' init_repos && git -C sparse-checkout status >full && @@ -794,6 +819,93 @@ test_expect_success 'update-index --cacheinfo' ' test_cmp expect sparse-checkout-out ' +for MERGE_TREES in "base HEAD update-folder2" \ + "update-folder1 update-folder2" \ + "update-folder2" +do + test_expect_success "'read-tree -mu $MERGE_TREES' with files outside sparse definition" ' + init_repos && + + # Although the index matches, without --no-sparse-checkout, outside-of- + # definition files will not exist on disk for sparse checkouts + test_all_match git read-tree -mu $MERGE_TREES && + test_all_match git status --porcelain=v2 && + test_path_is_missing sparse-checkout/folder2 && + test_path_is_missing sparse-index/folder2 && + + test_all_match git read-tree --reset -u HEAD && + test_all_match git status --porcelain=v2 && + + test_all_match git read-tree -mu --no-sparse-checkout $MERGE_TREES && + test_all_match git status --porcelain=v2 && + test_cmp sparse-checkout/folder2/a sparse-index/folder2/a && + test_cmp sparse-checkout/folder2/a full-checkout/folder2/a + + ' +done + +test_expect_success 'read-tree --merge with edit/edit conflicts in sparse directories' ' + init_repos && + + # Merge of multiple changes to same directory (but not same files) should + # succeed + test_all_match git read-tree -mu base rename-base update-folder1 && + test_all_match git status --porcelain=v2 && + + test_all_match git reset --hard && + + test_all_match git read-tree -mu rename-base update-folder2 && + test_all_match git status --porcelain=v2 && + + test_all_match git reset --hard && + + test_all_match test_must_fail git read-tree -mu base update-folder1 rename-out-to-in && + test_all_match test_must_fail git read-tree -mu rename-out-to-in update-folder1 +' + +test_expect_success 'read-tree --prefix' ' + init_repos && + + # If files differing between the index and target <commit-ish> exist + # inside the prefix, `read-tree --prefix` should fail + test_all_match test_must_fail git read-tree --prefix=deep/ deepest && + test_all_match test_must_fail git read-tree --prefix=folder1/ update-folder1 && + + # If no differing index entries exist matching the prefix, + # `read-tree --prefix` updates the index successfully + test_all_match git rm -rf deep/deeper1/deepest/ && + test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest && + test_all_match git status --porcelain=v2 && + + test_all_match git rm -rf --sparse folder1/ && + test_all_match git read-tree --prefix=folder1/ -u update-folder1 && + test_all_match git status --porcelain=v2 && + + test_all_match git rm -rf --sparse folder2/0 && + test_all_match git read-tree --prefix=folder2/0/ -u rename-out-to-out && + test_all_match git status --porcelain=v2 +' + +test_expect_success 'read-tree --merge with directory-file conflicts' ' + init_repos && + + test_all_match git checkout -b test-branch rename-base && + + # Although the index matches, without --no-sparse-checkout, outside-of- + # definition files will not exist on disk for sparse checkouts + test_sparse_match git read-tree -mu rename-out-to-out && + test_sparse_match git status --porcelain=v2 && + test_path_is_missing sparse-checkout/folder2 && + test_path_is_missing sparse-index/folder2 && + + test_sparse_match git read-tree --reset -u HEAD && + test_sparse_match git status --porcelain=v2 && + + test_sparse_match git read-tree -mu --no-sparse-checkout rename-out-to-out && + test_sparse_match git status --porcelain=v2 && + test_cmp sparse-checkout/folder2/0/1 sparse-index/folder2/0/1 +' + test_expect_success 'merge, cherry-pick, and rebase' ' init_repos && @@ -1297,6 +1409,27 @@ test_expect_success 'sparse index is not expanded: fetch/pull' ' ensure_not_expanded pull full base ' +test_expect_success 'sparse index is not expanded: read-tree' ' + init_repos && + + ensure_not_expanded checkout -b test-branch update-folder1 && + for MERGE_TREES in "base HEAD update-folder2" \ + "base HEAD rename-base" \ + "base update-folder2" \ + "base rename-base" \ + "update-folder2" + do + ensure_not_expanded read-tree -mu $MERGE_TREES && + ensure_not_expanded reset --hard || return 1 + done && + + rm -rf sparse-index/deep/deeper2 && + ensure_not_expanded add . && + ensure_not_expanded commit -m "test" && + + ensure_not_expanded read-tree --prefix=deep/deeper2 -u deepest +' + test_expect_success 'ls-files' ' init_repos && diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 68f69bb543..ea8e6ac2a0 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -423,4 +423,13 @@ test_expect_success 'expire with multiple worktrees' ' ) ' +test_expect_success REFFILES 'empty reflog' ' + test_when_finished "rm -rf empty" && + git init empty && + test_commit -C empty A && + >empty/.git/logs/refs/heads/foo && + git -C empty reflog expire --all 2>err && + test_must_be_empty err +' + test_done diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index 94fe413ee3..ba43168d12 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -132,8 +132,9 @@ test_expect_success 'use --default' ' test_must_fail git rev-parse --verify --default bar ' -test_expect_success 'main@{n} for various n' ' - N=$(git reflog | wc -l) && +test_expect_success !SANITIZE_LEAK 'main@{n} for various n' ' + git reflog >out && + N=$(wc -l <out) && Nm1=$(($N-1)) && Np1=$(($N+1)) && git rev-parse --verify main@{0} && diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh index 42601d5a31..1f6c4ed042 100755 --- a/t/t2012-checkout-last.sh +++ b/t/t2012-checkout-last.sh @@ -21,14 +21,20 @@ test_expect_success 'first branch switch' ' git checkout other ' +test_cmp_symbolic_HEAD_ref () { + echo refs/heads/"$1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +} + test_expect_success '"checkout -" switches back' ' git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/main" + test_cmp_symbolic_HEAD_ref main ' test_expect_success '"checkout -" switches forth' ' git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" + test_cmp_symbolic_HEAD_ref other ' test_expect_success 'detach HEAD' ' @@ -37,12 +43,16 @@ test_expect_success 'detach HEAD' ' test_expect_success '"checkout -" attaches again' ' git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" + test_cmp_symbolic_HEAD_ref other ' test_expect_success '"checkout -" detaches again' ' git checkout - && - test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" && + + git rev-parse other >expect && + git rev-parse HEAD >actual && + test_cmp expect actual && + test_must_fail git symbolic-ref HEAD ' @@ -63,31 +73,31 @@ more_switches () { test_expect_success 'switch to the last' ' more_switches && git checkout @{-1} && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2" + test_cmp_symbolic_HEAD_ref branch2 ' test_expect_success 'switch to second from the last' ' more_switches && git checkout @{-2} && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3" + test_cmp_symbolic_HEAD_ref branch3 ' test_expect_success 'switch to third from the last' ' more_switches && git checkout @{-3} && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4" + test_cmp_symbolic_HEAD_ref branch4 ' test_expect_success 'switch to fourth from the last' ' more_switches && git checkout @{-4} && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5" + test_cmp_symbolic_HEAD_ref branch5 ' test_expect_success 'switch to twelfth from the last' ' more_switches && git checkout @{-12} && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" + test_cmp_symbolic_HEAD_ref branch13 ' test_expect_success 'merge base test setup' ' @@ -98,19 +108,28 @@ test_expect_success 'merge base test setup' ' test_expect_success 'another...main' ' git checkout another && git checkout another...main && - test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify main^)" + + git rev-parse --verify main^ >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual ' test_expect_success '...main' ' git checkout another && git checkout ...main && - test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify main^)" + + git rev-parse --verify main^ >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual ' test_expect_success 'main...' ' git checkout another && git checkout main... && - test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify main^)" + + git rev-parse --verify main^ >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual ' test_expect_success '"checkout -" works after a rebase A' ' @@ -118,7 +137,7 @@ test_expect_success '"checkout -" works after a rebase A' ' git checkout other && git rebase main && git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/main" + test_cmp_symbolic_HEAD_ref main ' test_expect_success '"checkout -" works after a rebase A B' ' @@ -127,7 +146,7 @@ test_expect_success '"checkout -" works after a rebase A B' ' git checkout other && git rebase main moodle && git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/main" + test_cmp_symbolic_HEAD_ref main ' test_expect_success '"checkout -" works after a rebase -i A' ' @@ -135,7 +154,7 @@ test_expect_success '"checkout -" works after a rebase -i A' ' git checkout other && git rebase -i main && git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/main" + test_cmp_symbolic_HEAD_ref main ' test_expect_success '"checkout -" works after a rebase -i A B' ' @@ -144,7 +163,7 @@ test_expect_success '"checkout -" works after a rebase -i A B' ' git checkout other && git rebase main foodle && git checkout - && - test "z$(git symbolic-ref HEAD)" = "zrefs/heads/main" + test_cmp_symbolic_HEAD_ref main ' test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index acd3650d3c..0c38f8e356 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -14,7 +14,6 @@ only the updates to dir/sub. Also tested are "git add -u" without limiting, and "git add -u" without contents changes, and other conditions' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' @@ -41,20 +40,28 @@ test_expect_success update ' ' test_expect_success 'update noticed a removal' ' - test "$(git ls-files dir1/sub1)" = "" + git ls-files dir1/sub1 >out && + test_must_be_empty out ' test_expect_success 'update touched correct path' ' - test "$(git diff-files --name-status dir2/sub3)" = "" + git diff-files --name-status dir2/sub3 >out && + test_must_be_empty out ' test_expect_success 'update did not touch other tracked files' ' - test "$(git diff-files --name-status check)" = "M check" && - test "$(git diff-files --name-status top)" = "M top" + echo "M check" >expect && + git diff-files --name-status check >actual && + test_cmp expect actual && + + echo "M top" >expect && + git diff-files --name-status top >actual && + test_cmp expect actual ' test_expect_success 'update did not touch untracked files' ' - test "$(git ls-files dir2/other)" = "" + git ls-files dir2/other >out && + test_must_be_empty out ' test_expect_success 'cache tree has not been corrupted' ' @@ -76,9 +83,8 @@ test_expect_success 'update from a subdirectory' ' ' test_expect_success 'change gets noticed' ' - - test "$(git diff-files --name-status dir1)" = "" - + git diff-files --name-status dir1 >out && + test_must_be_empty out ' test_expect_success 'non-qualified update in subdir updates from the root' ' @@ -103,7 +109,8 @@ test_expect_success 'replace a file with a symlink' ' test_expect_success 'add everything changed' ' git add -u && - test -z "$(git diff-files)" + git diff-files >out && + test_must_be_empty out ' @@ -111,7 +118,8 @@ test_expect_success 'touch and then add -u' ' touch check && git add -u && - test -z "$(git diff-files)" + git diff-files >out && + test_must_be_empty out ' @@ -119,7 +127,8 @@ test_expect_success 'touch and then add explicitly' ' touch check && git add check && - test -z "$(git diff-files)" + git diff-files >out && + test_must_be_empty out ' diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index bc9d8ee1e6..bb5fea02a0 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -8,7 +8,6 @@ test_description='Test commit notes index (expensive!)' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh create_repo () { @@ -65,7 +64,8 @@ create_repo () { test_notes () { count=$1 && git config core.notesRef refs/notes/commits && - git log | grep "^ " >output && + git log >tmp && + grep "^ " tmp >output && i=$count && while test $i -gt 0 do @@ -90,7 +90,7 @@ write_script time_notes <<\EOF unset GIT_NOTES_REF ;; esac - git log + git log || exit $? i=$(($i+1)) done >/dev/null EOF diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index 7e0a8960af..eac193757b 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -5,7 +5,6 @@ test_description='Test commit notes organized in subtrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh number_of_commits=100 @@ -79,7 +78,7 @@ test_sha1_based () { ( start_note_commit && nr=$number_of_commits && - git rev-list refs/heads/main | + git rev-list refs/heads/main >out && while read sha1; do note_path=$(echo "$sha1" | sed "$1") cat <<INPUT_END && @@ -91,9 +90,9 @@ EOF INPUT_END nr=$(($nr-1)) - done - ) | - git fast-import --quiet + done <out + ) >gfi && + git fast-import --quiet <gfi } test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"' diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh index 1f5964865a..9976d787f4 100755 --- a/t/t3305-notes-fanout.sh +++ b/t/t3305-notes-fanout.sh @@ -2,7 +2,6 @@ test_description='Test that adding/removing many notes triggers automatic fanout restructuring' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh path_has_fanout() { @@ -24,7 +23,7 @@ touched_one_note_with_fanout() { all_notes_have_fanout() { notes_commit=$1 && fanout=$2 && - git ls-tree -r --name-only $notes_commit 2>/dev/null | + git ls-tree -r --name-only $notes_commit | while read path do path_has_fanout $path $fanout || return 1 @@ -51,8 +50,9 @@ test_expect_success 'creating many notes with git-notes' ' done ' -test_expect_success 'many notes created correctly with git-notes' ' - git log | grep "^ " > output && +test_expect_success !SANITIZE_LEAK 'many notes created correctly with git-notes' ' + git log >output.raw && + grep "^ " output.raw >output && i=$num_notes && while test $i -gt 0 do @@ -91,13 +91,13 @@ test_expect_success 'stable fanout 0 is followed by stable fanout 1' ' test_expect_success 'deleting most notes with git-notes' ' remove_notes=285 && i=0 && - git rev-list HEAD | + git rev-list HEAD >revs && while test $i -lt $remove_notes && read sha1 do i=$(($i + 1)) && test_tick && - git notes remove "$sha1" 2>/dev/null || return 1 - done + git notes remove "$sha1" || return 1 + done <revs ' test_expect_success 'most notes deleted correctly with git-notes' ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index f36e121210..42638b11d8 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -41,7 +41,7 @@ diff_cmp () { rm -f "$1.compare" "$2.compare" } -test_expect_success 'stash some dirty working directory' ' +setup_stash() { echo 1 >file && git add file && echo unrelated >other-file && @@ -55,6 +55,10 @@ test_expect_success 'stash some dirty working directory' ' git stash && git diff-files --quiet && git diff-index --cached --quiet HEAD +} + +test_expect_success 'stash some dirty working directory' ' + setup_stash ' cat >expect <<EOF @@ -185,6 +189,43 @@ test_expect_success 'drop middle stash by index' ' test 1 = $(git show HEAD:file) ' +test_expect_success 'drop stash reflog updates refs/stash' ' + git reset --hard && + git rev-parse refs/stash >expect && + echo 9 >file && + git stash && + git stash drop stash@{0} && + git rev-parse refs/stash >actual && + test_cmp expect actual +' + +test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' ' + git init repo && + ( + cd repo && + setup_stash + ) && + echo 9 >repo/file && + + old_oid="$(git -C repo rev-parse stash@{0})" && + git -C repo stash && + new_oid="$(git -C repo rev-parse stash@{0})" && + + cat >expect <<-EOF && + $(test_oid zero) $old_oid + $old_oid $new_oid + EOF + cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + test_cmp expect actual && + + git -C repo stash drop stash@{1} && + cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + cat >expect <<-EOF && + $(test_oid zero) $new_oid + EOF + test_cmp expect actual +' + test_expect_success 'stash pop' ' git reset --hard && git stash pop && diff --git a/t/t4018/kotlin-class b/t/t4018/kotlin-class new file mode 100644 index 0000000000..bb864f22e6 --- /dev/null +++ b/t/t4018/kotlin-class @@ -0,0 +1,5 @@ +class RIGHT { + //comment + //comment + return ChangeMe +} diff --git a/t/t4018/kotlin-enum-class b/t/t4018/kotlin-enum-class new file mode 100644 index 0000000000..8885f908fd --- /dev/null +++ b/t/t4018/kotlin-enum-class @@ -0,0 +1,5 @@ +enum class RIGHT{ + // Left + // a comment + ChangeMe +} diff --git a/t/t4018/kotlin-fun b/t/t4018/kotlin-fun new file mode 100644 index 0000000000..2a60280256 --- /dev/null +++ b/t/t4018/kotlin-fun @@ -0,0 +1,5 @@ +fun RIGHT(){ + //a comment + //b comment + return ChangeMe() +} diff --git a/t/t4018/kotlin-inheritace-class b/t/t4018/kotlin-inheritace-class new file mode 100644 index 0000000000..77376c1f05 --- /dev/null +++ b/t/t4018/kotlin-inheritace-class @@ -0,0 +1,5 @@ +open class RIGHT{ + // a comment + // b comment + // ChangeMe +} diff --git a/t/t4018/kotlin-inline-class b/t/t4018/kotlin-inline-class new file mode 100644 index 0000000000..7bf46dd8d4 --- /dev/null +++ b/t/t4018/kotlin-inline-class @@ -0,0 +1,5 @@ +value class RIGHT(Args){ + // a comment + // b comment + ChangeMe +} diff --git a/t/t4018/kotlin-interface b/t/t4018/kotlin-interface new file mode 100644 index 0000000000..f686ba7770 --- /dev/null +++ b/t/t4018/kotlin-interface @@ -0,0 +1,5 @@ +interface RIGHT{ + //another comment + //another comment + //ChangeMe +} diff --git a/t/t4018/kotlin-nested-fun b/t/t4018/kotlin-nested-fun new file mode 100644 index 0000000000..12186858cb --- /dev/null +++ b/t/t4018/kotlin-nested-fun @@ -0,0 +1,9 @@ +class LEFT{ + class CENTER{ + fun RIGHT( a:Int){ + //comment + //comment + ChangeMe + } + } +} diff --git a/t/t4018/kotlin-public-class b/t/t4018/kotlin-public-class new file mode 100644 index 0000000000..9433fcc226 --- /dev/null +++ b/t/t4018/kotlin-public-class @@ -0,0 +1,5 @@ +public class RIGHT{ + //comment1 + //comment2 + ChangeMe +} diff --git a/t/t4018/kotlin-sealed-class b/t/t4018/kotlin-sealed-class new file mode 100644 index 0000000000..0efa4a4eaf --- /dev/null +++ b/t/t4018/kotlin-sealed-class @@ -0,0 +1,5 @@ +sealed class RIGHT { + // a comment + // b comment + ChangeMe +} diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 54bb8ef27e..1219f8bd4c 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -24,45 +24,38 @@ test_expect_success setup ' ' test_expect_success 'GIT_EXTERNAL_DIFF environment' ' - - GIT_EXTERNAL_DIFF=echo git diff | { - read path oldfile oldhex oldmode newfile newhex newmode && - test "z$path" = zfile && - test "z$oldmode" = z100644 && - test "z$newhex" = "z$ZERO_OID" && - test "z$newmode" = z100644 && - oh=$(git rev-parse --verify HEAD:file) && - test "z$oh" = "z$oldhex" - } + cat >expect <<-EOF && + file $(git rev-parse --verify HEAD:file) 100644 file $(test_oid zero) 100644 + EOF + GIT_EXTERNAL_DIFF=echo git diff >out && + cut -d" " -f1,3- <out >actual && + test_cmp expect actual ' -test_expect_success 'GIT_EXTERNAL_DIFF environment should apply only to diff' ' - - GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD | - grep "^diff --git a/file b/file" +test_expect_success !SANITIZE_LEAK 'GIT_EXTERNAL_DIFF environment should apply only to diff' ' + GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'GIT_EXTERNAL_DIFF environment and --no-ext-diff' ' - - GIT_EXTERNAL_DIFF=echo git diff --no-ext-diff | - grep "^diff --git a/file b/file" + GIT_EXTERNAL_DIFF=echo git diff --no-ext-diff >out && + grep "^diff --git a/file b/file" out ' test_expect_success SYMLINKS 'typechange diff' ' rm -f file && ln -s elif file && - GIT_EXTERNAL_DIFF=echo git diff | { - read path oldfile oldhex oldmode newfile newhex newmode && - test "z$path" = zfile && - test "z$oldmode" = z100644 && - test "z$newhex" = "z$ZERO_OID" && - test "z$newmode" = z120000 && - oh=$(git rev-parse --verify HEAD:file) && - test "z$oh" = "z$oldhex" - } && + + cat >expect <<-EOF && + file $(git rev-parse --verify HEAD:file) 100644 $(test_oid zero) 120000 + EOF + GIT_EXTERNAL_DIFF=echo git diff >out && + cut -d" " -f1,3-4,6- <out >actual && + test_cmp expect actual && + GIT_EXTERNAL_DIFF=echo git diff --no-ext-diff >actual && git diff >expect && test_cmp expect actual @@ -72,27 +65,25 @@ test_expect_success 'diff.external' ' git reset --hard && echo third >file && test_config diff.external echo && - git diff | { - read path oldfile oldhex oldmode newfile newhex newmode && - test "z$path" = zfile && - test "z$oldmode" = z100644 && - test "z$newhex" = "z$ZERO_OID" && - test "z$newmode" = z100644 && - oh=$(git rev-parse --verify HEAD:file) && - test "z$oh" = "z$oldhex" - } + + cat >expect <<-EOF && + file $(git rev-parse --verify HEAD:file) 100644 $(test_oid zero) 100644 + EOF + git diff >out && + cut -d" " -f1,3-4,6- <out >actual && + test_cmp expect actual ' -test_expect_success 'diff.external should apply only to diff' ' +test_expect_success !SANITIZE_LEAK 'diff.external should apply only to diff' ' test_config diff.external echo && - git log -p -1 HEAD | - grep "^diff --git a/file b/file" + git log -p -1 HEAD >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'diff.external and --no-ext-diff' ' test_config diff.external echo && - git diff --no-ext-diff | - grep "^diff --git a/file b/file" + git diff --no-ext-diff >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'diff attribute' ' @@ -103,29 +94,23 @@ test_expect_success 'diff attribute' ' echo >.gitattributes "file diff=parrot" && - git diff | { - read path oldfile oldhex oldmode newfile newhex newmode && - test "z$path" = zfile && - test "z$oldmode" = z100644 && - test "z$newhex" = "z$ZERO_OID" && - test "z$newmode" = z100644 && - oh=$(git rev-parse --verify HEAD:file) && - test "z$oh" = "z$oldhex" - } - + cat >expect <<-EOF && + file $(git rev-parse --verify HEAD:file) 100644 $(test_oid zero) 100644 + EOF + git diff >out && + cut -d" " -f1,3-4,6- <out >actual && + test_cmp expect actual ' -test_expect_success 'diff attribute should apply only to diff' ' - - git log -p -1 HEAD | - grep "^diff --git a/file b/file" +test_expect_success !SANITIZE_LEAK 'diff attribute should apply only to diff' ' + git log -p -1 HEAD >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'diff attribute and --no-ext-diff' ' - - git diff --no-ext-diff | - grep "^diff --git a/file b/file" + git diff --no-ext-diff >out && + grep "^diff --git a/file b/file" out ' @@ -136,48 +121,55 @@ test_expect_success 'diff attribute' ' echo >.gitattributes "file diff=color" && - git diff | { - read path oldfile oldhex oldmode newfile newhex newmode && - test "z$path" = zfile && - test "z$oldmode" = z100644 && - test "z$newhex" = "z$ZERO_OID" && - test "z$newmode" = z100644 && - oh=$(git rev-parse --verify HEAD:file) && - test "z$oh" = "z$oldhex" - } - + cat >expect <<-EOF && + file $(git rev-parse --verify HEAD:file) 100644 $(test_oid zero) 100644 + EOF + git diff >out && + cut -d" " -f1,3-4,6- <out >actual && + test_cmp expect actual ' -test_expect_success 'diff attribute should apply only to diff' ' - - git log -p -1 HEAD | - grep "^diff --git a/file b/file" +test_expect_success !SANITIZE_LEAK 'diff attribute should apply only to diff' ' + git log -p -1 HEAD >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'diff attribute and --no-ext-diff' ' - - git diff --no-ext-diff | - grep "^diff --git a/file b/file" + git diff --no-ext-diff >out && + grep "^diff --git a/file b/file" out ' test_expect_success 'GIT_EXTERNAL_DIFF trumps diff.external' ' >.gitattributes && test_config diff.external "echo ext-global" && - GIT_EXTERNAL_DIFF="echo ext-env" git diff | grep ext-env + + cat >expect <<-EOF && + ext-env file $(git rev-parse --verify HEAD:file) 100644 file $(test_oid zero) 100644 + EOF + GIT_EXTERNAL_DIFF="echo ext-env" git diff >out && + cut -d" " -f1-2,4- <out >actual && + test_cmp expect actual ' test_expect_success 'attributes trump GIT_EXTERNAL_DIFF and diff.external' ' test_config diff.foo.command "echo ext-attribute" && test_config diff.external "echo ext-global" && echo "file diff=foo" >.gitattributes && - GIT_EXTERNAL_DIFF="echo ext-env" git diff | grep ext-attribute + + cat >expect <<-EOF && + ext-attribute file $(git rev-parse --verify HEAD:file) 100644 file $(test_oid zero) 100644 + EOF + GIT_EXTERNAL_DIFF="echo ext-env" git diff >out && + cut -d" " -f1-2,4- <out >actual && + test_cmp expect actual ' test_expect_success 'no diff with -diff' ' echo >.gitattributes "file -diff" && - git diff | grep Binary + git diff >out && + grep Binary out ' echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file @@ -217,7 +209,12 @@ test_expect_success 'GIT_EXTERNAL_DIFF generates pretty paths' ' touch file.ext && git add file.ext && echo with extension > file.ext && - GIT_EXTERNAL_DIFF=echo git diff file.ext | grep ......_file\.ext && + + cat >expect <<-EOF && + file.ext file $(git rev-parse --verify HEAD:file) 100644 file.ext $(test_oid zero) 100644 + EOF + GIT_EXTERNAL_DIFF=echo git diff file.ext >out && + cut -d" " -f1,3- <out >actual && git update-index --force-remove file.ext && rm file.ext ' diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 6cef0da982..295da987cc 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -2,7 +2,6 @@ test_description='difference in submodules' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh @@ -28,10 +27,8 @@ test_expect_success setup ' git commit -m "submodule #2" ) && - set x $( - cd sub && - git rev-list HEAD - ) && + git -C sub rev-list HEAD >revs && + set x $(cat revs) && echo ":160000 160000 $3 $ZERO_OID M sub" >expect && subtip=$3 subprev=$2 ' diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index d5abcf4b4c..15764ee9ac 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -324,6 +324,7 @@ test_language_driver dts test_language_driver fortran test_language_driver html test_language_driver java +test_language_driver kotlin test_language_driver matlab test_language_driver objc test_language_driver pascal diff --git a/t/t4034/kotlin/expect b/t/t4034/kotlin/expect new file mode 100644 index 0000000000..7f76f7540d --- /dev/null +++ b/t/t4034/kotlin/expect @@ -0,0 +1,43 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index 11ea3de..2e1df4c 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,30 +1,30 @@<RESET> +println("Hello World<RED>!\n<RESET><GREEN>?<RESET>") +<GREEN>(<RESET>1<GREEN>) (<RESET>-1e10<GREEN>) (<RESET>0xabcdef<GREEN>)<RESET> '<RED>x<RESET><GREEN>y<RESET>' +[<RED>a<RESET><GREEN>x<RESET>] <RED>a<RESET><GREEN>x<RESET>-><RED>b a<RESET><GREEN>y x<RESET>.<RED>b<RESET><GREEN>y<RESET> +!<RED>a a<RESET><GREEN>x x<RESET>.inv() <RED>a<RESET><GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>&<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>*<RED>b a<RESET><GREEN>y x<RESET>/<RED>b a<RESET><GREEN>y x<RESET>%<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>+<RED>b a<RESET><GREEN>y x<RESET>-<RED>b<RESET><GREEN>y<RESET> +a <RED>shr<RESET><GREEN>shl<RESET> b +<RED>a<RESET><GREEN>x<RESET><<RED>b a<RESET><GREEN>y x<RESET><=<RED>b a<RESET><GREEN>y x<RESET>><RED>b a<RESET><GREEN>y x<RESET>>=<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>==<RED>b a<RESET><GREEN>y x<RESET>!=<RED>b a<RESET><GREEN>y x<RESET>===<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET> and <RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>^<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET> or <RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>&&<RED>b a<RESET><GREEN>y x<RESET>||<RED>b<RESET> +<RED>a<RESET><GREEN>y<RESET> +<GREEN>x<RESET>=<RED>b a<RESET><GREEN>y x<RESET>+=<RED>b a<RESET><GREEN>y x<RESET>-=<RED>b a<RESET><GREEN>y x<RESET>*=<RED>b a<RESET><GREEN>y x<RESET>/=<RED>b a<RESET><GREEN>y x<RESET>%=<RED>b a<RESET><GREEN>y x<RESET><<=<RED>b a<RESET><GREEN>y x<RESET>>>=<RED>b a<RESET><GREEN>y x<RESET>&=<RED>b a<RESET><GREEN>y x<RESET>^=<RED>b a<RESET><GREEN>y x<RESET>|=<RED>b<RESET><GREEN>y<RESET> +a<RED>=<RESET><GREEN>+=<RESET>b c<RED>+=<RESET><GREEN>=<RESET>d e<RED>-=<RESET><GREEN><=<RESET>f g<RED>*=<RESET><GREEN>>=<RESET>h i<RED>/=<RESET><GREEN>/<RESET>j k<RED>%=<RESET><GREEN>%<RESET>l m<RED><<=<RESET><GREEN><<<RESET>n o<RED>>>=<RESET><GREEN>>><RESET>p q<RED>&=<RESET><GREEN>&<RESET>r s<RED>^=<RESET><GREEN>^<RESET>t u<RED>|=<RESET><GREEN>|<RESET>v +a<RED><<=<RESET><GREEN><=<RESET>b +a<RED>||<RESET><GREEN>|<RESET>b a<RED>&&<RESET><GREEN>&<RESET>b +<RED>a<RESET><GREEN>x<RESET>,y +--a<RED>==<RESET><GREEN>!=<RESET>--b +a++<RED>==<RESET><GREEN>!=<RESET>++b +<RED>0xFF_EC_DE_5E 0b100_000 100_000<RESET><GREEN>0xFF_E1_DE_5E 0b100_100 200_000<RESET> +a<RED>==<RESET><GREEN>===<RESET>b +a<RED>!!<RESET><GREEN>!=<RESET>b +<RED>_32<RESET><GREEN>_33<RESET>.find(arr) +X.<RED>fill<RESET><GREEN>find<RESET>() +X.<RED>u<RESET><GREEN>f<RESET>+1 +X.u<RED>-<RESET><GREEN>+<RESET>2 +a<RED>.<RESET><GREEN>..<RESET>b +a<RED>?.<RESET><GREEN>?:<RESET>b +<RED>.32_00_456<RESET><GREEN>.32_00_446<RESET> diff --git a/t/t4034/kotlin/post b/t/t4034/kotlin/post new file mode 100644 index 0000000000..2e1df4c6d5 --- /dev/null +++ b/t/t4034/kotlin/post @@ -0,0 +1,30 @@ +println("Hello World?") +(1) (-1e10) (0xabcdef) 'y' +[x] x->y x.y +!x x.inv() x*y x&y +x*y x/y x%y +x+y x-y +a shl b +x<y x<=y x>y x>=y +x==y x!=y x===y +x and y +x^y +x or y +x&&y x||y +x=y x+=y x-=y x*=y x/=y x%=y x<<=y x>>=y x&=y x^=y x|=y +a+=b c=d e<=f g>=h i/j k%l m<<n o>>p q&r s^t u|v +a<=b +a|b a&b +x,y +--a!=--b +a++!=++b +0xFF_E1_DE_5E 0b100_100 200_000 +a===b +a!=b +_33.find(arr) +X.find() +X.f+1 +X.u+2 +a..b +a?:b +.32_00_446 diff --git a/t/t4034/kotlin/pre b/t/t4034/kotlin/pre new file mode 100644 index 0000000000..11ea3de665 --- /dev/null +++ b/t/t4034/kotlin/pre @@ -0,0 +1,30 @@ +println("Hello World!\n") +1 -1e10 0xabcdef 'x' +[a] a->b a.b +!a a.inv() a*b a&b +a*b a/b a%b +a+b a-b +a shr b +a<b a<=b a>b a>=b +a==b a!=b a===b +a and b +a^b +a or b +a&&b a||b +a=b a+=b a-=b a*=b a/=b a%=b a<<=b a>>=b a&=b a^=b a|=b +a=b c+=d e-=f g*=h i/=j k%=l m<<=n o>>=p q&=r s^=t u|=v +a<<=b +a||b a&&b +a,y +--a==--b +a++==++b +0xFF_EC_DE_5E 0b100_000 100_000 +a==b +a!!b +_32.find(arr) +X.fill() +X.u+1 +X.u-2 +a.b +a?.b +.32_00_456 diff --git a/t/t4123-apply-shrink.sh b/t/t4123-apply-shrink.sh index dfa053ff28..3ef84619f5 100755 --- a/t/t4123-apply-shrink.sh +++ b/t/t4123-apply-shrink.sh @@ -2,8 +2,6 @@ test_description='apply a patch that is larger than the preimage' - -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh cat >F <<\EOF @@ -41,20 +39,8 @@ test_expect_success setup ' ' test_expect_success 'apply should fail gracefully' ' - - if git apply --index patch - then - echo Oops, should not have succeeded - false - else - status=$? && - echo "Status was $status" && - if test -f .git/index.lock - then - echo Oops, should not have crashed - false - fi - fi + test_must_fail git apply --index patch && + test_path_is_missing .git/index.lock ' test_done diff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh index cb3181e8b7..f6db5a79dd 100755 --- a/t/t4128-apply-root.sh +++ b/t/t4128-apply-root.sh @@ -2,8 +2,6 @@ test_description='apply same filename' - -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' @@ -26,10 +24,11 @@ diff a/bla/blub/dir/file b/bla/blub/dir/file EOF test_expect_success 'apply --directory -p (1)' ' - git apply --directory=some/sub -p3 --index patch && - test Bello = $(git show :some/sub/dir/file) && - test Bello = $(cat some/sub/dir/file) + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file ' @@ -37,8 +36,10 @@ test_expect_success 'apply --directory -p (2) ' ' git reset --hard initial && git apply --directory=some/sub/ -p3 --index patch && - test Bello = $(git show :some/sub/dir/file) && - test Bello = $(cat some/sub/dir/file) + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file ' @@ -55,8 +56,10 @@ EOF test_expect_success 'apply --directory (new file)' ' git reset --hard initial && git apply --directory=some/sub/dir/ --index patch && - test content = $(git show :some/sub/dir/newfile) && - test content = $(cat some/sub/dir/newfile) + echo content >expect && + git show :some/sub/dir/newfile >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/newfile ' cat > patch << EOF @@ -72,8 +75,10 @@ EOF test_expect_success 'apply --directory -p (new file)' ' git reset --hard initial && git apply -p2 --directory=some/sub/dir/ --index patch && - test content = $(git show :some/sub/dir/newfile2) && - test content = $(cat some/sub/dir/newfile2) + echo content >expect && + git show :some/sub/dir/newfile2 >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/newfile2 ' cat > patch << EOF @@ -91,7 +96,8 @@ test_expect_success 'apply --directory (delete file)' ' echo content >some/sub/dir/delfile && git add some/sub/dir/delfile && git apply --directory=some/sub/dir/ --index patch && - ! (git ls-files | grep delfile) + git ls-files >out && + ! grep delfile out ' cat > patch << 'EOF' @@ -107,8 +113,10 @@ EOF test_expect_success 'apply --directory (quoted filename)' ' git reset --hard initial && git apply --directory=some/sub/dir/ --index patch && - test content = $(git show :some/sub/dir/quotefile) && - test content = $(cat some/sub/dir/quotefile) + echo content >expect && + git show :some/sub/dir/quotefile >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/quotefile ' test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 6306e2cbe5..be07407f85 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -484,7 +484,6 @@ do ) ' done -test_done test_expect_success 'log --author' ' cat >expect <<-\EOF && diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh index cc3cebf672..fa9d32facf 100755 --- a/t/t4216-log-bloom.sh +++ b/t/t4216-log-bloom.sh @@ -48,6 +48,7 @@ graph_read_expect () { header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 num_commits: $1 chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data + options: bloom(1,10,7) read_generation_data EOF test-tool read-graph >actual && test_cmp expect actual diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 2fd845187e..a11d61206a 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -315,8 +315,10 @@ test_expect_success \ git index-pack -o tmp.idx test-3.pack && cmp tmp.idx test-1-${packname_1}.idx && - git index-pack test-3.pack && + git index-pack --promisor=message test-3.pack && cmp test-3.idx test-1-${packname_1}.idx && + echo message >expect && + test_cmp expect test-3.promisor && cat test-2-${packname_2}.pack >test-3.pack && git index-pack -o tmp.idx test-2-${packname_2}.pack && diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index edb728f77c..fbf0d64578 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -29,12 +29,7 @@ test_expect_success 'setup full repo' ' cd "$TRASH_DIRECTORY/full" && git init && git config core.commitGraph true && - objdir=".git/objects" && - - test_oid_cache <<-EOF - oid_version sha1:1 - oid_version sha256:2 - EOF + objdir=".git/objects" ' test_expect_success POSIXPERM 'tweak umask for modebit tests' ' @@ -69,46 +64,10 @@ test_expect_success 'create commits and repack' ' git repack ' -graph_git_two_modes() { - git -c core.commitGraph=true $1 >output && - git -c core.commitGraph=false $1 >expect && - test_cmp expect output -} - -graph_git_behavior() { - MSG=$1 - DIR=$2 - BRANCH=$3 - COMPARE=$4 - test_expect_success "check normal git operations: $MSG" ' - cd "$TRASH_DIRECTORY/$DIR" && - graph_git_two_modes "log --oneline $BRANCH" && - graph_git_two_modes "log --topo-order $BRANCH" && - graph_git_two_modes "log --graph $COMPARE..$BRANCH" && - graph_git_two_modes "branch -vv" && - graph_git_two_modes "merge-base -a $BRANCH $COMPARE" - ' -} +. "$TEST_DIRECTORY"/lib-commit-graph.sh graph_git_behavior 'no graph' full commits/3 commits/1 -graph_read_expect() { - OPTIONAL="" - NUM_CHUNKS=3 - if test ! -z "$2" - then - OPTIONAL=" $2" - NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) - fi - cat >expect <<- EOF - header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 - num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL - EOF - test-tool read-graph >output && - test_cmp expect output -} - test_expect_success 'exit with correct error on bad input to --stdin-commits' ' cd "$TRASH_DIRECTORY/full" && # invalid, non-hex OID @@ -466,10 +425,10 @@ test_expect_success 'warn on improper hash version' ' ) ' -test_expect_success 'lower layers have overflow chunk' ' +test_expect_success TIME_IS_64BIT,TIME_T_IS_64BIT 'lower layers have overflow chunk' ' cd "$TRASH_DIRECTORY/full" && UNIX_EPOCH_ZERO="@0 +0000" && - FUTURE_DATE="@2147483646 +0000" && + FUTURE_DATE="@4147483646 +0000" && rm -f .git/objects/info/commit-graph && test_commit --date "$FUTURE_DATE" future-1 && test_commit --date "$UNIX_EPOCH_ZERO" old-1 && @@ -497,7 +456,7 @@ test_expect_success 'git commit-graph verify' ' cd "$TRASH_DIRECTORY/full" && git rev-parse commits/8 | git -c commitGraph.generationVersion=1 commit-graph write --stdin-commits && git commit-graph verify >output && - graph_read_expect 9 extra_edges + graph_read_expect 9 extra_edges 1 ' NUM_COMMITS=9 @@ -825,10 +784,6 @@ test_expect_success 'set up and verify repo with generation data overflow chunk' objdir=".git/objects" && UNIX_EPOCH_ZERO="@0 +0000" && FUTURE_DATE="@2147483646 +0000" && - test_oid_cache <<-EOF && - oid_version sha1:1 - oid_version sha256:2 - EOF cd "$TRASH_DIRECTORY" && mkdir repo && cd repo && diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh index 847b809710..669ddc645f 100755 --- a/t/t5324-split-commit-graph.sh +++ b/t/t5324-split-commit-graph.sh @@ -30,10 +30,16 @@ graph_read_expect() { then NUM_BASE=$2 fi + OPTIONS= + if test -z "$3" + then + OPTIONS=" read_generation_data" + fi cat >expect <<- EOF header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE num_commits: $1 chunks: oid_fanout oid_lookup commit_metadata generation_data + options:$OPTIONS EOF test-tool read-graph >output && test_cmp expect output @@ -508,6 +514,7 @@ test_expect_success 'setup repo for mixed generation commit-graph-chain' ' header: 43475048 1 $(test_oid oid_version) 4 1 num_commits: $NUM_SECOND_LAYER_COMMITS chunks: oid_fanout oid_lookup commit_metadata + options: EOF test_cmp expect output && git commit-graph verify && @@ -540,6 +547,7 @@ test_expect_success 'do not write generation data chunk if not present on existi header: 43475048 1 $(test_oid oid_version) 4 2 num_commits: $NUM_THIRD_LAYER_COMMITS chunks: oid_fanout oid_lookup commit_metadata + options: EOF test_cmp expect output && git commit-graph verify @@ -581,6 +589,7 @@ test_expect_success 'do not write generation data chunk if the topmost remaining header: 43475048 1 $(test_oid oid_version) 4 2 num_commits: $(($NUM_THIRD_LAYER_COMMITS + $NUM_FOURTH_LAYER_COMMITS)) chunks: oid_fanout oid_lookup commit_metadata + options: EOF test_cmp expect output && git commit-graph verify @@ -620,6 +629,7 @@ test_expect_success 'write generation data chunk if topmost remaining layer has header: 43475048 1 $(test_oid oid_version) 5 1 num_commits: $(($NUM_SECOND_LAYER_COMMITS + $NUM_THIRD_LAYER_COMMITS + $NUM_FOURTH_LAYER_COMMITS + $NUM_FIFTH_LAYER_COMMITS)) chunks: oid_fanout oid_lookup commit_metadata generation_data + options: read_generation_data EOF test_cmp expect output ) diff --git a/t/t5328-commit-graph-64bit-time.sh b/t/t5328-commit-graph-64bit-time.sh new file mode 100755 index 0000000000..093f0c067a --- /dev/null +++ b/t/t5328-commit-graph-64bit-time.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='commit graph with 64-bit timestamps' +. ./test-lib.sh + +if ! test_have_prereq TIME_IS_64BIT || ! test_have_prereq TIME_T_IS_64BIT +then + skip_all='skipping 64-bit timestamp tests' + test_done +fi + +. "$TEST_DIRECTORY"/lib-commit-graph.sh + +UNIX_EPOCH_ZERO="@0 +0000" +FUTURE_DATE="@4147483646 +0000" + +GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0 + +test_expect_success 'lower layers have overflow chunk' ' + rm -f .git/objects/info/commit-graph && + test_commit --date "$FUTURE_DATE" future-1 && + test_commit --date "$UNIX_EPOCH_ZERO" old-1 && + git commit-graph write --reachable && + test_commit --date "$FUTURE_DATE" future-2 && + test_commit --date "$UNIX_EPOCH_ZERO" old-2 && + git commit-graph write --reachable --split=no-merge && + test_commit extra && + git commit-graph write --reachable --split=no-merge && + git commit-graph write --reachable && + graph_read_expect 5 "generation_data generation_data_overflow" && + mv .git/objects/info/commit-graph commit-graph-upgraded && + git commit-graph write --reachable && + graph_read_expect 5 "generation_data generation_data_overflow" && + test_cmp .git/objects/info/commit-graph commit-graph-upgraded +' + +graph_git_behavior 'overflow' '' HEAD~2 HEAD + +test_expect_success 'set up and verify repo with generation data overflow chunk' ' + mkdir repo && + cd repo && + git init && + test_commit --date "$UNIX_EPOCH_ZERO" 1 && + test_commit 2 && + test_commit --date "$UNIX_EPOCH_ZERO" 3 && + git commit-graph write --reachable && + graph_read_expect 3 generation_data && + test_commit --date "$FUTURE_DATE" 4 && + test_commit 5 && + test_commit --date "$UNIX_EPOCH_ZERO" 6 && + git branch left && + git reset --hard 3 && + test_commit 7 && + test_commit --date "$FUTURE_DATE" 8 && + test_commit 9 && + git branch right && + git reset --hard 3 && + test_merge M left right && + git commit-graph write --reachable && + graph_read_expect 10 "generation_data generation_data_overflow" && + git commit-graph verify +' + +graph_git_behavior 'overflow 2' repo left right + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 9ab315424c..c90cf47acd 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -753,7 +753,9 @@ test_expect_success 'rename a remote' ' ( cd four && git config branch.main.pushRemote origin && - git remote rename origin upstream && + GIT_TRACE2_EVENT=$(pwd)/trace \ + git remote rename --progress origin upstream && + test_region progress "Renaming remote references" trace && grep "pushRemote" .git/config && test -z "$(git for-each-ref refs/remotes/origin)" && test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/main" && diff --git a/t/t6005-rev-list-count.sh b/t/t6005-rev-list-count.sh index 86542c650e..e960049f64 100755 --- a/t/t6005-rev-list-count.sh +++ b/t/t6005-rev-list-count.sh @@ -2,7 +2,6 @@ test_description='git rev-list --max-count and --skip test' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' @@ -14,39 +13,39 @@ test_expect_success 'setup' ' ' test_expect_success 'no options' ' - test $(git rev-list HEAD | wc -l) = 5 + test_stdout_line_count = 5 git rev-list HEAD ' test_expect_success '--max-count' ' - test $(git rev-list HEAD --max-count=0 | wc -l) = 0 && - test $(git rev-list HEAD --max-count=3 | wc -l) = 3 && - test $(git rev-list HEAD --max-count=5 | wc -l) = 5 && - test $(git rev-list HEAD --max-count=10 | wc -l) = 5 + test_stdout_line_count = 0 git rev-list HEAD --max-count=0 && + test_stdout_line_count = 3 git rev-list HEAD --max-count=3 && + test_stdout_line_count = 5 git rev-list HEAD --max-count=5 && + test_stdout_line_count = 5 git rev-list HEAD --max-count=10 ' test_expect_success '--max-count all forms' ' - test $(git rev-list HEAD --max-count=1 | wc -l) = 1 && - test $(git rev-list HEAD -1 | wc -l) = 1 && - test $(git rev-list HEAD -n1 | wc -l) = 1 && - test $(git rev-list HEAD -n 1 | wc -l) = 1 + test_stdout_line_count = 1 git rev-list HEAD --max-count=1 && + test_stdout_line_count = 1 git rev-list HEAD -1 && + test_stdout_line_count = 1 git rev-list HEAD -n1 && + test_stdout_line_count = 1 git rev-list HEAD -n 1 ' test_expect_success '--skip' ' - test $(git rev-list HEAD --skip=0 | wc -l) = 5 && - test $(git rev-list HEAD --skip=3 | wc -l) = 2 && - test $(git rev-list HEAD --skip=5 | wc -l) = 0 && - test $(git rev-list HEAD --skip=10 | wc -l) = 0 + test_stdout_line_count = 5 git rev-list HEAD --skip=0 && + test_stdout_line_count = 2 git rev-list HEAD --skip=3 && + test_stdout_line_count = 0 git rev-list HEAD --skip=5 && + test_stdout_line_count = 0 git rev-list HEAD --skip=10 ' test_expect_success '--skip --max-count' ' - test $(git rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 && - test $(git rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 && - test $(git rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 && - test $(git rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 && - test $(git rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 && - test $(git rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 && - test $(git rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 && - test $(git rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0 + test_stdout_line_count = 0 git rev-list HEAD --skip=0 --max-count=0 && + test_stdout_line_count = 5 git rev-list HEAD --skip=0 --max-count=10 && + test_stdout_line_count = 0 git rev-list HEAD --skip=3 --max-count=0 && + test_stdout_line_count = 1 git rev-list HEAD --skip=3 --max-count=1 && + test_stdout_line_count = 2 git rev-list HEAD --skip=3 --max-count=2 && + test_stdout_line_count = 2 git rev-list HEAD --skip=3 --max-count=10 && + test_stdout_line_count = 0 git rev-list HEAD --skip=5 --max-count=10 && + test_stdout_line_count = 0 git rev-list HEAD --skip=10 --max-count=10 ' test_done diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 63fcccec32..de1e87f162 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -12,7 +12,9 @@ note () { } unnote () { - git name-rev --tags --annotate-stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" + test_when_finished "rm -f tmp" && + git name-rev --tags --annotate-stdin >tmp && + sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" <tmp } # @@ -111,8 +113,8 @@ check_outcome () { shift && param="$*" && test_expect_$outcome "log $param" ' - git log --pretty="$FMT" --parents $param | - unnote >actual && + git log --pretty="$FMT" --parents $param >out && + unnote >actual <out && sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual && test_cmp expect check ' @@ -151,8 +153,8 @@ check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F check_result 'E C B A' --full-history E -- lost test_expect_success 'full history simplification without parent' ' printf "%s\n" E C B A >expect && - git log --pretty="$FMT" --full-history E -- lost | - unnote >actual && + git log --pretty="$FMT" --full-history E -- lost >out && + unnote >actual <out && sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual && test_cmp expect check ' diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh index b13e8a52a9..ed95d19542 100755 --- a/t/t6020-bundle-misc.sh +++ b/t/t6020-bundle-misc.sh @@ -475,4 +475,78 @@ test_expect_success 'clone from bundle' ' test_cmp expect actual ' +test_expect_success 'unfiltered bundle with --objects' ' + git bundle create all-objects.bdl \ + --all --objects && + git bundle create all.bdl \ + --all && + + # Compare the headers of these files. + sed -n -e "/^$/q" -e "p" all.bdl >expect && + sed -n -e "/^$/q" -e "p" all-objects.bdl >actual && + test_cmp expect actual +' + +for filter in "blob:none" "tree:0" "tree:1" "blob:limit=100" +do + test_expect_success "filtered bundle: $filter" ' + test_when_finished rm -rf .git/objects/pack cloned unbundled && + git bundle create partial.bdl \ + --all \ + --filter=$filter && + + git bundle verify partial.bdl >unfiltered && + make_user_friendly_and_stable_output <unfiltered >actual && + + cat >expect <<-EOF && + The bundle contains these 10 refs: + <COMMIT-P> refs/heads/main + <COMMIT-N> refs/heads/release + <COMMIT-D> refs/heads/topic/1 + <COMMIT-H> refs/heads/topic/2 + <COMMIT-D> refs/pull/1/head + <COMMIT-G> refs/pull/2/head + <TAG-1> refs/tags/v1 + <TAG-2> refs/tags/v2 + <TAG-3> refs/tags/v3 + <COMMIT-P> HEAD + The bundle uses this filter: $filter + The bundle records a complete history. + EOF + test_cmp expect actual && + + test_config uploadpack.allowfilter 1 && + test_config uploadpack.allowanysha1inwant 1 && + git clone --no-local --filter=$filter --bare "file://$(pwd)" cloned && + + git init unbundled && + git -C unbundled bundle unbundle ../partial.bdl >ref-list.txt && + ls unbundled/.git/objects/pack/pack-*.promisor >promisor && + test_line_count = 1 promisor && + + # Count the same number of reachable objects. + reflist=$(git for-each-ref --format="%(objectname)") && + git rev-list --objects --filter=$filter --missing=allow-any \ + $reflist >expect && + for repo in cloned unbundled + do + git -C $repo rev-list --objects --missing=allow-any \ + $reflist >actual && + test_cmp expect actual || return 1 + done + ' +done + +# NEEDSWORK: 'git clone --bare' should be able to clone from a filtered +# bundle, but that requires a change to promisor/filter config options. +# For now, we fail gracefully with a helpful error. This behavior can be +# changed in the future to succeed as much as possible. +test_expect_success 'cloning from filtered bundle has useful error' ' + git bundle create partial.bdl \ + --all \ + --filter=blob:none && + test_must_fail git clone --bare partial.bdl partial 2>err && + grep "cannot clone from filtered bundle" err +' + test_done diff --git a/t/t6102-rev-list-unexpected-objects.sh b/t/t6102-rev-list-unexpected-objects.sh index 6f0902b863..cf0195e826 100755 --- a/t/t6102-rev-list-unexpected-objects.sh +++ b/t/t6102-rev-list-unexpected-objects.sh @@ -17,8 +17,13 @@ test_expect_success 'setup unexpected non-blob entry' ' broken_tree="$(git hash-object -w --literally -t tree broken-tree)" ' -test_expect_failure 'traverse unexpected non-blob entry (lone)' ' - test_must_fail git rev-list --objects $broken_tree +test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob entry (lone)' ' + sed "s/Z$//" >expect <<-EOF && + $broken_tree Z + $tree foo + EOF + git rev-list --objects $broken_tree >actual && + test_cmp expect actual ' test_expect_success 'traverse unexpected non-blob entry (seen)' ' @@ -116,8 +121,8 @@ test_expect_success 'setup unexpected non-blob tag' ' tag=$(git hash-object -w --literally -t tag broken-tag) ' -test_expect_failure 'traverse unexpected non-blob tag (lone)' ' - test_must_fail git rev-list --objects $tag +test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob tag (lone)' ' + git rev-list --objects $tag ' test_expect_success 'traverse unexpected non-blob tag (seen)' ' diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 9781b92aed..9a35e783a7 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -488,6 +488,124 @@ test_expect_success 'name-rev covers all conditions while looking at parents' ' ) ' +# A-B-C-D-E-main +# +# Where C has a non-monotonically increasing commit timestamp w.r.t. other +# commits +test_expect_success 'non-monotonic commit dates setup' ' + UNIX_EPOCH_ZERO="@0 +0000" && + git init non-monotonic && + test_commit -C non-monotonic A && + test_commit -C non-monotonic --no-tag B && + test_commit -C non-monotonic --no-tag --date "$UNIX_EPOCH_ZERO" C && + test_commit -C non-monotonic D && + test_commit -C non-monotonic E +' + +test_expect_success 'name-rev with commitGraph handles non-monotonic timestamps' ' + test_config -C non-monotonic core.commitGraph true && + ( + cd non-monotonic && + + git commit-graph write --reachable && + + echo "main~3 tags/D~2" >expect && + git name-rev --tags main~3 >actual && + + test_cmp expect actual + ) +' + +test_expect_success 'name-rev --all works with non-monotonic timestamps' ' + test_config -C non-monotonic core.commitGraph false && + ( + cd non-monotonic && + + rm -rf .git/info/commit-graph* && + + cat >tags <<-\EOF && + tags/E + tags/D + tags/D~1 + tags/D~2 + tags/A + EOF + + git log --pretty=%H >revs && + + paste -d" " revs tags | sort >expect && + + git name-rev --tags --all | sort >actual && + test_cmp expect actual + ) +' + +test_expect_success 'name-rev --annotate-stdin works with non-monotonic timestamps' ' + test_config -C non-monotonic core.commitGraph false && + ( + cd non-monotonic && + + rm -rf .git/info/commit-graph* && + + cat >expect <<-\EOF && + E + D + D~1 + D~2 + A + EOF + + git log --pretty=%H >revs && + git name-rev --tags --annotate-stdin --name-only <revs >actual && + test_cmp expect actual + ) +' + +test_expect_success 'name-rev --all works with commitGraph' ' + test_config -C non-monotonic core.commitGraph true && + ( + cd non-monotonic && + + git commit-graph write --reachable && + + cat >tags <<-\EOF && + tags/E + tags/D + tags/D~1 + tags/D~2 + tags/A + EOF + + git log --pretty=%H >revs && + + paste -d" " revs tags | sort >expect && + + git name-rev --tags --all | sort >actual && + test_cmp expect actual + ) +' + +test_expect_success 'name-rev --annotate-stdin works with commitGraph' ' + test_config -C non-monotonic core.commitGraph true && + ( + cd non-monotonic && + + git commit-graph write --reachable && + + cat >expect <<-\EOF && + E + D + D~1 + D~2 + A + EOF + + git log --pretty=%H >revs && + git name-rev --tags --annotate-stdin --name-only <revs >actual && + test_cmp expect actual + ) +' + # B # o # \ diff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh index 8e6241f92e..0753fc95f4 100755 --- a/t/t6407-merge-binary.sh +++ b/t/t6407-merge-binary.sh @@ -43,14 +43,9 @@ test_expect_success resolve ' rm -f a* m* && git reset --hard anchor && - if git merge -s resolve main - then - echo Oops, should not have succeeded - false - else - git ls-files -s >current && - test_cmp expect current - fi + test_must_fail git merge -s resolve main && + git ls-files -s >current && + test_cmp expect current ' test_expect_success recursive ' @@ -58,14 +53,9 @@ test_expect_success recursive ' rm -f a* m* && git reset --hard anchor && - if git merge -s recursive main - then - echo Oops, should not have succeeded - false - else - git ls-files -s >current && - test_cmp expect current - fi + test_must_fail git merge -s recursive main && + git ls-files -s >current && + test_cmp expect current ' test_done diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index 5b81a130e9..479db32cd6 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -4421,14 +4421,14 @@ test_setup_12c1 () { git checkout A && git mv node2/ node1/ && - for i in `git ls-files`; do echo side A >>$i; done && + for i in $(git ls-files); do echo side A >>$i; done && git add -u && test_tick && git commit -m "A" && git checkout B && git mv node1/ node2/ && - for i in `git ls-files`; do echo side B >>$i; done && + for i in $(git ls-files); do echo side B >>$i; done && git add -u && test_tick && git commit -m "B" @@ -4511,7 +4511,7 @@ test_setup_12c2 () { git checkout A && git mv node2/ node1/ && - for i in `git ls-files`; do echo side A >>$i; done && + for i in $(git ls-files); do echo side A >>$i; done && git add -u && echo leaf5 >node1/leaf5 && git add node1/leaf5 && @@ -4520,7 +4520,7 @@ test_setup_12c2 () { git checkout B && git mv node1/ node2/ && - for i in `git ls-files`; do echo side B >>$i; done && + for i in $(git ls-files); do echo side B >>$i; done && git add -u && echo leaf6 >node2/leaf6 && git add node2/leaf6 && @@ -4759,7 +4759,7 @@ test_setup_12f () { echo g >dir/subdir/tweaked/g && echo h >dir/subdir/tweaked/h && test_seq 20 30 >dir/subdir/tweaked/Makefile && - for i in `test_seq 1 88`; do + for i in $(test_seq 1 88); do echo content $i >dir/unchanged/file_$i done && git add . && diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index a0c123b0a7..ca90ee805e 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -90,6 +90,9 @@ test_expect_success 'setup' ' cd worktree && mkdir done dtwo dthree && touch one two three done/one dtwo/two dthree/three && + test-tool chmtime =-300 one two three done/one dtwo/two dthree/three && + test-tool chmtime =-300 done dtwo dthree && + test-tool chmtime =-300 . && git add one two done/one && : >.git/info/exclude && git update-index --untracked-cache && @@ -142,7 +145,6 @@ two EOF test_expect_success 'status first time (empty cache)' ' - avoid_racy && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../actual && @@ -166,7 +168,6 @@ test_expect_success 'untracked cache after first status' ' ' test_expect_success 'status second time (fully populated cache)' ' - avoid_racy && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../actual && @@ -190,8 +191,8 @@ test_expect_success 'untracked cache after second status' ' ' test_expect_success 'modify in root directory, one dir invalidation' ' - avoid_racy && : >four && + test-tool chmtime =-240 four && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../actual && @@ -241,7 +242,6 @@ EOF ' test_expect_success 'new .gitignore invalidates recursively' ' - avoid_racy && echo four >.gitignore && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ @@ -292,7 +292,6 @@ EOF ' test_expect_success 'new info/exclude invalidates everything' ' - avoid_racy && echo three >>.git/info/exclude && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ @@ -520,14 +519,14 @@ test_expect_success 'create/modify files, some of which are gitignored' ' echo three >done/three && # three is gitignored echo four >done/four && # four is gitignored at a higher level echo five >done/five && # five is not gitignored - echo test >base && #we need to ensure that the root dir is touched - rm base && + test-tool chmtime =-180 done/two done/three done/four done/five done && + # we need to ensure that the root dir is touched (in the past); + test-tool chmtime =-180 . && sync_mtime ' test_expect_success 'test sparse status with untracked cache' ' : >../trace.output && - avoid_racy && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../status.actual && iuc status --porcelain >../status.iuc && @@ -570,7 +569,6 @@ EOF ' test_expect_success 'test sparse status again with untracked cache' ' - avoid_racy && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../status.actual && @@ -597,11 +595,11 @@ EOF test_expect_success 'set up for test of subdir and sparse checkouts' ' mkdir done/sub && mkdir done/sub/sub && - echo "sub" > done/sub/sub/file + echo "sub" > done/sub/sub/file && + test-tool chmtime =-120 done/sub/sub/file done/sub/sub done/sub done ' test_expect_success 'test sparse status with untracked cache and subdir' ' - avoid_racy && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../status.actual && @@ -651,7 +649,6 @@ EOF ' test_expect_success 'test sparse status again with untracked cache and subdir' ' - avoid_racy && : >../trace.output && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \ git status --porcelain >../status.actual && diff --git a/t/t7103-reset-bare.sh b/t/t7103-reset-bare.sh index 0de83e3619..a60153f9f3 100755 --- a/t/t7103-reset-bare.sh +++ b/t/t7103-reset-bare.sh @@ -63,9 +63,12 @@ test_expect_success '"mixed" reset is not allowed in bare' ' test_must_fail git reset --mixed HEAD^ ' -test_expect_success '"soft" reset is allowed in bare' ' +test_expect_success !SANITIZE_LEAK '"soft" reset is allowed in bare' ' git reset --soft HEAD^ && - test "$(git show --pretty=format:%s | head -n 1)" = "one" + git show --pretty=format:%s >out && + echo one >expect && + head -n 1 out >actual && + test_cmp expect actual ' test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 11cccbb333..000e055811 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -205,8 +205,18 @@ test_expect_success 'submodule update should fail due to local changes' ' (cd submodule && compare_head ) && - test_must_fail git submodule update submodule - ) + test_must_fail git submodule update submodule 2>../actual.raw + ) && + sed "s/^> //" >expect <<-\EOF && + > error: Your local changes to the following files would be overwritten by checkout: + > file + > Please commit your changes or stash them before you switch branches. + > Aborting + > fatal: Unable to checkout OID in submodule path '\''submodule'\'' + EOF + sed -e "s/checkout $SQ[^$SQ]*$SQ/checkout OID/" <actual.raw >actual && + test_cmp expect actual + ' test_expect_success 'submodule update should throw away changes with --force ' ' (cd super && @@ -1061,4 +1071,16 @@ test_expect_success 'submodule update --quiet passes quietness to fetch with a s ) ' +test_expect_success 'submodule update --filter requires --init' ' + test_expect_code 129 git -C super submodule update --filter blob:none +' + +test_expect_success 'submodule update --filter sets partial clone settings' ' + test_when_finished "rm -rf super-filter" && + git clone cloned super-filter && + git -C super-filter submodule update --init --filter blob:none && + test_cmp_config -C super-filter/submodule true remote.origin.promisor && + test_cmp_config -C super-filter/submodule blob:none remote.origin.partialclonefilter +' + test_done diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index a3892f494b..c3a4545510 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -193,7 +193,19 @@ test_expect_success 'missing nested submodule alternate fails clone and submodul cd supersuper-clone && check_that_two_of_three_alternates_are_used && # update of the submodule fails - test_must_fail git submodule update --init --recursive + cat >expect <<-\EOF && + fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist + Failed to clone '\''sub'\''. Retry scheduled + fatal: submodule '\''sub-dissociate'\'' cannot add alternate: path ... does not exist + Failed to clone '\''sub-dissociate'\''. Retry scheduled + fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist + Failed to clone '\''sub'\'' a second time, aborting + fatal: Failed to recurse into submodule path ... + EOF + test_must_fail git submodule update --init --recursive 2>err && + grep -e fatal: -e ^Failed err >actual.raw && + sed -e "s/path $SQ[^$SQ]*$SQ/path .../" <actual.raw >actual && + test_cmp expect actual ) ' diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index a6308acf00..fffc57120d 100755 --- a/t/t7519-status-fsmonitor.sh +++ b/t/t7519-status-fsmonitor.sh @@ -324,17 +324,24 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' cd dot-git && mkdir -p .git/hooks && : >tracked && + test-tool chmtime =-60 tracked && : >modified && + test-tool chmtime =-60 modified && mkdir dir1 && : >dir1/tracked && + test-tool chmtime =-60 dir1/tracked && : >dir1/modified && + test-tool chmtime =-60 dir1/modified && mkdir dir2 && : >dir2/tracked && + test-tool chmtime =-60 dir2/tracked && : >dir2/modified && + test-tool chmtime =-60 dir2/modified && write_integration_script && git config core.fsmonitor .git/hooks/fsmonitor-test && git update-index --untracked-cache && git update-index --fsmonitor && + git status && GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-before" \ git status && test-tool dump-untracked-cache >../before diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 5922fb5bdd..770d143204 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -381,4 +381,54 @@ test_expect_success TTY '--quiet disables progress' ' test_must_be_empty stderr ' +test_expect_success 'setup for update-server-info' ' + git init update-server-info && + test_commit -C update-server-info message +' + +test_server_info_present () { + test_path_is_file update-server-info/.git/objects/info/packs && + test_path_is_file update-server-info/.git/info/refs +} + +test_server_info_missing () { + test_path_is_missing update-server-info/.git/objects/info/packs && + test_path_is_missing update-server-info/.git/info/refs +} + +test_server_info_cleanup () { + rm -f update-server-info/.git/objects/info/packs update-server-info/.git/info/refs && + test_server_info_missing +} + +test_expect_success 'updates server info by default' ' + test_server_info_cleanup && + git -C update-server-info repack && + test_server_info_present +' + +test_expect_success '-n skips updating server info' ' + test_server_info_cleanup && + git -C update-server-info repack -n && + test_server_info_missing +' + +test_expect_success 'repack.updateServerInfo=true updates server info' ' + test_server_info_cleanup && + git -C update-server-info -c repack.updateServerInfo=true repack && + test_server_info_present +' + +test_expect_success 'repack.updateServerInfo=false skips updating server info' ' + test_server_info_cleanup && + git -C update-server-info -c repack.updateServerInfo=false repack && + test_server_info_missing +' + +test_expect_success '-n overrides repack.updateServerInfo=true' ' + test_server_info_cleanup && + git -C update-server-info -c repack.updateServerInfo=true repack -n && + test_server_info_missing +' + test_done diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index ca3f24f807..9047d665a1 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -11,9 +11,19 @@ test_expect_success GETTEXT_LOCALE 'setup' ' export LC_ALL ' -test_have_prereq GETTEXT_LOCALE && -test-tool regex "HALLÓ" "Halló" ICASE && -test_set_prereq REGEX_LOCALE +test_expect_success GETTEXT_LOCALE 'setup REGEX_LOCALE prerequisite' ' + # This "test-tool" invocation is identical... + if test-tool regex "HALLÓ" "Halló" ICASE + then + test_set_prereq REGEX_LOCALE + else + + # ... to this one, but this way "test_must_fail" will + # tell a segfault or abort() from the regexec() test + # itself + test_must_fail test-tool regex "HALLÓ" "Halló" ICASE + fi +' test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' git grep -i "TILRAUN: Halló Heimur!" && diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh index 3167473b30..8cb582f0e6 100755 --- a/t/t9502-gitweb-standalone-parse-output.sh +++ b/t/t9502-gitweb-standalone-parse-output.sh @@ -34,7 +34,7 @@ EOF # # This will check that gitweb HTTP header contains proposed filename # as <basename> with '.tar' suffix added, and that generated tarfile -# (gitweb message body) has <prefix> as prefix for al files in tarfile +# (gitweb message body) has <prefix> as prefix for all files in tarfile # # <prefix> default to <basename> check_snapshot () { @@ -207,4 +207,17 @@ test_expect_success 'xss checks' ' xss "" "$TAG+" ' +no_http_equiv_content_type() { + gitweb_run "$@" && + ! grep -E "http-equiv=['\"]?content-type" gitweb.body +} + +# See: <https://html.spec.whatwg.org/dev/semantics.html#attr-meta-http-equiv-content-type> +test_expect_success 'no http-equiv="content-type" in XHTML' ' + no_http_equiv_content_type && + no_http_equiv_content_type "p=.git" && + no_http_equiv_content_type "p=.git;a=log" && + no_http_equiv_content_type "p=.git;a=tree" +' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 9af5fb7674..515b1af7ed 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -548,11 +548,29 @@ then } else setup_malloc_check () { + local g + local t MALLOC_CHECK_=3 MALLOC_PERTURB_=165 export MALLOC_CHECK_ MALLOC_PERTURB_ + if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) && + _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} && + expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null + then + g= + LD_PRELOAD="libc_malloc_debug.so.0" + for t in \ + glibc.malloc.check=1 \ + glibc.malloc.perturb=165 + do + g="${g#:}:$t" + done + GLIBC_TUNABLES=$g + export LD_PRELOAD GLIBC_TUNABLES + fi } teardown_malloc_check () { unset MALLOC_CHECK_ MALLOC_PERTURB_ + unset LD_PRELOAD GLIBC_TUNABLES } fi diff --git a/tree-walk.c b/tree-walk.c index 3a94959d64..506234b4b8 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -89,7 +89,7 @@ void *fill_tree_descriptor(struct repository *r, void *buf = NULL; if (oid) { - buf = read_object_with_reference(r, oid, tree_type, &size, NULL); + buf = read_object_with_reference(r, oid, OBJ_TREE, &size, NULL); if (!buf) die("unable to read tree %s", oid_to_hex(oid)); } @@ -605,7 +605,7 @@ int get_tree_entry(struct repository *r, unsigned long size; struct object_id root; - tree = read_object_with_reference(r, tree_oid, tree_type, &size, &root); + tree = read_object_with_reference(r, tree_oid, OBJ_TREE, &size, &root); if (!tree) return -1; @@ -677,7 +677,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, unsigned long size; tree = read_object_with_reference(r, ¤t_tree_oid, - tree_type, &size, + OBJ_TREE, &size, &root); if (!tree) goto done; diff --git a/unpack-trees.c b/unpack-trees.c index 96525d2ec2..2763a029a1 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1360,6 +1360,42 @@ static int is_sparse_directory_entry(struct cache_entry *ce, return sparse_dir_matches_path(ce, info, name); } +static int unpack_sparse_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) +{ + struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; + struct unpack_trees_options *o = info->data; + int ret; + + assert(o->merge); + + /* + * Unlike in 'unpack_callback', where src[0] is derived from the index when + * merging, src[0] is a transient cache entry derived from the first tree + * provided. Create the temporary entry as if it came from a non-sparse index. + */ + if (!is_null_oid(&names[0].oid)) { + src[0] = create_ce_entry(info, &names[0], 0, + &o->result, 1, + dirmask & (1ul << 0)); + src[0]->ce_flags |= (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE); + } + + /* + * 'unpack_single_entry' assumes that src[0] is derived directly from + * the index, rather than from an entry in 'names'. This is *not* true when + * merging a sparse directory, in which case names[0] is the "index" source + * entry. To match the expectations of 'unpack_single_entry', shift past the + * "index" tree (i.e., names[0]) and adjust 'names', 'n', 'mask', and + * 'dirmask' accordingly. + */ + ret = unpack_single_entry(n - 1, mask >> 1, dirmask >> 1, src, names + 1, info); + + if (src[0]) + discard_cache_entry(src[0]); + + return ret >= 0 ? mask : -1; +} + /* * Note that traverse_by_cache_tree() duplicates some logic in this function * without actually calling it. If you change the logic here you may need to @@ -1693,6 +1729,41 @@ static void populate_from_existing_patterns(struct unpack_trees_options *o, o->pl = pl; } +static void update_sparsity_for_prefix(const char *prefix, + struct index_state *istate) +{ + int prefix_len = strlen(prefix); + struct strbuf ce_prefix = STRBUF_INIT; + + if (!istate->sparse_index) + return; + + while (prefix_len > 0 && prefix[prefix_len - 1] == '/') + prefix_len--; + + if (prefix_len <= 0) + BUG("Invalid prefix passed to update_sparsity_for_prefix"); + + strbuf_grow(&ce_prefix, prefix_len + 1); + strbuf_add(&ce_prefix, prefix, prefix_len); + strbuf_addch(&ce_prefix, '/'); + + /* + * If the prefix points to a sparse directory or a path inside a sparse + * directory, the index should be expanded. This is accomplished in one + * of two ways: + * - if the prefix is inside a sparse directory, it will be expanded by + * the 'ensure_full_index(...)' call in 'index_name_pos(...)'. + * - if the prefix matches an existing sparse directory entry, + * 'index_name_pos(...)' will return its index position, triggering + * the 'ensure_full_index(...)' below. + */ + if (!path_in_cone_mode_sparse_checkout(ce_prefix.buf, istate) && + index_name_pos(istate, ce_prefix.buf, ce_prefix.len) >= 0) + ensure_full_index(istate); + + strbuf_release(&ce_prefix); +} static int verify_absent(const struct cache_entry *, enum unpack_trees_error_types, @@ -1739,6 +1810,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options setup_standard_excludes(o->dir); } + if (o->prefix) + update_sparsity_for_prefix(o->prefix, o->src_index); + if (!core_apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout && !o->pl) { @@ -2436,6 +2510,37 @@ static int merged_entry(const struct cache_entry *ce, return 1; } +static int merged_sparse_dir(const struct cache_entry * const *src, int n, + struct unpack_trees_options *o) +{ + struct tree_desc t[MAX_UNPACK_TREES + 1]; + void * tree_bufs[MAX_UNPACK_TREES + 1]; + struct traverse_info info; + int i, ret; + + /* + * Create the tree traversal information for traversing into *only* the + * sparse directory. + */ + setup_traverse_info(&info, src[0]->name); + info.fn = unpack_sparse_callback; + info.data = o; + info.show_all_errors = o->show_all_errors; + info.pathspec = o->pathspec; + + /* Get the tree descriptors of the sparse directory in each of the merging trees */ + for (i = 0; i < n; i++) + tree_bufs[i] = fill_tree_descriptor(o->src_index->repo, &t[i], + src[i] && !is_null_oid(&src[i]->oid) ? &src[i]->oid : NULL); + + ret = traverse_trees(o->src_index, n, t, &info); + + for (i = 0; i < n; i++) + free(tree_bufs[i]); + + return ret; +} + static int deleted_entry(const struct cache_entry *ce, const struct cache_entry *old, struct unpack_trees_options *o) @@ -2540,16 +2645,24 @@ int threeway_merge(const struct cache_entry * const *stages, */ /* #14, #14ALT, #2ALT */ if (remote && !df_conflict_head && head_match && !remote_match) { - if (index && !same(index, remote) && !same(index, head)) - return reject_merge(index, o); + if (index && !same(index, remote) && !same(index, head)) { + if (S_ISSPARSEDIR(index->ce_mode)) + return merged_sparse_dir(stages, 4, o); + else + return reject_merge(index, o); + } return merged_entry(remote, index, o); } /* * If we have an entry in the index cache, then we want to * make sure that it matches head. */ - if (index && !same(index, head)) - return reject_merge(index, o); + if (index && !same(index, head)) { + if (S_ISSPARSEDIR(index->ce_mode)) + return merged_sparse_dir(stages, 4, o); + else + return reject_merge(index, o); + } if (head) { /* #5ALT, #15 */ @@ -2611,11 +2724,21 @@ int threeway_merge(const struct cache_entry * const *stages, } - /* Below are "no merge" cases, which require that the index be - * up-to-date to avoid the files getting overwritten with - * conflict resolution files. - */ + /* Handle "no merge" cases (see t/t1000-read-tree-m-3way.sh) */ if (index) { + /* + * If we've reached the "no merge" cases and we're merging + * a sparse directory, we may have an "edit/edit" conflict that + * can be resolved by individually merging directory contents. + */ + if (S_ISSPARSEDIR(index->ce_mode)) + return merged_sparse_dir(stages, 4, o); + + /* + * If we're not merging a sparse directory, ensure the index is + * up-to-date to avoid files getting overwritten with conflict + * resolution files + */ if (verify_uptodate(index, o)) return -1; } @@ -2706,6 +2829,14 @@ int twoway_merge(const struct cache_entry * const *src, * reject the merge instead. */ return merged_entry(newtree, current, o); + } else if (S_ISSPARSEDIR(current->ce_mode)) { + /* + * The sparse directories differ, but we don't know whether that's + * because of two different files in the directory being modified + * (can be trivially merged) or if there is a real file conflict. + * Merge the sparse directory by OID to compare file-by-file. + */ + return merged_sparse_dir(src, 3, o); } else return reject_merge(current, o); } diff --git a/upload-pack.c b/upload-pack.c index 8acc98741b..3a851b3606 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1400,13 +1400,19 @@ static int parse_want(struct packet_writer *writer, const char *line, const char *arg; if (skip_prefix(line, "want ", &arg)) { struct object_id oid; + struct commit *commit; struct object *o; if (get_oid_hex(arg, &oid)) die("git upload-pack: protocol error, " "expected to get oid, not '%s'", line); - o = parse_object(the_repository, &oid); + commit = lookup_commit_in_graph(the_repository, &oid); + if (commit) + o = &commit->object; + else + o = parse_object(the_repository, &oid); + if (!o) { packet_writer_error(writer, "upload-pack: not our ref %s", @@ -1434,7 +1440,7 @@ static int parse_want_ref(struct packet_writer *writer, const char *line, if (skip_prefix(line, "want-ref ", &refname_nons)) { struct object_id oid; struct string_list_item *item; - struct object *o; + struct object *o = NULL; struct strbuf refname = STRBUF_INIT; strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons); @@ -1448,7 +1454,15 @@ static int parse_want_ref(struct packet_writer *writer, const char *line, item = string_list_append(wanted_refs, refname_nons); item->util = oiddup(&oid); - o = parse_object_or_die(&oid, refname_nons); + if (!starts_with(refname_nons, "refs/tags/")) { + struct commit *commit = lookup_commit_in_graph(the_repository, &oid); + if (commit) + o = &commit->object; + } + + if (!o) + o = parse_object_or_die(&oid, refname_nons); + if (!(o->flags & WANTED)) { o->flags |= WANTED; add_object_array(o, NULL, want_obj); diff --git a/userdiff.c b/userdiff.c index 2d9eb99bf2..151d9a5278 100644 --- a/userdiff.c +++ b/userdiff.c @@ -180,6 +180,18 @@ PATTERNS("java", "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=" "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), +PATTERNS("kotlin", + "^[ \t]*(([a-z]+[ \t]+)*(fun|class|interface)[ \t]+.*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + /* hexadecimal and binary numbers */ + "|0[xXbB][0-9a-fA-F_]+[lLuU]*" + /* integers and floats */ + "|[0-9][0-9_]*([.][0-9_]*)?([Ee][-+]?[0-9]+)?[fFlLuU]*" + /* floating point numbers beginning with decimal point */ + "|[.][0-9][0-9_]*([Ee][-+]?[0-9]+)?[fFlLuU]?" + /* unary and binary operators */ + "|[-+*/<>%&^|=!]==?|--|\\+\\+|<<=|>>=|&&|\\|\\||->|\\.\\*|!!|[?:.][.:]"), PATTERNS("markdown", "^ {0,3}#{1,6}[ \t].*", /* -- */ diff --git a/wt-status.c b/wt-status.c index 335e723a71..d33f9272b7 100644 --- a/wt-status.c +++ b/wt-status.c @@ -651,6 +651,15 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename; rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit; rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score; + + /* + * The `recursive` option must be enabled to allow the diff to recurse + * into subdirectories of sparse directory index entries. If it is not + * enabled, a subdirectory containing file(s) with changes is reported + * as "modified", rather than the modified files themselves. + */ + rev.diffopt.flags.recursive = 1; + copy_pathspec(&rev.prune_data, &s->pathspec); run_diff_index(&rev, 1); object_array_clear(&rev.pending); @@ -1374,10 +1383,10 @@ static void show_rebase_information(struct wt_status *s, status_printf_ln(s, color, _("No commands done.")); else { status_printf_ln(s, color, - Q_("Last command done (%d command done):", - "Last commands done (%d commands done):", + Q_("Last command done (%"PRIuMAX" command done):", + "Last commands done (%"PRIuMAX" commands done):", have_done.nr), - have_done.nr); + (uintmax_t)have_done.nr); for (i = (have_done.nr > nr_lines_to_show) ? have_done.nr - nr_lines_to_show : 0; i < have_done.nr; @@ -1393,10 +1402,10 @@ static void show_rebase_information(struct wt_status *s, _("No commands remaining.")); else { status_printf_ln(s, color, - Q_("Next command to do (%d remaining command):", - "Next commands to do (%d remaining commands):", + Q_("Next command to do (%"PRIuMAX" remaining command):", + "Next commands to do (%"PRIuMAX" remaining commands):", yet_to_do.nr), - yet_to_do.nr); + (uintmax_t)yet_to_do.nr); for (i = 0; i < nr_lines_to_show && i < yet_to_do.nr; i++) status_printf_ln(s, color, " %s", yet_to_do.items[i].string); if (s->hints) |