diff options
101 files changed, 4531 insertions, 2048 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile index 06d85ad958..8fe829cc1b 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -77,6 +77,7 @@ API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technica SP_ARTICLES += $(API_DOCS) TECH_DOCS += MyFirstContribution +TECH_DOCS += MyFirstObjectWalk TECH_DOCS += SubmittingPatches TECH_DOCS += technical/hash-function-transition TECH_DOCS += technical/http-protocol diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index 5e9b808f5f..b55837e646 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -38,6 +38,26 @@ $ git clone https://github.com/git/git git $ cd git ---- +[[dependencies]] +=== Installing Dependencies + +To build Git from source, you need to have a handful of dependencies installed +on your system. For a hint of what's needed, you can take a look at +`INSTALL`, paying close attention to the section about Git's dependencies on +external programs and libraries. That document mentions a way to "test-drive" +our freshly built Git without installing; that's the method we'll be using in +this tutorial. + +Make sure that your environment has everything you need by building your brand +new clone of Git from the above step: + +---- +$ make +---- + +NOTE: The Git build is parallelizable. `-j#` is not included above but you can +use it as you prefer, here and elsewhere. + [[identify-problem]] === Identify Problem to Solve @@ -138,9 +158,6 @@ NOTE: When you are developing the Git project, it's preferred that you use the `DEVELOPER` flag; if there's some reason it doesn't work for you, you can turn it off, but it's a good idea to mention the problem to the mailing list. -NOTE: The Git build is parallelizable. `-j#` is not included above but you can -use it as you prefer, here and elsewhere. - Great, now your new command builds happily on its own. But nobody invokes it. Let's change that. @@ -534,6 +551,28 @@ you want to pass as a parameter something which would usually be interpreted as a flag.) `parse_options()` will terminate parsing when it reaches `--` and give you the rest of the options afterwards, untouched. +Now that you have a usage hint, you can teach Git how to show it in the general +command list shown by `git help git` or `git help -a`, which is generated from +`command-list.txt`. Find the line for 'git-pull' so you can add your 'git-psuh' +line above it in alphabetical order. Now, we can add some attributes about the +command which impacts where it shows up in the aforementioned help commands. The +top of `command-list.txt` shares some information about what each attribute +means; in those help pages, the commands are sorted according to these +attributes. `git psuh` is user-facing, or porcelain - so we will mark it as +"mainporcelain". For "mainporcelain" commands, the comments at the top of +`command-list.txt` indicate we can also optionally add an attribute from another +list; since `git psuh` shows some information about the user's workspace but +doesn't modify anything, let's mark it as "info". Make sure to keep your +attributes in the same style as the rest of `command-list.txt` using spaces to +align and delineate them: + +---- +git-prune-packed plumbingmanipulators +git-psuh mainporcelain info +git-pull mainporcelain remote +git-push mainporcelain remote +---- + Build again. Now, when you run with `-h`, you should see your usage printed and your command terminated before anything else interesting happens. Great! @@ -746,6 +785,14 @@ will automatically run your PRs through the CI even without the permission given but you will not be able to `/submit` your changes until someone allows you to use the tool. +NOTE: You can typically find someone who can `/allow` you on GitGitGadget by +either examining recent pull requests where someone has been granted `/allow` +(https://github.com/gitgitgadget/git/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+%22%2Fallow%22[Search: +is:pr is:open "/allow"]), in which case both the author and the person who +granted the `/allow` can now `/allow` you, or by inquiring on the +https://webchat.freenode.net/#git-devel[#git-devel] IRC channel on Freenode +linking your pull request and asking for someone to `/allow` you. + If the CI fails, you can update your changes with `git rebase -i` and push your branch again: diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt new file mode 100644 index 0000000000..4d24daeb9f --- /dev/null +++ b/Documentation/MyFirstObjectWalk.txt @@ -0,0 +1,906 @@ += My First Object Walk + +== What's an Object Walk? + +The object walk is a key concept in Git - this is the process that underpins +operations like object transfer and fsck. Beginning from a given commit, the +list of objects is found by walking parent relationships between commits (commit +X based on commit W) and containment relationships between objects (tree Y is +contained within commit X, and blob Z is located within tree Y, giving our +working tree for commit X something like `y/z.txt`). + +A related concept is the revision walk, which is focused on commit objects and +their parent relationships and does not delve into other object types. The +revision walk is used for operations like `git log`. + +=== Related Reading + +- `Documentation/user-manual.txt` under "Hacking Git" contains some coverage of + the revision walker in its various incarnations. +- `Documentation/technical/api-revision-walking.txt` +- https://eagain.net/articles/git-for-computer-scientists/[Git for Computer Scientists] + gives a good overview of the types of objects in Git and what your object + walk is really describing. + +== Setting Up + +Create a new branch from `master`. + +---- +git checkout -b revwalk origin/master +---- + +We'll put our fiddling into a new command. For fun, let's name it `git walken`. +Open up a new file `builtin/walken.c` and set up the command handler: + +---- +/* + * "git walken" + * + * Part of the "My First Object Walk" tutorial. + */ + +#include "builtin.h" + +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + trace_printf(_("cmd_walken incoming...\n")); + return 0; +} +---- + +NOTE: `trace_printf()` differs from `printf()` in that it can be turned on or +off at runtime. For the purposes of this tutorial, we will write `walken` as +though it is intended for use as a "plumbing" command: that is, a command which +is used primarily in scripts, rather than interactively by humans (a "porcelain" +command). So we will send our debug output to `trace_printf()` instead. When +running, enable trace output by setting the environment variable `GIT_TRACE`. + +Add usage text and `-h` handling, like all subcommands should consistently do +(our test suite will notice and complain if you fail to do so). + +---- +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + const char * const walken_usage[] = { + N_("git walken"), + NULL, + } + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, walken_usage, 0); + + ... +} +---- + +Also add the relevant line in `builtin.h` near `cmd_whatchanged()`: + +---- +int cmd_walken(int argc, const char **argv, const char *prefix); +---- + +Include the command in `git.c` in `commands[]` near the entry for `whatchanged`, +maintaining alphabetical ordering: + +---- +{ "walken", cmd_walken, RUN_SETUP }, +---- + +Add it to the `Makefile` near the line for `builtin/worktree.o`: + +---- +BUILTIN_OBJS += builtin/walken.o +---- + +Build and test out your command, without forgetting to ensure the `DEVELOPER` +flag is set, and with `GIT_TRACE` enabled so the debug output can be seen: + +---- +$ echo DEVELOPER=1 >>config.mak +$ make +$ GIT_TRACE=1 ./bin-wrappers/git walken +---- + +NOTE: For a more exhaustive overview of the new command process, take a look at +`Documentation/MyFirstContribution.txt`. + +NOTE: A reference implementation can be found at +https://github.com/nasamuffin/git/tree/revwalk. + +=== `struct rev_cmdline_info` + +The definition of `struct rev_cmdline_info` can be found in `revision.h`. + +This struct is contained within the `rev_info` struct and is used to reflect +parameters provided by the user over the CLI. + +`nr` represents the number of `rev_cmdline_entry` present in the array. + +`alloc` is used by the `ALLOC_GROW` macro. Check +`Documentation/technical/api-allocation-growing.txt` - this variable is used to +track the allocated size of the list. + +Per entry, we find: + +`item` is the object provided upon which to base the object walk. Items in Git +can be blobs, trees, commits, or tags. (See `Documentation/gittutorial-2.txt`.) + +`name` is the object ID (OID) of the object - a hex string you may be familiar +with from using Git to organize your source in the past. Check the tutorial +mentioned above towards the top for a discussion of where the OID can come +from. + +`whence` indicates some information about what to do with the parents of the +specified object. We'll explore this flag more later on; take a look at +`Documentation/revisions.txt` to get an idea of what could set the `whence` +value. + +`flags` are used to hint the beginning of the revision walk and are the first +block under the `#include`s in `revision.h`. The most likely ones to be set in +the `rev_cmdline_info` are `UNINTERESTING` and `BOTTOM`, but these same flags +can be used during the walk, as well. + +=== `struct rev_info` + +This one is quite a bit longer, and many fields are only used during the walk +by `revision.c` - not configuration options. Most of the configurable flags in +`struct rev_info` have a mirror in `Documentation/rev-list-options.txt`. It's a +good idea to take some time and read through that document. + +== Basic Commit Walk + +First, let's see if we can replicate the output of `git log --oneline`. We'll +refer back to the implementation frequently to discover norms when performing +an object walk of our own. + +To do so, we'll first find all the commits, in order, which preceded the current +commit. We'll extract the name and subject of the commit from each. + +Ideally, we will also be able to find out which ones are currently at the tip of +various branches. + +=== Setting Up + +Preparing for your object walk has some distinct stages. + +1. Perform default setup for this mode, and others which may be invoked. +2. Check configuration files for relevant settings. +3. Set up the `rev_info` struct. +4. Tweak the initialized `rev_info` to suit the current walk. +5. Prepare the `rev_info` for the walk. +6. Iterate over the objects, processing each one. + +==== Default Setups + +Before examining configuration files which may modify command behavior, set up +default state for switches or options your command may have. If your command +utilizes other Git components, ask them to set up their default states as well. +For instance, `git log` takes advantage of `grep` and `diff` functionality, so +its `init_log_defaults()` sets its own state (`decoration_style`) and asks +`grep` and `diff` to initialize themselves by calling each of their +initialization functions. + +For our first example within `git walken`, we don't intend to use any other +components within Git, and we don't have any configuration to do. However, we +may want to add some later, so for now, we can add an empty placeholder. Create +a new function in `builtin/walken.c`: + +---- +static void init_walken_defaults(void) +{ + /* + * We don't actually need the same components `git log` does; leave this + * empty for now. + */ +} +---- + +Make sure to add a line invoking it inside of `cmd_walken()`. + +---- +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + init_walken_defaults(); +} +---- + +==== Configuring From `.gitconfig` + +Next, we should have a look at any relevant configuration settings (i.e., +settings readable and settable from `git config`). This is done by providing a +callback to `git_config()`; within that callback, you can also invoke methods +from other components you may need that need to intercept these options. Your +callback will be invoked once per each configuration value which Git knows about +(global, local, worktree, etc.). + +Similarly to the default values, we don't have anything to do here yet +ourselves; however, we should call `git_default_config()` if we aren't calling +any other existing config callbacks. + +Add a new function to `builtin/walken.c`: + +---- +static int git_walken_config(const char *var, const char *value, void *cb) +{ + /* + * For now, we don't have any custom configuration, so fall back to + * the default config. + */ + return git_default_config(var, value, cb); +} +---- + +Make sure to invoke `git_config()` with it in your `cmd_walken()`: + +---- +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + ... + + git_config(git_walken_config, NULL); + + ... +} +---- + +==== Setting Up `rev_info` + +Now that we've gathered external configuration and options, it's time to +initialize the `rev_info` object which we will use to perform the walk. This is +typically done by calling `repo_init_revisions()` with the repository you intend +to target, as well as the `prefix` argument of `cmd_walken` and your `rev_info` +struct. + +Add the `struct rev_info` and the `repo_init_revisions()` call: +---- +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + /* This can go wherever you like in your declarations.*/ + struct rev_info rev; + ... + + /* This should go after the git_config() call. */ + repo_init_revisions(the_repository, &rev, prefix); + + ... +} +---- + +==== Tweaking `rev_info` For the Walk + +We're getting close, but we're still not quite ready to go. Now that `rev` is +initialized, we can modify it to fit our needs. This is usually done within a +helper for clarity, so let's add one: + +---- +static void final_rev_info_setup(struct rev_info *rev) +{ + /* + * We want to mimic the appearance of `git log --oneline`, so let's + * force oneline format. + */ + get_commit_format("oneline", rev); + + /* Start our object walk at HEAD. */ + add_head_to_pending(rev); +} +---- + +[NOTE] +==== +Instead of using the shorthand `add_head_to_pending()`, you could do +something like this: +---- + struct setup_revision_opt opt; + + memset(&opt, 0, sizeof(opt)); + opt.def = "HEAD"; + opt.revarg_opt = REVARG_COMMITTISH; + setup_revisions(argc, argv, rev, &opt); +---- +Using a `setup_revision_opt` gives you finer control over your walk's starting +point. +==== + +Then let's invoke `final_rev_info_setup()` after the call to +`repo_init_revisions()`: + +---- +int cmd_walken(int argc, const char **argv, const char *prefix) +{ + ... + + final_rev_info_setup(&rev); + + ... +} +---- + +Later, we may wish to add more arguments to `final_rev_info_setup()`. But for +now, this is all we need. + +==== Preparing `rev_info` For the Walk + +Now that `rev` is all initialized and configured, we've got one more setup step +before we get rolling. We can do this in a helper, which will both prepare the +`rev_info` for the walk, and perform the walk itself. Let's start the helper +with the call to `prepare_revision_walk()`, which can return an error without +dying on its own: + +---- +static void walken_commit_walk(struct rev_info *rev) +{ + if (prepare_revision_walk(rev)) + die(_("revision walk setup failed")); +} +---- + +NOTE: `die()` prints to `stderr` and exits the program. Since it will print to +`stderr` it's likely to be seen by a human, so we will localize it. + +==== Performing the Walk! + +Finally! We are ready to begin the walk itself. Now we can see that `rev_info` +can also be used as an iterator; we move to the next item in the walk by using +`get_revision()` repeatedly. Add the listed variable declarations at the top and +the walk loop below the `prepare_revision_walk()` call within your +`walken_commit_walk()`: + +---- +static void walken_commit_walk(struct rev_info *rev) +{ + struct commit *commit; + struct strbuf prettybuf = STRBUF_INIT; + + ... + + while ((commit = get_revision(rev))) { + if (!commit) + continue; + + strbuf_reset(&prettybuf); + pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf); + puts(prettybuf.buf); + } + strbuf_release(&prettybuf); +} +---- + +NOTE: `puts()` prints a `char*` to `stdout`. Since this is the part of the +command we expect to be machine-parsed, we're sending it directly to stdout. + +Give it a shot. + +---- +$ make +$ ./bin-wrappers/git walken +---- + +You should see all of the subject lines of all the commits in +your tree's history, in order, ending with the initial commit, "Initial revision +of "git", the information manager from hell". Congratulations! You've written +your first revision walk. You can play with printing some additional fields +from each commit if you're curious; have a look at the functions available in +`commit.h`. + +=== Adding a Filter + +Next, let's try to filter the commits we see based on their author. This is +equivalent to running `git log --author=<pattern>`. We can add a filter by +modifying `rev_info.grep_filter`, which is a `struct grep_opt`. + +First some setup. Add `init_grep_defaults()` to `init_walken_defaults()` and add +`grep_config()` to `git_walken_config()`: + +---- +static void init_walken_defaults(void) +{ + init_grep_defaults(the_repository); +} + +... + +static int git_walken_config(const char *var, const char *value, void *cb) +{ + grep_config(var, value, cb); + return git_default_config(var, value, cb); +} +---- + +Next, we can modify the `grep_filter`. This is done with convenience functions +found in `grep.h`. For fun, we're filtering to only commits from folks using a +`gmail.com` email address - a not-very-precise guess at who may be working on +Git as a hobby. Since we're checking the author, which is a specific line in the +header, we'll use the `append_header_grep_pattern()` helper. We can use +the `enum grep_header_field` to indicate which part of the commit header we want +to search. + +In `final_rev_info_setup()`, add your filter line: + +---- +static void final_rev_info_setup(int argc, const char **argv, + const char *prefix, struct rev_info *rev) +{ + ... + + append_header_grep_pattern(&rev->grep_filter, GREP_HEADER_AUTHOR, + "gmail"); + compile_grep_patterns(&rev->grep_filter); + + ... +} +---- + +`append_header_grep_pattern()` adds your new "gmail" pattern to `rev_info`, but +it won't work unless we compile it with `compile_grep_patterns()`. + +NOTE: If you are using `setup_revisions()` (for example, if you are passing a +`setup_revision_opt` instead of using `add_head_to_pending()`), you don't need +to call `compile_grep_patterns()` because `setup_revisions()` calls it for you. + +NOTE: We could add the same filter via the `append_grep_pattern()` helper if we +wanted to, but `append_header_grep_pattern()` adds the `enum grep_context` and +`enum grep_pat_token` for us. + +=== Changing the Order + +There are a few ways that we can change the order of the commits during a +revision walk. Firstly, we can use the `enum rev_sort_order` to choose from some +typical orderings. + +`topo_order` is the same as `git log --topo-order`: we avoid showing a parent +before all of its children have been shown, and we avoid mixing commits which +are in different lines of history. (`git help log`'s section on `--topo-order` +has a very nice diagram to illustrate this.) + +Let's see what happens when we run with `REV_SORT_BY_COMMIT_DATE` as opposed to +`REV_SORT_BY_AUTHOR_DATE`. Add the following: + +---- +static void final_rev_info_setup(int argc, const char **argv, + const char *prefix, struct rev_info *rev) +{ + ... + + rev->topo_order = 1; + rev->sort_order = REV_SORT_BY_COMMIT_DATE; + + ... +} +---- + +Let's output this into a file so we can easily diff it with the walk sorted by +author date. + +---- +$ make +$ ./bin-wrappers/git walken > commit-date.txt +---- + +Then, let's sort by author date and run it again. + +---- +static void final_rev_info_setup(int argc, const char **argv, + const char *prefix, struct rev_info *rev) +{ + ... + + rev->topo_order = 1; + rev->sort_order = REV_SORT_BY_AUTHOR_DATE; + + ... +} +---- + +---- +$ make +$ ./bin-wrappers/git walken > author-date.txt +---- + +Finally, compare the two. This is a little less helpful without object names or +dates, but hopefully we get the idea. + +---- +$ diff -u commit-date.txt author-date.txt +---- + +This display indicates that commits can be reordered after they're written, for +example with `git rebase`. + +Let's try one more reordering of commits. `rev_info` exposes a `reverse` flag. +Set that flag somewhere inside of `final_rev_info_setup()`: + +---- +static void final_rev_info_setup(int argc, const char **argv, const char *prefix, + struct rev_info *rev) +{ + ... + + rev->reverse = 1; + + ... +} +---- + +Run your walk again and note the difference in order. (If you remove the grep +pattern, you should see the last commit this call gives you as your current +HEAD.) + +== Basic Object Walk + +So far we've been walking only commits. But Git has more types of objects than +that! Let's see if we can walk _all_ objects, and find out some information +about each one. + +We can base our work on an example. `git pack-objects` prepares all kinds of +objects for packing into a bitmap or packfile. The work we are interested in +resides in `builtins/pack-objects.c:get_object_list()`; examination of that +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. + +- `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. +- `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`. +- `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. + +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 +tracking variables: + +---- +static int commit_count; +static int tag_count; +static int blob_count; +static int tree_count; +---- + +Commits are handled by a different callback than other objects; let's do that +one first: + +---- +static void walken_show_commit(struct commit *cmt, void *buf) +{ + commit_count++; +} +---- + +The `cmt` argument is fairly self-explanatory. But it's worth mentioning that +the `buf` argument is actually the context buffer that we can provide to the +traversal calls - `show_data`, which we mentioned a moment ago. + +Since we have the `struct commit` object, we can look at all the same parts that +we looked at in our earlier commit-only walk. For the sake of this tutorial, +though, we'll just increment the commit counter and move on. + +The callback for non-commits is a little different, as we'll need to check +which kind of object we're dealing with: + +---- +static void walken_show_object(struct object *obj, const char *str, void *buf) +{ + switch (obj->type) { + case OBJ_TREE: + tree_count++; + break; + case OBJ_BLOB: + blob_count++; + break; + case OBJ_TAG: + tag_count++; + break; + case OBJ_COMMIT: + BUG("unexpected commit object in walken_show_object\n"); + default: + BUG("unexpected object type %s in walken_show_object\n", + type_name(obj->type)); + } +} +---- + +Again, `obj` is fairly self-explanatory, and we can guess that `buf` is the same +context pointer that `walken_show_commit()` receives: the `show_data` argument +to `traverse_commit_list()` and `traverse_commit_list_filtered()`. Finally, +`str` contains the name of the object, which ends up being something like +`foo.txt` (blob), `bar/baz` (tree), or `v1.2.3` (tag). + +To help assure us that we aren't double-counting commits, we'll include some +complaining if a commit object is routed through our non-commit callback; we'll +also complain if we see an invalid object type. Since those two cases should be +unreachable, and would only change in the event of a semantic change to the Git +codebase, we complain by using `BUG()` - which is a signal to a developer that +the change they made caused unintended consequences, and the rest of the +codebase needs to be updated to understand that change. `BUG()` is not intended +to be seen by the public, so it is not localized. + +Our main object walk implementation is substantially different from our commit +walk implementation, so let's make a new function to perform the object walk. We +can perform setup which is applicable to all objects here, too, to keep separate +from setup which is applicable to commit-only walks. + +We'll start by enabling all types of objects in the `struct rev_info`. We'll +also turn on `tree_blobs_in_commit_order`, which means that we will walk a +commit's tree and everything it points to immediately after we find each commit, +as opposed to waiting for the end and walking through all trees after the commit +history has been discovered. With the appropriate settings configured, we are +ready to call `prepare_revision_walk()`. + +---- +static void walken_object_walk(struct rev_info *rev) +{ + rev->tree_objects = 1; + rev->blob_objects = 1; + rev->tag_objects = 1; + rev->tree_blobs_in_commit_order = 1; + + if (prepare_revision_walk(rev)) + die(_("revision walk setup failed")); + + commit_count = 0; + tag_count = 0; + blob_count = 0; + tree_count = 0; +---- + +Let's start by calling just the unfiltered walk and reporting our counts. +Complete your implementation of `walken_object_walk()`: + +---- + traverse_commit_list(rev, walken_show_commit, walken_show_object, NULL); + + printf("commits %d\nblobs %d\ntags %d\ntrees %d\n", commit_count, + blob_count, tag_count, tree_count); +} +---- + +NOTE: This output is intended to be machine-parsed. Therefore, we are not +sending it to `trace_printf()`, and we are not localizing it - we need scripts +to be able to count on the formatting to be exactly the way it is shown here. +If we were intending this output to be read by humans, we would need to localize +it with `_()`. + +Finally, we'll ask `cmd_walken()` to use the object walk instead. Discussing +command line options is out of scope for this tutorial, so we'll just hardcode +a branch we can change at compile time. Where you call `final_rev_info_setup()` +and `walken_commit_walk()`, instead branch like so: + +---- + if (1) { + add_head_to_pending(&rev); + walken_object_walk(&rev); + } else { + final_rev_info_setup(argc, argv, prefix, &rev); + walken_commit_walk(&rev); + } +---- + +NOTE: For simplicity, we've avoided all the filters and sorts we applied in +`final_rev_info_setup()` and simply added `HEAD` to our pending queue. If you +want, you can certainly use the filters we added before by moving +`final_rev_info_setup()` out of the conditional and removing the call to +`add_head_to_pending()`. + +Now we can try to run our command! It should take noticeably longer than the +commit walk, but an examination of the output will give you an idea why. Your +output should look similar to this example, but with different counts: + +---- +Object walk completed. Found 55733 commits, 100274 blobs, 0 tags, and 104210 trees. +---- + +This makes sense. We have more trees than commits because the Git project has +lots of subdirectories which can change, plus at least one tree per commit. We +have no tags because we started on a commit (`HEAD`) and while tags can point to +commits, commits can't point to tags. + +NOTE: You will have different counts when you run this yourself! The number of +objects grows along with the Git project. + +=== Adding a Filter + +There are a handful of filters that we can apply to the object walk laid out in +`Documentation/rev-list-options.txt`. These filters are typically useful for +operations such as creating packfiles or performing a partial clone. They are +defined in `list-objects-filter-options.h`. For the purposes of this tutorial we +will use the "tree:1" filter, which causes the walk to omit all trees and blobs +which are not directly referenced by commits reachable from the commit in +`pending` when the walk begins. (`pending` is the list of objects which need to +be traversed during a walk; you can imagine a breadth-first tree traversal to +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 = {}; + + ... +---- + +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 +`traverse_commit_list()` with the following, which will remind us which kind of +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); + } +---- + +`struct list_objects_filter_options` 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()`. + +With the filter spec "tree:1", we are expecting to see _only_ the root tree for +each commit; therefore, the tree object count should be less than or equal to +the number of commits. (For an example of why that's true: `git commit --revert` +points to the same tree object as its grandparent.) + +=== Counting Omitted Objects + +We also have the capability to enumerate all objects which were omitted by a +filter, like with `git log --filter=<spec> --filter-print-omitted`. Asking +`traverse_commit_list_filtered()` to populate the `omitted` list means that our +object walk does not perform any better than an unfiltered object walk; all +reachable objects are walked in order to populate the list. + +First, add the `struct oidset` and related items we will use to iterate it: + +---- +static void walken_object_walk( + ... + + struct oidset omitted; + struct oidset_iter oit; + struct object_id *oid = NULL; + int omitted_count = 0; + oidset_init(&omitted, 0); + + ... +---- + +Modify the call to `traverse_commit_list_filtered()` to include your `omitted` +object: + +---- + ... + + traverse_commit_list_filtered(&filter_options, rev, + walken_show_commit, walken_show_object, NULL, &omitted); + + ... +---- + +Then, after your traversal, the `oidset` traversal is pretty straightforward. +Count all the objects within and modify the print statement: + +---- + /* Count the omitted objects. */ + oidset_iter_init(&omitted, &oit); + + while ((oid = oidset_iter_next(&oit))) + omitted_count++; + + printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n", + commit_count, blob_count, tag_count, tree_count, omitted_count); +---- + +By running your walk with and without the filter, you should find that the total +object count in each case is identical. You can also time each invocation of +the `walken` subcommand, with and without `omitted` being passed in, to confirm +to yourself the runtime impact of tracking all omitted objects. + +=== Changing the Order + +Finally, let's demonstrate that you can also reorder walks of all objects, not +just walks of commits. First, we'll make our handlers chattier - modify +`walken_show_commit()` and `walken_show_object()` to print the object as they +go: + +---- +static void walken_show_commit(struct commit *cmt, void *buf) +{ + trace_printf("commit: %s\n", oid_to_hex(&cmt->object.oid)); + commit_count++; +} + +static void walken_show_object(struct object *obj, const char *str, void *buf) +{ + trace_printf("%s: %s\n", type_name(obj->type), oid_to_hex(&obj->oid)); + + ... +} +---- + +NOTE: Since we will be examining this output directly as humans, we'll use +`trace_printf()` here. Additionally, since this change introduces a significant +number of printed lines, using `trace_printf()` will allow us to easily silence +those lines without having to recompile. + +(Leave the counter increment logic in place.) + +With only that change, run again (but save yourself some scrollback): + +---- +$ GIT_TRACE=1 ./bin-wrappers/git walken | head -n 10 +---- + +Take a look at the top commit with `git show` and the object ID you printed; it +should be the same as the output of `git show HEAD`. + +Next, let's change a setting on our `struct rev_info` within +`walken_object_walk()`. Find where you're changing the other settings on `rev`, +such as `rev->tree_objects` and `rev->tree_blobs_in_commit_order`, and add the +`reverse` setting at the bottom: + +---- + ... + + rev->tree_objects = 1; + rev->blob_objects = 1; + rev->tag_objects = 1; + rev->tree_blobs_in_commit_order = 1; + rev->reverse = 1; + + ... +---- + +Now, run again, but this time, let's grab the last handful of objects instead +of the first handful: + +---- +$ make +$ GIT_TRACE=1 ./bin-wrappers git walken | tail -n 10 +---- + +The last commit object given should have the same OID as the one we saw at the +top before, and running `git show <oid>` with that OID should give you again +the same results as `git show HEAD`. Furthermore, if you run and examine the +first ten lines again (with `head` instead of `tail` like we did before applying +the `reverse` setting), you should see that now the first commit printed is the +initial commit, `e83c5163`. + +== Wrapping Up + +Let's review. In this tutorial, we: + +- Built a commit walk from the ground up +- Enabled a grep filter for that commit walk +- Changed the sort order of that filtered commit walk +- Built an object walk (tags, commits, trees, and blobs) from the ground up +- Learned how to add a filter-spec to an object walk +- Changed the display order of the filtered object walk diff --git a/Documentation/RelNotes/2.25.0.txt b/Documentation/RelNotes/2.25.0.txt new file mode 100644 index 0000000000..b61b69f20b --- /dev/null +++ b/Documentation/RelNotes/2.25.0.txt @@ -0,0 +1,68 @@ +Git 2.25 Release Notes +====================== + +Updates since v2.24 +------------------- + +Backward compatibility notes + + +UI, Workflows & Features + + * A tutorial on object enumeration has been added. + + * The branch description ("git branch --edit-description") has been + used to fill the body of the cover letters by the format-patch + command; this has been enhanced so that the subject can also be + filled. + + * "git rebase --preserve-merges" has been marked as deprecated; this + release stops advertising it in the "git rebase -h" output. + + * The code to generate multi-pack index learned to show (or not to + show) progress indicators. + + * "git apply --3way" learned to honor merge.conflictStyle + configuration variable, like merges would. + + * The custom format for "git log --format=<format>" learned the l/L + placeholder that is similar to e/E that fills in the e-mail + address, but only the local part on the left side of '@'. + + +Performance, Internal Implementation, Development Support etc. + + * Debugging support for lazy cloning has been a bit improved. + + * Move the definition of a set of bitmask constants from 0ctal + literal to (1U<<count) notation. + + * Test updates to prepare for SHA-2 transition continues. + + +Fixes since v2.24 +----------------- + + * "rebase -i" ceased to run post-commit hook by mistake in an earlier + update, which has been corrected. + + * "git notes copy $original" ought to copy the notes attached to the + original object to HEAD, but a mistaken tightening to command line + parameter validation made earlier disabled that feature by mistake. + + * When all files from some subdirectory were renamed to the root + directory, the directory rename heuristics would fail to detect that + as a rename/merge of the subdirectory to the root directory, which has + been corrected. + + * Code clean-up and a bugfix in the logic used to tell worktree local + and repository global refs apart. + (merge f45f88b2e4 sg/dir-trie-fixes later to maint). + + * "git stash save" in a working tree that is sparsely checked out + mistakenly removed paths that are outside the area of interest. + (merge 4a58c3d7f7 js/update-index-ignore-removal-for-skip-worktree later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge 80736d7c5e jc/am-show-current-patch-docfix later to maint). + (merge 8b656572ca sg/commit-graph-usage-fix later to maint). diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt index 40cad9278f..513fcd88d5 100644 --- a/Documentation/config/format.txt +++ b/Documentation/config/format.txt @@ -36,6 +36,12 @@ format.subjectPrefix:: The default for format-patch is to output files with the '[PATCH]' subject prefix. Use this variable to change that prefix. +format.coverFromDescription:: + The default mode for format-patch to determine which parts of + the cover letter will be populated using the branch's + description. See the `--cover-from-description` option in + linkgit:git-format-patch[1]. + format.signature:: The default for format-patch is to output a signature containing the Git version number. Use this variable to change that default. diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index fc3b993c33..fc5750b3b8 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -177,7 +177,7 @@ default. You can use `--no-utf8` to override this. untouched. --show-current-patch:: - Show the patch being applied when "git am" is stopped because + Show the entire e-mail message "git am" has stopped at, because of conflicts. DISCUSSION diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 4b45d837a7..7586c5a843 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -413,7 +413,7 @@ $ cat ~/test.sh # tweak the working tree by merging the hot-fix branch # and then attempt a build -if git merge --no-commit hot-fix && +if git merge --no-commit --no-ff hot-fix && make then # run project specific test and report its status diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 72179d993c..37781cf175 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -36,7 +36,7 @@ two blob objects, or changes between two files on disk. running the command in a working tree controlled by Git and at least one of the paths points outside the working tree, or when running the command outside a working tree - controlled by Git. + controlled by Git. This form implies `--exit-code`. 'git diff' [<options>] --cached [<commit>] [--] [<path>...]:: diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 2035d4d5d5..00bdf9b125 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -19,6 +19,7 @@ SYNOPSIS [--start-number <n>] [--numbered-files] [--in-reply-to=<message id>] [--suffix=.<sfx>] [--ignore-if-in-upstream] + [--cover-from-description=<mode>] [--rfc] [--subject-prefix=<subject prefix>] [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] @@ -172,6 +173,26 @@ will want to ensure that threading is disabled for `git send-email`. patches being generated, and any patch that matches is ignored. +--cover-from-description=<mode>:: + Controls which parts of the cover letter will be automatically + populated using the branch's description. ++ +If `<mode>` is `message` or `default`, the cover letter subject will be +populated with placeholder text. The body of the cover letter will be +populated with the branch's description. This is the default mode when +no configuration nor command line option is specified. ++ +If `<mode>` is `subject`, the first paragraph of the branch description will +populate the cover letter subject. The remainder of the description will +populate the body of the cover letter. ++ +If `<mode>` is `auto`, if the first paragraph of the branch description +is greater than 100 bytes, then the mode will be `message`, otherwise +`subject` will be used. ++ +If `<mode>` is `none`, both the cover letter subject and body will be +populated with placeholder text. + --subject-prefix=<subject prefix>:: Instead of the standard '[PATCH]' prefix in the subject line, instead use '[<subject prefix>]'. This @@ -348,6 +369,7 @@ with configuration variables. signOff = true outputDirectory = <directory> coverLetter = auto + coverFromDescription = auto ------------ diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index 233b2b7862..642d9ac5b7 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -9,7 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes SYNOPSIS -------- [verse] -'git multi-pack-index' [--object-dir=<dir>] <subcommand> +'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress] <subcommand> DESCRIPTION ----------- @@ -23,6 +23,10 @@ OPTIONS `<dir>/packs/multi-pack-index` for the current MIDX file, and `<dir>/packs` for the pack-files to index. +--[no-]progress:: + Turn progress on/off explicitly. If neither is specified, progress is + shown if standard error is connected to a terminal. + The following subcommands are available: write:: diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index f56a5a9197..ced2e8280e 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git notes' [list [<object>]] 'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>] -'git notes' copy [-f] ( --stdin | <from-object> <to-object> ) +'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] ) 'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>] 'git notes' edit [--allow-empty] [<object>] 'git notes' show [<object>] @@ -68,8 +68,8 @@ add:: subcommand). copy:: - Copy the notes for the first object onto the second object. - Abort if the second object already has notes, or if the first + Copy the notes for the first object onto the second object (defaults to + HEAD). Abort if the second object already has notes, or if the first object has none (use -f to overwrite existing notes to the second object). This subcommand is equivalent to: `git notes add [-f] -C $(git notes list <from-object>) <to-object>` diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index e72d332b83..9985477efe 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -274,6 +274,13 @@ print a message to stderr and exit with nonzero status. Show the path to the shared index file in split index mode, or empty if not in split-index mode. +--show-object-format[=(storage|input|output)]:: + Show the object format (hash algorithm) used for the repository + for storage inside the `.git` directory, input, or output. For + input, multiple algorithms may be printed, space-separated. + If not specified, the default is "storage". + + Other Options ~~~~~~~~~~~~~ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 1c4d146a41..08393445e7 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -16,6 +16,7 @@ SYNOPSIS [--chmod=(+|-)x] [--[no-]assume-unchanged] [--[no-]skip-worktree] + [--[no-]ignore-skip-worktree-entries] [--[no-]fsmonitor-valid] [--ignore-submodules] [--[no-]split-index] @@ -113,6 +114,11 @@ you will need to handle the situation manually. set and unset the "skip-worktree" bit for the paths. See section "Skip-worktree bit" below for more information. + +--[no-]ignore-skip-worktree-entries:: + Do not remove skip-worktree (AKA "index-only") entries even when + the `--remove` option was specified. + --[no-]fsmonitor-valid:: When one of these flags is specified, the object name recorded for the paths are not updated. Instead, these options diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index d6388f10bb..1a2ef4c150 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -96,9 +96,9 @@ refs:: directory. The 'git prune' command knows to preserve objects reachable from refs found in this directory and its subdirectories. - This directory is ignored (except refs/bisect and - refs/worktree) if $GIT_COMMON_DIR is set and - "$GIT_COMMON_DIR/refs" will be used instead. + This directory is ignored (except refs/bisect, + refs/rewritten and refs/worktree) if $GIT_COMMON_DIR is + set and "$GIT_COMMON_DIR/refs" will be used instead. refs/heads/`name`:: records tip-of-the-tree commit objects of branch `name` @@ -240,8 +240,8 @@ remotes:: logs:: Records of changes made to refs are stored in this directory. See linkgit:git-update-ref[1] for more information. This - directory is ignored if $GIT_COMMON_DIR is set and - "$GIT_COMMON_DIR/logs" will be used instead. + directory is ignored (except logs/HEAD) if $GIT_COMMON_DIR is + set and "$GIT_COMMON_DIR/logs" will be used instead. logs/refs/heads/`name`:: Records all changes made to the branch tip named `name`. diff --git a/Documentation/gitsubmodules.txt b/Documentation/gitsubmodules.txt index 0a890205b8..c476f891b5 100644 --- a/Documentation/gitsubmodules.txt +++ b/Documentation/gitsubmodules.txt @@ -3,7 +3,7 @@ gitsubmodules(7) NAME ---- -gitsubmodules - mounting one repository inside another +gitsubmodules - Mounting one repository inside another SYNOPSIS -------- diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index b87e2e83e6..31c6e8d2b8 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -163,6 +163,9 @@ The placeholders are: '%ae':: author email '%aE':: author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%al':: author email local-part (the part before the '@' sign) +'%aL':: author local-part (see '%al') respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) '%ad':: author date (format respects --date= option) '%aD':: author date, RFC2822 style '%ar':: author date, relative @@ -175,6 +178,9 @@ The placeholders are: '%ce':: committer email '%cE':: committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%cl':: author email local-part (the part before the '@' sign) +'%cL':: author local-part (see '%cl') respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) '%cd':: committer date (format respects --date= option) '%cD':: committer date, RFC2822 style '%cr':: committer date, relative diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt deleted file mode 100644 index 7d20716c32..0000000000 --- a/Documentation/technical/api-config.txt +++ /dev/null @@ -1,319 +0,0 @@ -config API -========== - -The config API gives callers a way to access Git configuration files -(and files which have the same syntax). See linkgit:git-config[1] for a -discussion of the config file syntax. - -General Usage -------------- - -Config files are parsed linearly, and each variable found is passed to a -caller-provided callback function. The callback function is responsible -for any actions to be taken on the config option, and is free to ignore -some options. It is not uncommon for the configuration to be parsed -several times during the run of a Git program, with different callbacks -picking out different variables useful to themselves. - -A config callback function takes three parameters: - -- the name of the parsed variable. This is in canonical "flat" form: the - section, subsection, and variable segments will be separated by dots, - and the section and variable segments will be all lowercase. E.g., - `core.ignorecase`, `diff.SomeType.textconv`. - -- the value of the found variable, as a string. If the variable had no - value specified, the value will be NULL (typically this means it - should be interpreted as boolean true). - -- a void pointer passed in by the caller of the config API; this can - contain callback-specific data - -A config callback should return 0 for success, or -1 if the variable -could not be parsed properly. - -Basic Config Querying ---------------------- - -Most programs will simply want to look up variables in all config files -that Git knows about, using the normal precedence rules. To do this, -call `git_config` with a callback function and void data pointer. - -`git_config` will read all config sources in order of increasing -priority. Thus a callback should typically overwrite previously-seen -entries with new ones (e.g., if both the user-wide `~/.gitconfig` and -repo-specific `.git/config` contain `color.ui`, the config machinery -will first feed the user-wide one to the callback, and then the -repo-specific one; by overwriting, the higher-priority repo-specific -value is left at the end). - -The `config_with_options` function lets the caller examine config -while adjusting some of the default behavior of `git_config`. It should -almost never be used by "regular" Git code that is looking up -configuration variables. It is intended for advanced callers like -`git-config`, which are intentionally tweaking the normal config-lookup -process. It takes two extra parameters: - -`config_source`:: -If this parameter is non-NULL, it specifies the source to parse for -configuration, rather than looking in the usual files. See `struct -git_config_source` in `config.h` for details. Regular `git_config` defaults -to `NULL`. - -`opts`:: -Specify options to adjust the behavior of parsing config files. See `struct -config_options` in `config.h` for details. As an example: regular `git_config` -sets `opts.respect_includes` to `1` by default. - -Reading Specific Files ----------------------- - -To read a specific file in git-config format, use -`git_config_from_file`. This takes the same callback and data parameters -as `git_config`. - -Querying For Specific Variables -------------------------------- - -For programs wanting to query for specific variables in a non-callback -manner, the config API provides two functions `git_config_get_value` -and `git_config_get_value_multi`. They both read values from an internal -cache generated previously from reading the config files. - -`int git_config_get_value(const char *key, const char **value)`:: - - Finds the highest-priority value for the configuration variable `key`, - stores the pointer to it in `value` and returns 0. When the - configuration variable `key` is not found, returns 1 without touching - `value`. The caller should not free or modify `value`, as it is owned - by the cache. - -`const struct string_list *git_config_get_value_multi(const char *key)`:: - - Finds and returns the value list, sorted in order of increasing priority - for the configuration variable `key`. When the configuration variable - `key` is not found, returns NULL. The caller should not free or modify - the returned pointer, as it is owned by the cache. - -`void git_config_clear(void)`:: - - Resets and invalidates the config cache. - -The config API also provides type specific API functions which do conversion -as well as retrieval for the queried variable, including: - -`int git_config_get_int(const char *key, int *dest)`:: - - Finds and parses the value to an integer for the configuration variable - `key`. Dies on error; otherwise, stores the value of the parsed integer in - `dest` and returns 0. When the configuration variable `key` is not found, - returns 1 without touching `dest`. - -`int git_config_get_ulong(const char *key, unsigned long *dest)`:: - - Similar to `git_config_get_int` but for unsigned longs. - -`int git_config_get_bool(const char *key, int *dest)`:: - - Finds and parses the value into a boolean value, for the configuration - variable `key` respecting keywords like "true" and "false". Integer - values are converted into true/false values (when they are non-zero or - zero, respectively). Other values cause a die(). If parsing is successful, - stores the value of the parsed result in `dest` and returns 0. When the - configuration variable `key` is not found, returns 1 without touching - `dest`. - -`int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)`:: - - Similar to `git_config_get_bool`, except that integers are copied as-is, - and `is_bool` flag is unset. - -`int git_config_get_maybe_bool(const char *key, int *dest)`:: - - Similar to `git_config_get_bool`, except that it returns -1 on error - rather than dying. - -`int git_config_get_string_const(const char *key, const char **dest)`:: - - Allocates and copies the retrieved string into the `dest` parameter for - the configuration variable `key`; if NULL string is given, prints an - error message and returns -1. When the configuration variable `key` is - not found, returns 1 without touching `dest`. - -`int git_config_get_string(const char *key, char **dest)`:: - - Similar to `git_config_get_string_const`, except that retrieved value - copied into the `dest` parameter is a mutable string. - -`int git_config_get_pathname(const char *key, const char **dest)`:: - - Similar to `git_config_get_string`, but expands `~` or `~user` into - the user's home directory when found at the beginning of the path. - -`git_die_config(const char *key, const char *err, ...)`:: - - First prints the error message specified by the caller in `err` and then - dies printing the line number and the file name of the highest priority - value for the configuration variable `key`. - -`void git_die_config_linenr(const char *key, const char *filename, int linenr)`:: - - Helper function which formats the die error message according to the - parameters entered. Used by `git_die_config()`. It can be used by callers - handling `git_config_get_value_multi()` to print the correct error message - for the desired value. - -See test-config.c for usage examples. - -Value Parsing Helpers ---------------------- - -To aid in parsing string values, the config API provides callbacks with -a number of helper functions, including: - -`git_config_int`:: -Parse the string to an integer, including unit factors. Dies on error; -otherwise, returns the parsed result. - -`git_config_ulong`:: -Identical to `git_config_int`, but for unsigned longs. - -`git_config_bool`:: -Parse a string into a boolean value, respecting keywords like "true" and -"false". Integer values are converted into true/false values (when they -are non-zero or zero, respectively). Other values cause a die(). If -parsing is successful, the return value is the result. - -`git_config_bool_or_int`:: -Same as `git_config_bool`, except that integers are returned as-is, and -an `is_bool` flag is unset. - -`git_parse_maybe_bool`:: -Same as `git_config_bool`, except that it returns -1 on error rather -than dying. - -`git_config_string`:: -Allocates and copies the value string into the `dest` parameter; if no -string is given, prints an error message and returns -1. - -`git_config_pathname`:: -Similar to `git_config_string`, but expands `~` or `~user` into the -user's home directory when found at the beginning of the path. - -Include Directives ------------------- - -By default, the config parser does not respect include directives. -However, a caller can use the special `git_config_include` wrapper -callback to support them. To do so, you simply wrap your "real" callback -function and data pointer in a `struct config_include_data`, and pass -the wrapper to the regular config-reading functions. For example: - -------------------------------------------- -int read_file_with_include(const char *file, config_fn_t fn, void *data) -{ - struct config_include_data inc = CONFIG_INCLUDE_INIT; - inc.fn = fn; - inc.data = data; - return git_config_from_file(git_config_include, file, &inc); -} -------------------------------------------- - -`git_config` respects includes automatically. The lower-level -`git_config_from_file` does not. - -Custom Configsets ------------------ - -A `config_set` can be used to construct an in-memory cache for -config-like files that the caller specifies (i.e., files like `.gitmodules`, -`~/.gitconfig` etc.). For example, - ----------------------------------------- -struct config_set gm_config; -git_configset_init(&gm_config); -int b; -/* we add config files to the config_set */ -git_configset_add_file(&gm_config, ".gitmodules"); -git_configset_add_file(&gm_config, ".gitmodules_alt"); - -if (!git_configset_get_bool(gm_config, "submodule.frotz.ignore", &b)) { - /* hack hack hack */ -} - -/* when we are done with the configset */ -git_configset_clear(&gm_config); ----------------------------------------- - -Configset API provides functions for the above mentioned work flow, including: - -`void git_configset_init(struct config_set *cs)`:: - - Initializes the config_set `cs`. - -`int git_configset_add_file(struct config_set *cs, const char *filename)`:: - - Parses the file and adds the variable-value pairs to the `config_set`, - dies if there is an error in parsing the file. Returns 0 on success, or - -1 if the file does not exist or is inaccessible. The user has to decide - if he wants to free the incomplete configset or continue using it when - the function returns -1. - -`int git_configset_get_value(struct config_set *cs, const char *key, const char **value)`:: - - Finds the highest-priority value for the configuration variable `key` - and config set `cs`, stores the pointer to it in `value` and returns 0. - When the configuration variable `key` is not found, returns 1 without - touching `value`. The caller should not free or modify `value`, as it - is owned by the cache. - -`const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)`:: - - Finds and returns the value list, sorted in order of increasing priority - for the configuration variable `key` and config set `cs`. When the - configuration variable `key` is not found, returns NULL. The caller - should not free or modify the returned pointer, as it is owned by the cache. - -`void git_configset_clear(struct config_set *cs)`:: - - Clears `config_set` structure, removes all saved variable-value pairs. - -In addition to above functions, the `config_set` API provides type specific -functions in the vein of `git_config_get_int` and family but with an extra -parameter, pointer to struct `config_set`. -They all behave similarly to the `git_config_get*()` family described in -"Querying For Specific Variables" above. - -Writing Config Files --------------------- - -Git gives multiple entry points in the Config API to write config values to -files namely `git_config_set_in_file` and `git_config_set`, which write to -a specific config file or to `.git/config` respectively. They both take a -key/value pair as parameter. -In the end they both call `git_config_set_multivar_in_file` which takes four -parameters: - -- the name of the file, as a string, to which key/value pairs will be written. - -- the name of key, as a string. This is in canonical "flat" form: the section, - subsection, and variable segments will be separated by dots, and the section - and variable segments will be all lowercase. - E.g., `core.ignorecase`, `diff.SomeType.textconv`. - -- the value of the variable, as a string. If value is equal to NULL, it will - remove the matching key from the config file. - -- the value regex, as a string. It will disregard key/value pairs where value - does not match. - -- a multi_replace value, as an int. If value is equal to zero, nothing or only - one matching key/value is replaced, else all matching key/values (regardless - how many) are removed, before the new pair is written. - -It returns 0 on success. - -Also, there are functions `git_config_rename_section` and -`git_config_rename_section_in_file` with parameters `old_name` and `new_name` -for renaming or removing sections in the config files. If NULL is passed -through `new_name` parameter, the section will be removed from the config file. diff --git a/Documentation/technical/api-grep.txt b/Documentation/technical/api-grep.txt deleted file mode 100644 index a69cc8964d..0000000000 --- a/Documentation/technical/api-grep.txt +++ /dev/null @@ -1,8 +0,0 @@ -grep API -======== - -Talk about <grep.h>, things like: - -* grep_buffer() - -(JC) diff --git a/Documentation/technical/api-object-access.txt b/Documentation/technical/api-object-access.txt deleted file mode 100644 index 5b29622d00..0000000000 --- a/Documentation/technical/api-object-access.txt +++ /dev/null @@ -1,15 +0,0 @@ -object access API -================= - -Talk about <sha1-file.c> and <object.h> family, things like - -* read_sha1_file() -* read_object_with_reference() -* has_sha1_file() -* write_sha1_file() -* pretend_object_file() -* lookup_{object,commit,tag,blob,tree} -* parse_{object,commit,tag,blob,tree} -* Use of object flags - -(JC, Shawn, Daniel, Dscho, Linus) diff --git a/Documentation/technical/api-quote.txt b/Documentation/technical/api-quote.txt deleted file mode 100644 index e8a1bce94e..0000000000 --- a/Documentation/technical/api-quote.txt +++ /dev/null @@ -1,10 +0,0 @@ -quote API -========= - -Talk about <quote.h>, things like - -* sq_quote and unquote -* c_style quote and unquote -* quoting for foreign languages - -(JC) diff --git a/Documentation/technical/api-xdiff-interface.txt b/Documentation/technical/api-xdiff-interface.txt deleted file mode 100644 index 6296ecad1d..0000000000 --- a/Documentation/technical/api-xdiff-interface.txt +++ /dev/null @@ -1,7 +0,0 @@ -xdiff interface API -=================== - -Talk about our calling convention to xdiff library, including -xdiff_emit_consume_fn. - -(Dscho, JC) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 5048d9bff6..22e8d83d98 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.24.0 +DEF_VER=v2.24.GIT LF=' ' @@ -1 +1 @@ -Documentation/RelNotes/2.24.0.txt
\ No newline at end of file +Documentation/RelNotes/2.25.0.txt
\ No newline at end of file @@ -32,7 +32,7 @@ static void git_apply_config(void) { git_config_get_string_const("apply.whitespace", &apply_default_whitespace); git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); - git_config(git_default_config, NULL); + git_config(git_xmerge_config, NULL); } static int parse_whitespace_option(struct apply_state *state, const char *option) diff --git a/builtin/blame.c b/builtin/blame.c index e946ba6cd9..bf1cecdf3f 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -319,18 +319,18 @@ static const char *format_time(timestamp_t time, const char *tz_str, return time_buf.buf; } -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 -#define OUTPUT_SHOW_EMAIL 0400 -#define OUTPUT_LINE_PORCELAIN 01000 -#define OUTPUT_COLOR_LINE 02000 -#define OUTPUT_SHOW_AGE_WITH_COLOR 04000 +#define OUTPUT_ANNOTATE_COMPAT (1U<<0) +#define OUTPUT_LONG_OBJECT_NAME (1U<<1) +#define OUTPUT_RAW_TIMESTAMP (1U<<2) +#define OUTPUT_PORCELAIN (1U<<3) +#define OUTPUT_SHOW_NAME (1U<<4) +#define OUTPUT_SHOW_NUMBER (1U<<5) +#define OUTPUT_SHOW_SCORE (1U<<6) +#define OUTPUT_NO_AUTHOR (1U<<7) +#define OUTPUT_SHOW_EMAIL (1U<<8) +#define OUTPUT_LINE_PORCELAIN (1U<<9) +#define OUTPUT_COLOR_LINE (1U<<10) +#define OUTPUT_SHOW_AGE_WITH_COLOR (1U<<11) static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { @@ -861,14 +861,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), - - /* - * The following two options are parsed by parse_revision_opt() - * and are only included here to get included in the "-h" - * output: - */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb }, - OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), diff --git a/builtin/clone.c b/builtin/clone.c index c46ee29f0a..b24f04cf33 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -899,7 +899,7 @@ static void dissociate_from_references(void) free(alternates); } -static int dir_exists(const char *path) +static int path_exists(const char *path) { struct stat sb; return !stat(path, &sb); @@ -981,7 +981,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - dest_exists = dir_exists(dir); + dest_exists = path_exists(dir); if (dest_exists && !is_empty_dir(dir)) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); @@ -992,7 +992,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) work_tree = NULL; else { work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && dir_exists(work_tree)) + if (work_tree && path_exists(work_tree)) die(_("working tree '%s' already exists."), work_tree); } @@ -1020,7 +1020,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (real_git_dir) { - if (dir_exists(real_git_dir)) + if (path_exists(real_git_dir)) junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = real_git_dir; } else { diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index addc8d4cc0..ec0fc93d39 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -8,7 +8,6 @@ #include "object-store.h" static char const * const builtin_commit_graph_usage[] = { - N_("git commit-graph [--object-dir <objdir>]"), N_("git commit-graph read [--object-dir <objdir>]"), N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"), diff --git a/builtin/commit.c b/builtin/commit.c index e588bc6ad3..294dc574cd 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1463,28 +1463,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) -{ - struct argv_array hook_env = ARGV_ARRAY_INIT; - va_list args; - int ret; - - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); - - /* - * Let the hook know that no editor will be launched. - */ - if (!editor_is_used) - argv_array_push(&hook_env, "GIT_EDITOR=:"); - - va_start(args, name); - ret = run_hook_ve(hook_env.argv,name, args); - va_end(args); - argv_array_clear(&hook_env); - - return ret; -} - int cmd_commit(int argc, const char **argv, const char *prefix) { const char *argv_gc_auto[] = {"gc", "--auto", NULL}; diff --git a/builtin/fetch.c b/builtin/fetch.c index 863c858fde..e8c0276d26 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1411,7 +1411,7 @@ static int do_fetch(struct transport *transport, for (rm = ref_map; rm; rm = rm->next) { if (!rm->peer_ref) { if (source_ref) { - warning(_("multiple branch detected, incompatible with --set-upstream")); + warning(_("multiple branches detected, incompatible with --set-upstream")); goto skip; } else { source_ref = rm; diff --git a/builtin/fsck.c b/builtin/fsck.c index 18403a94fa..8d13794b14 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -50,40 +50,20 @@ static int name_objects; #define ERROR_REFS 010 #define ERROR_COMMIT_GRAPH 020 -static const char *describe_object(struct object *obj) +static const char *describe_object(const struct object_id *oid) { - static struct strbuf bufs[] = { - STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT - }; - static int b = 0; - struct strbuf *buf; - char *name = NULL; - - if (name_objects) - name = lookup_decoration(fsck_walk_options.object_names, obj); - - buf = bufs + b; - b = (b + 1) % ARRAY_SIZE(bufs); - strbuf_reset(buf); - strbuf_addstr(buf, oid_to_hex(&obj->oid)); - if (name) - strbuf_addf(buf, " (%s)", name); - - return buf->buf; + return fsck_describe_object(&fsck_walk_options, oid); } -static const char *printable_type(struct object *obj) +static const char *printable_type(const struct object_id *oid, + enum object_type type) { const char *ret; - if (obj->type == OBJ_NONE) { - enum object_type type = oid_object_info(the_repository, - &obj->oid, NULL); - if (type > 0) - object_as_type(the_repository, obj, type, 0); - } + if (type == OBJ_NONE) + type = oid_object_info(the_repository, oid, NULL); - ret = type_name(obj->type); + ret = type_name(type); if (!ret) ret = _("unknown"); @@ -118,26 +98,32 @@ static int objerror(struct object *obj, const char *err) errors_found |= ERROR_OBJECT; /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), err); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid), err); return -1; } static int fsck_error_func(struct fsck_options *o, - struct object *obj, int type, const char *message) + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) { - switch (type) { + switch (msg_type) { case FSCK_WARN: /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("warning in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 0; case FSCK_ERROR: /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 1; default: - BUG("%d (FSCK_IGNORE?) should never trigger this callback", type); + BUG("%d (FSCK_IGNORE?) should never trigger this callback", + msg_type); } } @@ -155,7 +141,8 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf_ln(_("broken link from %7s %s"), - printable_type(parent), describe_object(parent)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid)); printf_ln(_("broken link from %7s %s"), (type == OBJ_ANY ? _("unknown") : type_name(type)), _("unknown")); @@ -183,10 +170,10 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (parent && !has_object_file(&obj->oid)) { printf_ln(_("broken link from %7s %s\n" " to %7s %s"), - printable_type(parent), - describe_object(parent), - printable_type(obj), - describe_object(obj)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; } return 1; @@ -292,8 +279,9 @@ static void check_reachable_object(struct object *obj) return; if (has_object_pack(&obj->oid)) return; /* it is in pack - forget about it */ - printf_ln(_("missing %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("missing %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; return; } @@ -318,8 +306,9 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf_ln(_("unreachable %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("unreachable %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); return; } @@ -337,12 +326,13 @@ static void check_unreachable_object(struct object *obj) */ if (!(obj->flags & USED)) { if (show_dangling) - printf_ln(_("dangling %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("dangling %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - describe_object(obj)); + describe_object(&obj->oid)); FILE *f; if (safe_create_leading_directories_const(filename)) { @@ -355,7 +345,7 @@ static void check_unreachable_object(struct object *obj) if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno(_("could not write '%s'"), filename); } else - fprintf(f, "%s\n", describe_object(obj)); + fprintf(f, "%s\n", describe_object(&obj->oid)); if (fclose(f)) die_errno(_("could not finish '%s'"), filename); @@ -374,7 +364,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf_ln(stderr, _("Checking %s"), describe_object(obj)); + fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -432,7 +422,8 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (verbose) fprintf_ln(stderr, _("Checking %s %s"), - printable_type(obj), describe_object(obj)); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, _("broken links")); @@ -445,7 +436,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (!commit->parents && show_root) printf_ln(_("root %s"), - describe_object(&commit->object)); + describe_object(&commit->object.oid)); } if (obj->type == OBJ_TAG) { @@ -453,10 +444,10 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (show_tags && tag->tagged) { printf_ln(_("tagged %s %s (%s) in %s"), - printable_type(tag->tagged), - describe_object(tag->tagged), + printable_type(&tag->tagged->oid, tag->tagged->type), + describe_object(&tag->tagged->oid), tag->tag, - describe_object(&tag->object)); + describe_object(&tag->object.oid)); } } @@ -499,10 +490,10 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, if (!is_null_oid(oid)) { obj = lookup_object(the_repository, oid); if (obj && (obj->flags & HAS_OBJ)) { - if (timestamp && name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt("%s@{%"PRItime"}", refname, timestamp)); + if (timestamp) + fsck_put_object_name(&fsck_walk_options, oid, + "%s@{%"PRItime"}", + refname, timestamp); obj->flags |= USED; mark_object_reachable(obj); } else if (!is_promisor_object(oid)) { @@ -566,9 +557,8 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, } default_refs++; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(refname)); + fsck_put_object_name(&fsck_walk_options, + oid, "%s", refname); mark_object_reachable(obj); return 0; @@ -742,9 +732,7 @@ static int fsck_cache_tree(struct cache_tree *it) return 1; } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(":")); + fsck_put_object_name(&fsck_walk_options, &it->oid, ":"); mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, _("non-tree in cache-tree")); @@ -830,8 +818,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (name_objects) - fsck_walk_options.object_names = - xcalloc(1, sizeof(struct decoration)); + fsck_enable_object_names(&fsck_walk_options); git_config(fsck_config, NULL); @@ -890,9 +877,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(arg)); + fsck_put_object_name(&fsck_walk_options, &oid, + "%s", arg); mark_object_reachable(obj); continue; } @@ -928,10 +914,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj = &blob->object; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt(":%s", active_cache[i]->name)); + fsck_put_object_name(&fsck_walk_options, &obj->oid, + ":%s", active_cache[i]->name); mark_object_reachable(obj); } if (active_cache_tree) diff --git a/builtin/log.c b/builtin/log.c index 89873d2dc2..a26f223ab4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -37,6 +37,7 @@ #include "range-diff.h" #define MAIL_DEFAULT_WRAP 72 +#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -765,23 +766,51 @@ static void add_header(const char *value) item->string[len] = '\0'; } -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread; +enum cover_setting { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; + +enum thread_level { + THREAD_UNSET, + THREAD_SHALLOW, + THREAD_DEEP +}; + +enum cover_from_description { + COVER_FROM_NONE, + COVER_FROM_MESSAGE, + COVER_FROM_SUBJECT, + COVER_FROM_AUTO +}; + +static enum thread_level thread; static int do_signoff; static int base_auto; static char *from; static const char *signature = git_version_string; static const char *signature_file; -static int config_cover_letter; +static enum cover_setting config_cover_letter; static const char *config_output_directory; +static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; -enum { - COVER_UNSET, - COVER_OFF, - COVER_ON, - COVER_AUTO -}; +static enum cover_from_description parse_cover_from_description(const char *arg) +{ + if (!arg || !strcmp(arg, "default")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "none")) + return COVER_FROM_NONE; + else if (!strcmp(arg, "message")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "subject")) + return COVER_FROM_SUBJECT; + else if (!strcmp(arg, "auto")) + return COVER_FROM_AUTO; + else + die(_("%s: invalid cover from description mode"), arg); +} static int git_format_config(const char *var, const char *value, void *cb) { @@ -836,7 +865,7 @@ static int git_format_config(const char *var, const char *value, void *cb) thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) && THREAD_SHALLOW; + thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { @@ -888,6 +917,10 @@ static int git_format_config(const char *var, const char *value, void *cb) } return 0; } + if (!strcmp(var, "format.coverfromdescription")) { + cover_from_description_mode = parse_cover_from_description(value); + return 0; + } return git_log_config(var, value, cb); } @@ -994,20 +1027,6 @@ static void print_signature(FILE *file) putc('\n', file); } -static void add_branch_description(struct strbuf *buf, const char *branch_name) -{ - struct strbuf desc = STRBUF_INIT; - if (!branch_name || !*branch_name) - return; - read_branch_desc(&desc, branch_name); - if (desc.len) { - strbuf_addch(buf, '\n'); - strbuf_addbuf(buf, &desc); - strbuf_addch(buf, '\n'); - } - strbuf_release(&desc); -} - static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; @@ -1054,6 +1073,44 @@ static void show_diffstat(struct rev_info *rev, fprintf(rev->diffopt.file, "\n"); } +static void prepare_cover_text(struct pretty_print_context *pp, + const char *branch_name, + struct strbuf *sb, + const char *encoding, + int need_8bit_cte) +{ + const char *subject = "*** SUBJECT HERE ***"; + const char *body = "*** BLURB HERE ***"; + struct strbuf description_sb = STRBUF_INIT; + struct strbuf subject_sb = STRBUF_INIT; + + if (cover_from_description_mode == COVER_FROM_NONE) + goto do_pp; + + if (branch_name && *branch_name) + read_branch_desc(&description_sb, branch_name); + if (!description_sb.len) + goto do_pp; + + if (cover_from_description_mode == COVER_FROM_SUBJECT || + cover_from_description_mode == COVER_FROM_AUTO) + body = format_subject(&subject_sb, description_sb.buf, " "); + + if (cover_from_description_mode == COVER_FROM_MESSAGE || + (cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + body = description_sb.buf; + else + subject = subject_sb.buf; + +do_pp: + pp_title_line(pp, &subject, sb, encoding, need_8bit_cte); + pp_remainder(pp, &body, sb, 0); + + strbuf_release(&description_sb); + strbuf_release(&subject_sb); +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, int nr, struct commit **list, @@ -1061,8 +1118,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int quiet) { const char *committer; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -1092,15 +1147,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (!branch_name) branch_name = find_branch_name(rev); - msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); - pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); - pp_remainder(&pp, &msg, &sb, 0); - add_branch_description(&sb, branch_name); + prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte); fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -1249,9 +1301,9 @@ static int output_directory_callback(const struct option *opt, const char *arg, static int thread_callback(const struct option *opt, const char *arg, int unset) { - int *thread = (int *)opt->value; + enum thread_level *thread = (enum thread_level *)opt->value; if (unset) - *thread = 0; + *thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) *thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) @@ -1542,6 +1594,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int use_patch_format = 0; int quiet = 0; int reroll_count = -1; + char *cover_from_description_arg = NULL; char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; @@ -1578,6 +1631,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "rfc", &rev, NULL, N_("Use [RFC PATCH] instead of [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, + OPT_STRING(0, "cover-from-description", &cover_from_description_arg, + N_("cover-from-description-mode"), + N_("generate parts of a cover letter based on a branch's description")), { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1669,6 +1725,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (cover_from_description_arg) + cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + if (0 < reroll_count) { struct strbuf sprefix = STRBUF_INIT; strbuf_addf(&sprefix, "%s v%d", diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index b1ea1a6aa1..5bf88cd2a8 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -6,21 +6,25 @@ #include "trace2.h" static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [--object-dir=<dir>] (write|verify|expire|repack --batch-size=<size>)"), + N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"), NULL }; static struct opts_multi_pack_index { const char *object_dir; unsigned long batch_size; + int progress; } opts; int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { + unsigned flags = 0; + static struct option builtin_multi_pack_index_options[] = { OPT_FILENAME(0, "object-dir", &opts.object_dir, N_("object directory containing set of packfile and pack-index pairs")), + OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_END(), @@ -28,12 +32,15 @@ int cmd_multi_pack_index(int argc, const char **argv, git_config(git_default_config, NULL); + opts.progress = isatty(2); argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, 0); if (!opts.object_dir) opts.object_dir = get_object_directory(); + if (opts.progress) + flags |= MIDX_PROGRESS; if (argc == 0) usage_with_options(builtin_multi_pack_index_usage, @@ -47,16 +54,17 @@ int cmd_multi_pack_index(int argc, const char **argv, trace2_cmd_mode(argv[0]); if (!strcmp(argv[0], "repack")) - return midx_repack(the_repository, opts.object_dir, (size_t)opts.batch_size); + return midx_repack(the_repository, opts.object_dir, + (size_t)opts.batch_size, flags); if (opts.batch_size) die(_("--batch-size option is only for 'repack' subcommand")); if (!strcmp(argv[0], "write")) - return write_midx_file(opts.object_dir); + return write_midx_file(opts.object_dir, flags); if (!strcmp(argv[0], "verify")) - return verify_midx_file(the_repository, opts.object_dir); + return verify_midx_file(the_repository, opts.object_dir, flags); if (!strcmp(argv[0], "expire")) - return expire_midx_packs(the_repository, opts.object_dir); + return expire_midx_packs(the_repository, opts.object_dir, flags); die(_("unrecognized subcommand: %s"), argv[0]); } diff --git a/builtin/notes.c b/builtin/notes.c index 02e97f55c5..95456f3165 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -513,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix) } } - if (argc < 2) { + if (argc < 1) { error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } diff --git a/builtin/rebase.c b/builtin/rebase.c index 4a20582e72..e755087b0f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1471,9 +1471,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("let the user edit the list of commits to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_interactive }, - OPT_SET_INT('p', "preserve-merges", &options.type, - N_("(DEPRECATED) try to recreate merges instead of " - "ignoring them"), REBASE_PRESERVE_MERGES), + OPT_SET_INT_F('p', "preserve-merges", &options.type, + N_("(DEPRECATED) try to recreate merges instead of " + "ignoring them"), + REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), OPT_BOOL('k', "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), diff --git a/builtin/repack.c b/builtin/repack.c index 094c2f8ea4..0781763b06 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -233,6 +233,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args, /* * pack-objects creates the .pack and .idx files, but not the * .promisor file. Create the .promisor file, which is empty. + * + * NEEDSWORK: fetch-pack sometimes generates non-empty + * .promisor files containing the ref names and associated + * hashes at the point of generation of the corresponding + * packfile, but this would not preserve their contents. Maybe + * concatenate the contents of all .promisor files instead of + * just creating a new empty file. */ promisor_name = mkpathdup("%s-%s.promisor", packtmp, line.buf); @@ -562,7 +569,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) remove_temporary_files(); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) - write_midx_file(get_object_directory()); + write_midx_file(get_object_directory(), 0); string_list_clear(&names, 0); string_list_clear(&rollback, 0); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 308c67e4fc..85ce2095bf 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -919,6 +919,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show_datestring("--min-age=", arg); continue; } + if (opt_with_value(arg, "--show-object-format", &arg)) { + const char *val = arg ? arg : "storage"; + + if (strcmp(val, "storage") && + strcmp(val, "input") && + strcmp(val, "output")) + die("unknown mode for --show-object-format: %s", + arg); + puts(the_hash_algo->name); + continue; + } if (show_flag(arg) && verify) die_no_single_rev(quiet); continue; diff --git a/builtin/stash.c b/builtin/stash.c index 4e806176b0..d913487a43 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1088,8 +1088,9 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); + argv_array_pushl(&cp_upd_index.args, "update-index", + "--ignore-skip-worktree-entries", + "-z", "--add", "--remove", "--stdin", NULL); argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); diff --git a/builtin/update-index.c b/builtin/update-index.c index 49302d98c5..d527b8f106 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -35,6 +35,7 @@ static int verbose; static int mark_valid_only; static int mark_skip_worktree_only; static int mark_fsmonitor_only; +static int ignore_skip_worktree_entries; #define MARK_FLAG 1 #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; @@ -381,7 +382,8 @@ static int process_path(const char *path, struct stat *st, int stat_errno) * so updating it does not make sense. * On the other hand, removing it from index should work */ - if (allow_remove && remove_file_from_cache(path)) + if (!ignore_skip_worktree_entries && allow_remove && + remove_file_from_cache(path)) return error("%s: cannot remove from the index", path); return 0; } @@ -1014,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, N_("clear skip-worktree bit"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, + N_("do not touch index-only entries")), OPT_SET_INT(0, "info-only", &info_only, N_("add to index only; do not add content to object database"), 1), OPT_SET_INT(0, "force-remove", &force_remove, diff --git a/builtin/worktree.c b/builtin/worktree.c index 4de44f579a..d6bc5263f1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -376,7 +376,7 @@ static int add_worktree(const char *path, const char *refname, if (opts->checkout) { cp.argv = NULL; argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); + argv_array_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); if (opts->quiet) argv_array_push(&cp.args, "--quiet"); cp.env = child_env.argv; diff --git a/command-list.txt b/command-list.txt index a9ac72bef4..72e435c5a3 100644 --- a/command-list.txt +++ b/command-list.txt @@ -203,6 +203,7 @@ gitmodules guide gitnamespaces guide gitrepository-layout guide gitrevisions guide +gitsubmodules guide gittutorial-2 guide gittutorial guide gitworkflows guide diff --git a/commit-graph.c b/commit-graph.c index 0aea7b2ae5..a0f868522b 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -858,9 +858,6 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len, die(_("unable to parse commit %s"), oid_to_hex(&(*list)->object.oid)); tree = get_commit_tree_oid(*list); - if (!tree) - die(_("unable to get tree for %s"), - oid_to_hex(&(*list)->object.oid)); hashwrite(f, tree->hash, hash_len); parent = (*list)->parents; @@ -19,6 +19,7 @@ #include "advice.h" #include "refs.h" #include "commit-reach.h" +#include "run-command.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -401,10 +402,22 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b struct commit_graft *graft; const int tree_entry_len = the_hash_algo->hexsz + 5; const int parent_entry_len = the_hash_algo->hexsz + 7; + struct tree *tree; if (item->object.parsed) return 0; - item->object.parsed = 1; + + if (item->parents) { + /* + * Presumably this is leftover from an earlier failed parse; + * clear it out in preparation for us re-parsing (we'll hit the + * same error, but that's good, since it lets our caller know + * the result cannot be trusted. + */ + free_commit_list(item->parents); + item->parents = NULL; + } + tail += size; if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) || bufptr[tree_entry_len] != '\n') @@ -412,7 +425,12 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b if (get_oid_hex(bufptr + 5, &parent) < 0) return error("bad tree pointer in commit %s", oid_to_hex(&item->object.oid)); - set_commit_tree(item, lookup_tree(r, &parent)); + tree = lookup_tree(r, &parent); + if (!tree) + return error("bad tree pointer %s in commit %s", + oid_to_hex(&parent), + oid_to_hex(&item->object.oid)); + set_commit_tree(item, tree); bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */ pptr = &item->parents; @@ -432,8 +450,11 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b if (graft && (graft->nr_parent < 0 || grafts_replace_parents)) continue; new_parent = lookup_commit(r, &parent); - if (new_parent) - pptr = &commit_list_insert(new_parent, pptr)->next; + if (!new_parent) + return error("bad parent %s in commit %s", + oid_to_hex(&parent), + oid_to_hex(&item->object.oid)); + pptr = &commit_list_insert(new_parent, pptr)->next; } if (graft) { int i; @@ -442,7 +463,9 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b new_parent = lookup_commit(r, &graft->parent[i]); if (!new_parent) - continue; + return error("bad graft parent %s in commit %s", + oid_to_hex(&graft->parent[i]), + oid_to_hex(&item->object.oid)); pptr = &commit_list_insert(new_parent, pptr)->next; } } @@ -451,6 +474,7 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b if (check_graph) load_commit_graph_info(r, item); + item->object.parsed = 1; return 0; } @@ -1581,3 +1605,26 @@ size_t ignore_non_trailer(const char *buf, size_t len) } return boc ? len - boc : len - cutoff; } + +int run_commit_hook(int editor_is_used, const char *index_file, + const char *name, ...) +{ + struct argv_array hook_env = ARGV_ARRAY_INIT; + va_list args; + int ret; + + argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); + + /* + * Let the hook know that no editor will be launched. + */ + if (!editor_is_used) + argv_array_push(&hook_env, "GIT_EDITOR=:"); + + va_start(args, name); + ret = run_hook_ve(hook_env.argv,name, args); + va_end(args); + argv_array_clear(&hook_env); + + return ret; +} @@ -4,6 +4,22 @@ #include "hashmap.h" #include "string-list.h" + +/** + * The config API gives callers a way to access Git configuration files + * (and files which have the same syntax). + * + * General Usage + * ------------- + * + * Config files are parsed linearly, and each variable found is passed to a + * caller-provided callback function. The callback function is responsible + * for any actions to be taken on the config option, and is free to ignore + * some options. It is not uncommon for the configuration to be parsed + * several times during the run of a Git program, with different callbacks + * picking out different variables useful to themselves. + */ + struct object_id; /* git_config_parse_key() returns these negated: */ @@ -71,9 +87,34 @@ struct config_options { } error_action; }; +/** + * A config callback function takes three parameters: + * + * - the name of the parsed variable. This is in canonical "flat" form: the + * section, subsection, and variable segments will be separated by dots, + * and the section and variable segments will be all lowercase. E.g., + * `core.ignorecase`, `diff.SomeType.textconv`. + * + * - the value of the found variable, as a string. If the variable had no + * value specified, the value will be NULL (typically this means it + * should be interpreted as boolean true). + * + * - a void pointer passed in by the caller of the config API; this can + * contain callback-specific data + * + * A config callback should return 0 for success, or -1 if the variable + * could not be parsed properly. + */ typedef int (*config_fn_t)(const char *, const char *, void *); + int git_default_config(const char *, const char *, void *); + +/** + * Read a specific file in git-config format. + * This function takes the same callback and data parameters as `git_config`. + */ int git_config_from_file(config_fn_t fn, const char *, void *); + int git_config_from_file_with_options(config_fn_t fn, const char *, void *, const struct config_options *); @@ -88,34 +129,157 @@ void git_config_push_parameter(const char *text); int git_config_from_parameters(config_fn_t fn, void *data); void read_early_config(config_fn_t cb, void *data); void read_very_early_config(config_fn_t cb, void *data); + +/** + * Most programs will simply want to look up variables in all config files + * that Git knows about, using the normal precedence rules. To do this, + * call `git_config` with a callback function and void data pointer. + * + * `git_config` will read all config sources in order of increasing + * priority. Thus a callback should typically overwrite previously-seen + * entries with new ones (e.g., if both the user-wide `~/.gitconfig` and + * repo-specific `.git/config` contain `color.ui`, the config machinery + * will first feed the user-wide one to the callback, and then the + * repo-specific one; by overwriting, the higher-priority repo-specific + * value is left at the end). + */ void git_config(config_fn_t fn, void *); + +/** + * Lets the caller examine config while adjusting some of the default + * behavior of `git_config`. It should almost never be used by "regular" + * Git code that is looking up configuration variables. + * It is intended for advanced callers like `git-config`, which are + * intentionally tweaking the normal config-lookup process. + * It takes two extra parameters: + * + * - `config_source` + * If this parameter is non-NULL, it specifies the source to parse for + * configuration, rather than looking in the usual files. See `struct + * git_config_source` in `config.h` for details. Regular `git_config` defaults + * to `NULL`. + * + * - `opts` + * Specify options to adjust the behavior of parsing config files. See `struct + * config_options` in `config.h` for details. As an example: regular `git_config` + * sets `opts.respect_includes` to `1` by default. + */ int config_with_options(config_fn_t fn, void *, struct git_config_source *config_source, const struct config_options *opts); + +/** + * Value Parsing Helpers + * --------------------- + * + * The following helper functions aid in parsing string values + */ + int git_parse_ssize_t(const char *, ssize_t *); int git_parse_ulong(const char *, unsigned long *); + +/** + * Same as `git_config_bool`, except that it returns -1 on error rather + * than dying. + */ int git_parse_maybe_bool(const char *); + +/** + * Parse the string to an integer, including unit factors. Dies on error; + * otherwise, returns the parsed result. + */ int git_config_int(const char *, const char *); + int64_t git_config_int64(const char *, const char *); + +/** + * Identical to `git_config_int`, but for unsigned longs. + */ unsigned long git_config_ulong(const char *, const char *); + ssize_t git_config_ssize_t(const char *, const char *); + +/** + * Same as `git_config_bool`, except that integers are returned as-is, and + * an `is_bool` flag is unset. + */ int git_config_bool_or_int(const char *, const char *, int *); + +/** + * Parse a string into a boolean value, respecting keywords like "true" and + * "false". Integer values are converted into true/false values (when they + * are non-zero or zero, respectively). Other values cause a die(). If + * parsing is successful, the return value is the result. + */ int git_config_bool(const char *, const char *); + +/** + * Allocates and copies the value string into the `dest` parameter; if no + * string is given, prints an error message and returns -1. + */ int git_config_string(const char **, const char *, const char *); + +/** + * Similar to `git_config_string`, but expands `~` or `~user` into the + * user's home directory when found at the beginning of the path. + */ int git_config_pathname(const char **, const char *, const char *); + int git_config_expiry_date(timestamp_t *, const char *, const char *); int git_config_color(char *, const char *, const char *); int git_config_set_in_file_gently(const char *, const char *, const char *); + +/** + * write config values to a specific config file, takes a key/value pair as + * parameter. + */ void git_config_set_in_file(const char *, const char *, const char *); + int git_config_set_gently(const char *, const char *); + +/** + * write config values to `.git/config`, takes a key/value pair as parameter. + */ void git_config_set(const char *, const char *); + int git_config_parse_key(const char *, char **, int *); int git_config_key_is_valid(const char *key); int git_config_set_multivar_gently(const char *, const char *, const char *, int); void git_config_set_multivar(const char *, const char *, const char *, int); int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int); + +/** + * takes four parameters: + * + * - the name of the file, as a string, to which key/value pairs will be written. + * + * - the name of key, as a string. This is in canonical "flat" form: the section, + * subsection, and variable segments will be separated by dots, and the section + * and variable segments will be all lowercase. + * E.g., `core.ignorecase`, `diff.SomeType.textconv`. + * + * - the value of the variable, as a string. If value is equal to NULL, it will + * remove the matching key from the config file. + * + * - the value regex, as a string. It will disregard key/value pairs where value + * does not match. + * + * - a multi_replace value, as an int. If value is equal to zero, nothing or only + * one matching key/value is replaced, else all matching key/values (regardless + * how many) are removed, before the new pair is written. + * + * It returns 0 on success. + */ void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); + +/** + * rename or remove sections in the config file + * parameters `old_name` and `new_name` + * If NULL is passed through `new_name` parameter, + * the section will be removed from the config file. + */ int git_config_rename_section(const char *, const char *); + int git_config_rename_section_in_file(const char *, const char *, const char *); int git_config_copy_section(const char *, const char *); int git_config_copy_section_in_file(const char *, const char *, const char *); @@ -142,6 +306,30 @@ enum config_scope current_config_scope(void); const char *current_config_origin_type(void); const char *current_config_name(void); +/** + * Include Directives + * ------------------ + * + * By default, the config parser does not respect include directives. + * However, a caller can use the special `git_config_include` wrapper + * callback to support them. To do so, you simply wrap your "real" callback + * function and data pointer in a `struct config_include_data`, and pass + * the wrapper to the regular config-reading functions. For example: + * + * ------------------------------------------- + * int read_file_with_include(const char *file, config_fn_t fn, void *data) + * { + * struct config_include_data inc = CONFIG_INCLUDE_INIT; + * inc.fn = fn; + * inc.data = data; + * return git_config_from_file(git_config_include, file, &inc); + * } + * ------------------------------------------- + * + * `git_config` respects includes automatically. The lower-level + * `git_config_from_file` does not. + * + */ struct config_include_data { int depth; config_fn_t fn; @@ -169,6 +357,33 @@ int parse_config_key(const char *var, const char **subsection, int *subsection_len, const char **key); +/** + * Custom Configsets + * ----------------- + * + * A `config_set` can be used to construct an in-memory cache for + * config-like files that the caller specifies (i.e., files like `.gitmodules`, + * `~/.gitconfig` etc.). For example, + * + * ---------------------------------------- + * struct config_set gm_config; + * git_configset_init(&gm_config); + * int b; + * //we add config files to the config_set + * git_configset_add_file(&gm_config, ".gitmodules"); + * git_configset_add_file(&gm_config, ".gitmodules_alt"); + * + * if (!git_configset_get_bool(gm_config, "submodule.frotz.ignore", &b)) { + * //hack hack hack + * } + * + * when we are done with the configset: + * git_configset_clear(&gm_config); + * ---------------------------------------- + * + * Configset API provides functions for the above mentioned work flow + */ + struct config_set_element { struct hashmap_entry ent; char *key; @@ -197,16 +412,47 @@ struct config_set { struct configset_list list; }; +/** + * Initializes the config_set `cs`. + */ void git_configset_init(struct config_set *cs); + +/** + * Parses the file and adds the variable-value pairs to the `config_set`, + * dies if there is an error in parsing the file. Returns 0 on success, or + * -1 if the file does not exist or is inaccessible. The user has to decide + * if he wants to free the incomplete configset or continue using it when + * the function returns -1. + */ int git_configset_add_file(struct config_set *cs, const char *filename); + +/** + * Finds and returns the value list, sorted in order of increasing priority + * for the configuration variable `key` and config set `cs`. When the + * configuration variable `key` is not found, returns NULL. The caller + * should not free or modify the returned pointer, as it is owned by the cache. + */ const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key); + +/** + * Clears `config_set` structure, removes all saved variable-value pairs. + */ void git_configset_clear(struct config_set *cs); /* * These functions return 1 if not found, and 0 if found, leaving the found * value in the 'dest' pointer. */ + +/* + * Finds the highest-priority value for the configuration variable `key` + * and config set `cs`, stores the pointer to it in `value` and returns 0. + * When the configuration variable `key` is not found, returns 1 without + * touching `value`. The caller should not free or modify `value`, as it + * is owned by the cache. + */ int git_configset_get_value(struct config_set *cs, const char *key, const char **dest); + int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest); int git_configset_get_string(struct config_set *cs, const char *key, char **dest); int git_configset_get_int(struct config_set *cs, const char *key, int *dest); @@ -240,17 +486,94 @@ int repo_config_get_maybe_bool(struct repository *repo, int repo_config_get_pathname(struct repository *repo, const char *key, const char **dest); +/** + * Querying For Specific Variables + * ------------------------------- + * + * For programs wanting to query for specific variables in a non-callback + * manner, the config API provides two functions `git_config_get_value` + * and `git_config_get_value_multi`. They both read values from an internal + * cache generated previously from reading the config files. + */ + +/** + * Finds the highest-priority value for the configuration variable `key`, + * stores the pointer to it in `value` and returns 0. When the + * configuration variable `key` is not found, returns 1 without touching + * `value`. The caller should not free or modify `value`, as it is owned + * by the cache. + */ int git_config_get_value(const char *key, const char **value); + +/** + * Finds and returns the value list, sorted in order of increasing priority + * for the configuration variable `key`. When the configuration variable + * `key` is not found, returns NULL. The caller should not free or modify + * the returned pointer, as it is owned by the cache. + */ const struct string_list *git_config_get_value_multi(const char *key); + +/** + * Resets and invalidates the config cache. + */ void git_config_clear(void); + +/** + * Allocates and copies the retrieved string into the `dest` parameter for + * the configuration variable `key`; if NULL string is given, prints an + * error message and returns -1. When the configuration variable `key` is + * not found, returns 1 without touching `dest`. + */ int git_config_get_string_const(const char *key, const char **dest); + +/** + * Similar to `git_config_get_string_const`, except that retrieved value + * copied into the `dest` parameter is a mutable string. + */ int git_config_get_string(const char *key, char **dest); + +/** + * Finds and parses the value to an integer for the configuration variable + * `key`. Dies on error; otherwise, stores the value of the parsed integer in + * `dest` and returns 0. When the configuration variable `key` is not found, + * returns 1 without touching `dest`. + */ int git_config_get_int(const char *key, int *dest); + +/** + * Similar to `git_config_get_int` but for unsigned longs. + */ int git_config_get_ulong(const char *key, unsigned long *dest); + +/** + * Finds and parses the value into a boolean value, for the configuration + * variable `key` respecting keywords like "true" and "false". Integer + * values are converted into true/false values (when they are non-zero or + * zero, respectively). Other values cause a die(). If parsing is successful, + * stores the value of the parsed result in `dest` and returns 0. When the + * configuration variable `key` is not found, returns 1 without touching + * `dest`. + */ int git_config_get_bool(const char *key, int *dest); + +/** + * Similar to `git_config_get_bool`, except that integers are copied as-is, + * and `is_bool` flag is unset. + */ int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); + +/** + * Similar to `git_config_get_bool`, except that it returns -1 on error + * rather than dying. + */ int git_config_get_maybe_bool(const char *key, int *dest); + +/** + * Similar to `git_config_get_string`, but expands `~` or `~user` into + * the user's home directory when found at the beginning of the path. + */ int git_config_get_pathname(const char *key, const char **dest); + int git_config_get_index_threads(int *dest); int git_config_get_untracked_cache(void); int git_config_get_split_index(void); @@ -270,7 +593,19 @@ struct key_value_info { enum config_scope scope; }; +/** + * First prints the error message specified by the caller in `err` and then + * dies printing the line number and the file name of the highest priority + * value for the configuration variable `key`. + */ NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3))); + +/** + * Helper function which formats the die error message according to the + * parameters entered. Used by `git_die_config()`. It can be used by callers + * handling `git_config_get_value_multi()` to print the correct error message + * for the desired value. + */ NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr); #define LOOKUP_CONFIG(mapping, var) \ diff --git a/fetch-pack.c b/fetch-pack.c index 0130b44112..f80e2d1149 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -756,8 +756,33 @@ static int sideband_demux(int in, int out, void *data) return ret; } +static void write_promisor_file(const char *keep_name, + struct ref **sought, int nr_sought) +{ + struct strbuf promisor_name = STRBUF_INIT; + int suffix_stripped; + FILE *output; + int i; + + strbuf_addstr(&promisor_name, keep_name); + suffix_stripped = strbuf_strip_suffix(&promisor_name, ".keep"); + if (!suffix_stripped) + BUG("name of pack lockfile should end with .keep (was '%s')", + keep_name); + strbuf_addstr(&promisor_name, ".promisor"); + + output = xfopen(promisor_name.buf, "w"); + for (i = 0; i < nr_sought; i++) + fprintf(output, "%s %s\n", oid_to_hex(&sought[i]->old_oid), + sought[i]->name); + fclose(output); + + strbuf_release(&promisor_name); +} + static int get_pack(struct fetch_pack_args *args, - int xd[2], char **pack_lockfile) + int xd[2], char **pack_lockfile, + struct ref **sought, int nr_sought) { struct async demux; int do_keep = args->keep_pack; @@ -819,7 +844,13 @@ static int get_pack(struct fetch_pack_args *args, } if (args->check_self_contained_and_connected) argv_array_push(&cmd.args, "--check-self-contained-and-connected"); - if (args->from_promisor) + /* + * If we're obtaining the filename of a lockfile, we'll use + * that filename to write a .promisor file with more + * information below. If not, we need index-pack to do it for + * us. + */ + if (!(do_keep && pack_lockfile) && args->from_promisor) argv_array_push(&cmd.args, "--promisor"); } else { @@ -873,6 +904,14 @@ static int get_pack(struct fetch_pack_args *args, die(_("%s failed"), cmd_name); if (use_sideband && finish_async(&demux)) die(_("error in sideband demultiplexer")); + + /* + * Now that index-pack has succeeded, write the promisor file using the + * obtained .keep filename if necessary + */ + if (do_keep && pack_lockfile && args->from_promisor) + write_promisor_file(*pack_lockfile, sought, nr_sought); + return 0; } @@ -1008,7 +1047,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, alternate_shallow_file = setup_temporary_shallow(si->shallow); else alternate_shallow_file = NULL; - if (get_pack(args, fd, pack_lockfile)) + if (get_pack(args, fd, pack_lockfile, sought, nr_sought)) die(_("git fetch-pack: fetch failed.")); all_done: @@ -1464,7 +1503,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, /* get the pack */ process_section_header(&reader, "packfile", 0); - if (get_pack(args, fd, pack_lockfile)) + if (get_pack(args, fd, pack_lockfile, sought, nr_sought)) die(_("git fetch-pack: fetch failed.")); state = FETCH_DONE; @@ -43,21 +43,17 @@ static struct oidset gitmodules_done = OIDSET_INIT; FUNC(MISSING_AUTHOR, ERROR) \ FUNC(MISSING_COMMITTER, ERROR) \ FUNC(MISSING_EMAIL, ERROR) \ - FUNC(MISSING_GRAFT, ERROR) \ FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \ FUNC(MISSING_OBJECT, ERROR) \ - FUNC(MISSING_PARENT, ERROR) \ FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \ FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \ FUNC(MISSING_TAG, ERROR) \ FUNC(MISSING_TAG_ENTRY, ERROR) \ - FUNC(MISSING_TAG_OBJECT, ERROR) \ FUNC(MISSING_TREE, ERROR) \ FUNC(MISSING_TREE_OBJECT, ERROR) \ FUNC(MISSING_TYPE, ERROR) \ FUNC(MISSING_TYPE_ENTRY, ERROR) \ FUNC(MULTIPLE_AUTHORS, ERROR) \ - FUNC(TAG_OBJECT_NOT_TAG, ERROR) \ FUNC(TREE_NOT_SORTED, ERROR) \ FUNC(UNKNOWN_TYPE, ERROR) \ FUNC(ZERO_PADDED_DATE, ERROR) \ @@ -281,14 +277,16 @@ static void append_msg_id(struct strbuf *sb, const char *msg_id) strbuf_addstr(sb, ": "); } -static int object_on_skiplist(struct fsck_options *opts, struct object *obj) +static int object_on_skiplist(struct fsck_options *opts, + const struct object_id *oid) { - return opts && obj && oidset_contains(&opts->skiplist, &obj->oid); + return opts && oid && oidset_contains(&opts->skiplist, oid); } -__attribute__((format (printf, 4, 5))) -static int report(struct fsck_options *options, struct object *object, - enum fsck_msg_id id, const char *fmt, ...) +__attribute__((format (printf, 5, 6))) +static int report(struct fsck_options *options, + const struct object_id *oid, enum object_type object_type, + enum fsck_msg_id id, const char *fmt, ...) { va_list ap; struct strbuf sb = STRBUF_INIT; @@ -297,7 +295,7 @@ static int report(struct fsck_options *options, struct object *object, if (msg_type == FSCK_IGNORE) return 0; - if (object_on_skiplist(options, object)) + if (object_on_skiplist(options, oid)) return 0; if (msg_type == FSCK_FATAL) @@ -309,49 +307,71 @@ static int report(struct fsck_options *options, struct object *object, va_start(ap, fmt); strbuf_vaddf(&sb, fmt, ap); - result = options->error_func(options, object, msg_type, sb.buf); + result = options->error_func(options, oid, object_type, + msg_type, sb.buf); strbuf_release(&sb); va_end(ap); return result; } -static char *get_object_name(struct fsck_options *options, struct object *obj) +void fsck_enable_object_names(struct fsck_options *options) { if (!options->object_names) + options->object_names = kh_init_oid_map(); +} + +const char *fsck_get_object_name(struct fsck_options *options, + const struct object_id *oid) +{ + khiter_t pos; + if (!options->object_names) + return NULL; + pos = kh_get_oid_map(options->object_names, *oid); + if (pos >= kh_end(options->object_names)) return NULL; - return lookup_decoration(options->object_names, obj); + return kh_value(options->object_names, pos); } -static void put_object_name(struct fsck_options *options, struct object *obj, - const char *fmt, ...) +void fsck_put_object_name(struct fsck_options *options, + const struct object_id *oid, + const char *fmt, ...) { va_list ap; struct strbuf buf = STRBUF_INIT; - char *existing; + khiter_t pos; + int hashret; if (!options->object_names) return; - existing = lookup_decoration(options->object_names, obj); - if (existing) + + pos = kh_put_oid_map(options->object_names, *oid, &hashret); + if (!hashret) return; va_start(ap, fmt); strbuf_vaddf(&buf, fmt, ap); - add_decoration(options->object_names, obj, strbuf_detach(&buf, NULL)); + kh_value(options->object_names, pos) = strbuf_detach(&buf, NULL); va_end(ap); } -static const char *describe_object(struct fsck_options *o, struct object *obj) +const char *fsck_describe_object(struct fsck_options *options, + const struct object_id *oid) { - static struct strbuf buf = STRBUF_INIT; - char *name; - - strbuf_reset(&buf); - strbuf_addstr(&buf, oid_to_hex(&obj->oid)); - if (o->object_names && (name = lookup_decoration(o->object_names, obj))) - strbuf_addf(&buf, " (%s)", name); + static struct strbuf bufs[] = { + STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; + static int b = 0; + struct strbuf *buf; + const char *name = fsck_get_object_name(options, oid); + + buf = bufs + b; + b = (b + 1) % ARRAY_SIZE(bufs); + strbuf_reset(buf); + strbuf_addstr(buf, oid_to_hex(oid)); + if (name) + strbuf_addf(buf, " (%s)", name); - return buf.buf; + return buf->buf; } static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *options) @@ -364,7 +384,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op if (parse_tree(tree)) return -1; - name = get_object_name(options, &tree->object); + name = fsck_get_object_name(options, &tree->object.oid); if (init_tree_desc_gently(&desc, tree->buffer, tree->size)) return -1; while (tree_entry_gently(&desc, &entry)) { @@ -377,20 +397,21 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op if (S_ISDIR(entry.mode)) { obj = (struct object *)lookup_tree(the_repository, &entry.oid); if (name && obj) - put_object_name(options, obj, "%s%s/", name, - entry.path); + fsck_put_object_name(options, &entry.oid, "%s%s/", + name, entry.path); result = options->walk(obj, OBJ_TREE, data, options); } else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) { obj = (struct object *)lookup_blob(the_repository, &entry.oid); if (name && obj) - put_object_name(options, obj, "%s%s", name, - entry.path); + fsck_put_object_name(options, &entry.oid, "%s%s", + name, entry.path); result = options->walk(obj, OBJ_BLOB, data, options); } else { result = error("in tree %s: entry %s has bad mode %.6o", - describe_object(options, &tree->object), entry.path, entry.mode); + fsck_describe_object(options, &tree->object.oid), + entry.path, entry.mode); } if (result < 0) return result; @@ -411,10 +432,10 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio if (parse_commit(commit)) return -1; - name = get_object_name(options, &commit->object); + name = fsck_get_object_name(options, &commit->object.oid); if (name) - put_object_name(options, &get_commit_tree(commit)->object, - "%s:", name); + fsck_put_object_name(options, get_commit_tree_oid(commit), + "%s:", name); result = options->walk((struct object *)get_commit_tree(commit), OBJ_TREE, data, options); @@ -442,16 +463,17 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio while (parents) { if (name) { - struct object *obj = &parents->item->object; + struct object_id *oid = &parents->item->object.oid; if (counter++) - put_object_name(options, obj, "%s^%d", - name, counter); + fsck_put_object_name(options, oid, "%s^%d", + name, counter); else if (generation > 0) - put_object_name(options, obj, "%.*s~%d", - name_prefix_len, name, generation + 1); + fsck_put_object_name(options, oid, "%.*s~%d", + name_prefix_len, name, + generation + 1); else - put_object_name(options, obj, "%s^", name); + fsck_put_object_name(options, oid, "%s^", name); } result = options->walk((struct object *)parents->item, OBJ_COMMIT, data, options); if (result < 0) @@ -465,12 +487,12 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *options) { - char *name = get_object_name(options, &tag->object); + const char *name = fsck_get_object_name(options, &tag->object.oid); if (parse_tag(tag)) return -1; if (name) - put_object_name(options, tag->tagged, "%s", name); + fsck_put_object_name(options, &tag->tagged->oid, "%s", name); return options->walk(tag->tagged, OBJ_ANY, data, options); } @@ -492,7 +514,8 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) case OBJ_TAG: return fsck_walk_tag((struct tag *)obj, data, options); default: - error("Unknown object type for %s", describe_object(options, obj)); + error("Unknown object type for %s", + fsck_describe_object(options, &obj->oid)); return -1; } } @@ -543,7 +566,9 @@ static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, con return c1 < c2 ? 0 : TREE_UNORDERED; } -static int fsck_tree(struct tree *item, struct fsck_options *options) +static int fsck_tree(const struct object_id *oid, + const char *buffer, unsigned long size, + struct fsck_options *options) { int retval = 0; int has_null_sha1 = 0; @@ -560,8 +585,8 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) unsigned o_mode; const char *o_name; - if (init_tree_desc_gently(&desc, item->buffer, item->size)) { - retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); + if (init_tree_desc_gently(&desc, buffer, size)) { + retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); return retval; } @@ -587,13 +612,14 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) if (!S_ISLNK(mode)) oidset_insert(&gitmodules_found, oid); else - retval += report(options, &item->object, + retval += report(options, + oid, OBJ_TREE, FSCK_MSG_GITMODULES_SYMLINK, ".gitmodules is a symbolic link"); } if (update_tree_entry_gently(&desc)) { - retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); break; } @@ -638,30 +664,31 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) } if (has_null_sha1) - retval += report(options, &item->object, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1"); if (has_full_path) - retval += report(options, &item->object, FSCK_MSG_FULL_PATHNAME, "contains full pathnames"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_FULL_PATHNAME, "contains full pathnames"); if (has_empty_name) - retval += report(options, &item->object, FSCK_MSG_EMPTY_NAME, "contains empty pathname"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_EMPTY_NAME, "contains empty pathname"); if (has_dot) - retval += report(options, &item->object, FSCK_MSG_HAS_DOT, "contains '.'"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOT, "contains '.'"); if (has_dotdot) - retval += report(options, &item->object, FSCK_MSG_HAS_DOTDOT, "contains '..'"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTDOT, "contains '..'"); if (has_dotgit) - retval += report(options, &item->object, FSCK_MSG_HAS_DOTGIT, "contains '.git'"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTGIT, "contains '.git'"); if (has_zero_pad) - retval += report(options, &item->object, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes"); if (has_bad_modes) - retval += report(options, &item->object, FSCK_MSG_BAD_FILEMODE, "contains bad file modes"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_FILEMODE, "contains bad file modes"); if (has_dup_entries) - retval += report(options, &item->object, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries"); if (not_properly_sorted) - retval += report(options, &item->object, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); + retval += report(options, oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); return retval; } static int verify_headers(const void *data, unsigned long size, - struct object *obj, struct fsck_options *options) + const struct object_id *oid, enum object_type type, + struct fsck_options *options) { const char *buffer = (const char *)data; unsigned long i; @@ -669,7 +696,7 @@ static int verify_headers(const void *data, unsigned long size, for (i = 0; i < size; i++) { switch (buffer[i]) { case '\0': - return report(options, obj, + return report(options, oid, type, FSCK_MSG_NUL_IN_HEADER, "unterminated header: NUL at offset %ld", i); case '\n': @@ -687,11 +714,13 @@ static int verify_headers(const void *data, unsigned long size, if (size && buffer[size - 1] == '\n') return 0; - return report(options, obj, + return report(options, oid, type, FSCK_MSG_UNTERMINATED_HEADER, "unterminated header"); } -static int fsck_ident(const char **ident, struct object *obj, struct fsck_options *options) +static int fsck_ident(const char **ident, + const struct object_id *oid, enum object_type type, + struct fsck_options *options) { const char *p = *ident; char *end; @@ -701,28 +730,28 @@ static int fsck_ident(const char **ident, struct object *obj, struct fsck_option (*ident)++; if (*p == '<') - return report(options, obj, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + return report(options, oid, type, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); p += strcspn(p, "<>\n"); if (*p == '>') - return report(options, obj, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); + return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); if (*p != '<') - return report(options, obj, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); + return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); if (p[-1] != ' ') - return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); + return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); p++; p += strcspn(p, "<>\n"); if (*p != '>') - return report(options, obj, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); + return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); p++; if (*p != ' ') - return report(options, obj, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); + return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); p++; if (*p == '0' && p[1] != ' ') - return report(options, obj, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); + return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); if (date_overflows(parse_timestamp(p, &end, 10))) - return report(options, obj, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); + return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); if ((end == p || *end != ' ')) - return report(options, obj, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); + return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); p = end + 1; if ((*p != '+' && *p != '-') || !isdigit(p[1]) || @@ -730,83 +759,60 @@ static int fsck_ident(const char **ident, struct object *obj, struct fsck_option !isdigit(p[3]) || !isdigit(p[4]) || (p[5] != '\n')) - return report(options, obj, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone"); + return report(options, oid, type, FSCK_MSG_BAD_TIMEZONE, "invalid author/committer line - bad time zone"); p += 6; return 0; } -static int fsck_commit_buffer(struct commit *commit, const char *buffer, - unsigned long size, struct fsck_options *options) +static int fsck_commit(const struct object_id *oid, + const char *buffer, unsigned long size, + struct fsck_options *options) { - struct object_id tree_oid, oid; - struct commit_graft *graft; - unsigned parent_count, parent_line_count = 0, author_count; + struct object_id tree_oid, parent_oid; + unsigned author_count; int err; const char *buffer_begin = buffer; const char *p; - if (verify_headers(buffer, size, &commit->object, options)) + if (verify_headers(buffer, size, oid, OBJ_COMMIT, options)) return -1; if (!skip_prefix(buffer, "tree ", &buffer)) - return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); + return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line"); if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') { - err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); + err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1"); if (err) return err; } buffer = p + 1; while (skip_prefix(buffer, "parent ", &buffer)) { - if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') { - err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); + if (parse_oid_hex(buffer, &parent_oid, &p) || *p != '\n') { + err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1"); if (err) return err; } buffer = p + 1; - parent_line_count++; - } - graft = lookup_commit_graft(the_repository, &commit->object.oid); - parent_count = commit_list_count(commit->parents); - if (graft) { - if (graft->nr_parent == -1 && !parent_count) - ; /* shallow commit */ - else if (graft->nr_parent != parent_count) { - err = report(options, &commit->object, FSCK_MSG_MISSING_GRAFT, "graft objects missing"); - if (err) - return err; - } - } else { - if (parent_count != parent_line_count) { - err = report(options, &commit->object, FSCK_MSG_MISSING_PARENT, "parent objects missing"); - if (err) - return err; - } } author_count = 0; while (skip_prefix(buffer, "author ", &buffer)) { author_count++; - err = fsck_ident(&buffer, &commit->object, options); + err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); if (err) return err; } if (author_count < 1) - err = report(options, &commit->object, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line"); + err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_AUTHOR, "invalid format - expected 'author' line"); else if (author_count > 1) - err = report(options, &commit->object, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); + err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines"); if (err) return err; if (!skip_prefix(buffer, "committer ", &buffer)) - return report(options, &commit->object, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); - err = fsck_ident(&buffer, &commit->object, options); + return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); + err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); if (err) return err; - if (!get_commit_tree(commit)) { - err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", oid_to_hex(&tree_oid)); - if (err) - return err; - } if (memchr(buffer_begin, '\0', size)) { - err = report(options, &commit->object, FSCK_MSG_NUL_IN_COMMIT, + err = report(options, oid, OBJ_COMMIT, FSCK_MSG_NUL_IN_COMMIT, "NUL byte in the commit object body"); if (err) return err; @@ -814,91 +820,60 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer, return 0; } -static int fsck_commit(struct commit *commit, const char *data, - unsigned long size, struct fsck_options *options) -{ - const char *buffer = data ? data : get_commit_buffer(commit, &size); - int ret = fsck_commit_buffer(commit, buffer, size, options); - if (!data) - unuse_commit_buffer(commit, buffer); - return ret; -} - -static int fsck_tag_buffer(struct tag *tag, const char *data, - unsigned long size, struct fsck_options *options) +static int fsck_tag(const struct object_id *oid, const char *buffer, + unsigned long size, struct fsck_options *options) { - struct object_id oid; + struct object_id tagged_oid; int ret = 0; - const char *buffer; - char *to_free = NULL, *eol; + char *eol; struct strbuf sb = STRBUF_INIT; const char *p; - if (data) - buffer = data; - else { - enum object_type type; - - buffer = to_free = - read_object_file(&tag->object.oid, &type, &size); - if (!buffer) - return report(options, &tag->object, - FSCK_MSG_MISSING_TAG_OBJECT, - "cannot read tag object"); - - if (type != OBJ_TAG) { - ret = report(options, &tag->object, - FSCK_MSG_TAG_OBJECT_NOT_TAG, - "expected tag got %s", - type_name(type)); - goto done; - } - } - - ret = verify_headers(buffer, size, &tag->object, options); + ret = verify_headers(buffer, size, oid, OBJ_TAG, options); if (ret) goto done; if (!skip_prefix(buffer, "object ", &buffer)) { - ret = report(options, &tag->object, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line"); goto done; } - if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') { - ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1"); + if (parse_oid_hex(buffer, &tagged_oid, &p) || *p != '\n') { + ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1"); if (ret) goto done; } buffer = p + 1; if (!skip_prefix(buffer, "type ", &buffer)) { - ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line"); goto done; } if (type_from_string_gently(buffer, eol - buffer, 1) < 0) - ret = report(options, &tag->object, FSCK_MSG_BAD_TYPE, "invalid 'type' value"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_BAD_TYPE, "invalid 'type' value"); if (ret) goto done; buffer = eol + 1; if (!skip_prefix(buffer, "tag ", &buffer)) { - ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line"); goto done; } eol = strchr(buffer, '\n'); if (!eol) { - ret = report(options, &tag->object, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line"); goto done; } strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer); if (check_refname_format(sb.buf, 0)) { - ret = report(options, &tag->object, FSCK_MSG_BAD_TAG_NAME, - "invalid 'tag' name: %.*s", - (int)(eol - buffer), buffer); + ret = report(options, oid, OBJ_TAG, + FSCK_MSG_BAD_TAG_NAME, + "invalid 'tag' name: %.*s", + (int)(eol - buffer), buffer); if (ret) goto done; } @@ -906,32 +881,20 @@ static int fsck_tag_buffer(struct tag *tag, const char *data, if (!skip_prefix(buffer, "tagger ", &buffer)) { /* early tags do not contain 'tagger' lines; warn only */ - ret = report(options, &tag->object, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); + ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line"); if (ret) goto done; } else - ret = fsck_ident(&buffer, &tag->object, options); + ret = fsck_ident(&buffer, oid, OBJ_TAG, options); done: strbuf_release(&sb); - free(to_free); return ret; } -static int fsck_tag(struct tag *tag, const char *data, - unsigned long size, struct fsck_options *options) -{ - struct object *tagged = tag->tagged; - - if (!tagged) - return report(options, &tag->object, FSCK_MSG_BAD_TAG_OBJECT, "could not load tagged object"); - - return fsck_tag_buffer(tag, data, size, options); -} - struct fsck_gitmodules_data { - struct object *obj; + const struct object_id *oid; struct fsck_options *options; int ret; }; @@ -949,19 +912,22 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) name = xmemdupz(subsection, subsection_len); if (check_submodule_name(name) < 0) - data->ret |= report(data->options, data->obj, + data->ret |= report(data->options, + data->oid, OBJ_BLOB, FSCK_MSG_GITMODULES_NAME, "disallowed submodule name: %s", name); if (!strcmp(key, "url") && value && looks_like_command_line_option(value)) - data->ret |= report(data->options, data->obj, + data->ret |= report(data->options, + data->oid, OBJ_BLOB, FSCK_MSG_GITMODULES_URL, "disallowed submodule url: %s", value); if (!strcmp(key, "path") && value && looks_like_command_line_option(value)) - data->ret |= report(data->options, data->obj, + data->ret |= report(data->options, + data->oid, OBJ_BLOB, FSCK_MSG_GITMODULES_PATH, "disallowed submodule path: %s", value); @@ -970,17 +936,17 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) return 0; } -static int fsck_blob(struct blob *blob, const char *buf, +static int fsck_blob(const struct object_id *oid, const char *buf, unsigned long size, struct fsck_options *options) { struct fsck_gitmodules_data data; struct config_options config_opts = { 0 }; - if (!oidset_contains(&gitmodules_found, &blob->object.oid)) + if (!oidset_contains(&gitmodules_found, oid)) return 0; - oidset_insert(&gitmodules_done, &blob->object.oid); + oidset_insert(&gitmodules_done, oid); - if (object_on_skiplist(options, &blob->object)) + if (object_on_skiplist(options, oid)) return 0; if (!buf) { @@ -989,18 +955,18 @@ static int fsck_blob(struct blob *blob, const char *buf, * blob too gigantic to load into memory. Let's just consider * that an error. */ - return report(options, &blob->object, + return report(options, oid, OBJ_BLOB, FSCK_MSG_GITMODULES_LARGE, ".gitmodules too large to parse"); } - data.obj = &blob->object; + data.oid = oid; data.options = options; data.ret = 0; config_opts.error_action = CONFIG_ERROR_SILENT; if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB, ".gitmodules", buf, size, &data, &config_opts)) - data.ret |= report(options, &blob->object, + data.ret |= report(options, oid, OBJ_BLOB, FSCK_MSG_GITMODULES_PARSE, "could not parse gitmodules blob"); @@ -1011,31 +977,33 @@ int fsck_object(struct object *obj, void *data, unsigned long size, struct fsck_options *options) { if (!obj) - return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); + return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck"); if (obj->type == OBJ_BLOB) - return fsck_blob((struct blob *)obj, data, size, options); + return fsck_blob(&obj->oid, data, size, options); if (obj->type == OBJ_TREE) - return fsck_tree((struct tree *) obj, options); + return fsck_tree(&obj->oid, data, size, options); if (obj->type == OBJ_COMMIT) - return fsck_commit((struct commit *) obj, (const char *) data, - size, options); + return fsck_commit(&obj->oid, data, size, options); if (obj->type == OBJ_TAG) - return fsck_tag((struct tag *) obj, (const char *) data, - size, options); + return fsck_tag(&obj->oid, data, size, options); - return report(options, obj, FSCK_MSG_UNKNOWN_TYPE, "unknown type '%d' (internal fsck error)", - obj->type); + return report(options, &obj->oid, obj->type, + FSCK_MSG_UNKNOWN_TYPE, + "unknown type '%d' (internal fsck error)", + obj->type); } int fsck_error_function(struct fsck_options *o, - struct object *obj, int msg_type, const char *message) + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) { if (msg_type == FSCK_WARN) { - warning("object %s: %s", describe_object(o, obj), message); + warning("object %s: %s", fsck_describe_object(o, oid), message); return 0; } - error("object %s: %s", describe_object(o, obj), message); + error("object %s: %s", fsck_describe_object(o, oid), message); return 1; } @@ -1047,7 +1015,6 @@ int fsck_finish(struct fsck_options *options) oidset_iter_init(&gitmodules_found, &iter); while ((oid = oidset_iter_next(&iter))) { - struct blob *blob; enum object_type type; unsigned long size; char *buf; @@ -1055,29 +1022,22 @@ int fsck_finish(struct fsck_options *options) if (oidset_contains(&gitmodules_done, oid)) continue; - blob = lookup_blob(the_repository, oid); - if (!blob) { - struct object *obj = lookup_unknown_object(oid); - ret |= report(options, obj, - FSCK_MSG_GITMODULES_BLOB, - "non-blob found at .gitmodules"); - continue; - } - buf = read_object_file(oid, &type, &size); if (!buf) { - if (is_promisor_object(&blob->object.oid)) + if (is_promisor_object(oid)) continue; - ret |= report(options, &blob->object, + ret |= report(options, + oid, OBJ_BLOB, FSCK_MSG_GITMODULES_MISSING, "unable to read .gitmodules blob"); continue; } if (type == OBJ_BLOB) - ret |= fsck_blob(blob, buf, size, options); + ret |= fsck_blob(oid, buf, size, options); else - ret |= report(options, &blob->object, + ret |= report(options, + oid, type, FSCK_MSG_GITMODULES_BLOB, "non-blob found at .gitmodules"); free(buf); @@ -27,10 +27,12 @@ typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct f /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ typedef int (*fsck_error)(struct fsck_options *o, - struct object *obj, int type, const char *message); + const struct object_id *oid, enum object_type object_type, + int msg_type, const char *message); int fsck_error_function(struct fsck_options *o, - struct object *obj, int type, const char *message); + const struct object_id *oid, enum object_type object_type, + int msg_type, const char *message); struct fsck_options { fsck_walk_func walk; @@ -38,7 +40,7 @@ struct fsck_options { unsigned strict:1; int *msg_type; struct oidset skiplist; - struct decoration *object_names; + kh_oid_map_t *object_names; }; #define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL, OIDSET_INIT } @@ -52,7 +54,11 @@ struct fsck_options { * 0 everything OK */ int fsck_walk(struct object *obj, void *data, struct fsck_options *options); -/* If NULL is passed for data, we assume the object is local and read it. */ + +/* + * Blob objects my pass a NULL data pointer, which indicates they are too large + * to fit in memory. All other types must pass a real buffer. + */ int fsck_object(struct object *obj, void *data, unsigned long size, struct fsck_options *options); @@ -63,4 +69,29 @@ int fsck_object(struct object *obj, void *data, unsigned long size, */ int fsck_finish(struct fsck_options *options); +/* + * Subsystem for storing human-readable names for each object. + * + * If fsck_enable_object_names() has not been called, all other functions are + * noops. + * + * Use fsck_put_object_name() to seed initial names (e.g. from refnames); the + * fsck code will extend that while walking trees, etc. + * + * Use fsck_get_object_name() to get a single name (or NULL if none). Or the + * more convenient describe_object(), which always produces an output string + * with the oid combined with the name (if any). Note that the return value + * points to a rotating array of static buffers, and may be invalidated by a + * subsequent call. + */ +void fsck_enable_object_names(struct fsck_options *options); +const char *fsck_get_object_name(struct fsck_options *options, + const struct object_id *oid); +__attribute__((format (printf,3,4))) +void fsck_put_object_name(struct fsck_options *options, + const struct object_id *oid, + const char *fmt, ...); +const char *fsck_describe_object(struct fsck_options *options, + const struct object_id *oid); + #endif diff --git a/git-legacy-stash.sh b/git-legacy-stash.sh index 07ad4a5459..53fa574301 100755 --- a/git-legacy-stash.sh +++ b/git-legacy-stash.sh @@ -193,7 +193,8 @@ create_stash () { GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" && - git update-index -z --add --remove --stdin <"$TMP-stagenames" && + git update-index --ignore-skip-worktree-entries \ + -z --add --remove --stdin <"$TMP-stagenames" && git write-tree && rm -f "$TMPindex" ) ) || @@ -112,14 +112,42 @@ static const char *column_get_color_code(unsigned short color) return column_colors[color]; } -static void strbuf_write_column(struct strbuf *sb, const struct column *c, - char col_char) +struct graph_line { + struct strbuf *buf; + size_t width; +}; + +static inline void graph_line_addch(struct graph_line *line, int c) +{ + strbuf_addch(line->buf, c); + line->width++; +} + +static inline void graph_line_addchars(struct graph_line *line, int c, size_t n) +{ + strbuf_addchars(line->buf, c, n); + line->width += n; +} + +static inline void graph_line_addstr(struct graph_line *line, const char *s) +{ + strbuf_addstr(line->buf, s); + line->width += strlen(s); +} + +static inline void graph_line_addcolor(struct graph_line *line, unsigned short color) +{ + strbuf_addstr(line->buf, column_get_color_code(color)); +} + +static void graph_line_write_column(struct graph_line *line, const struct column *c, + char col_char) { if (c->color < column_colors_max) - strbuf_addstr(sb, column_get_color_code(c->color)); - strbuf_addch(sb, col_char); + graph_line_addcolor(line, c->color); + graph_line_addch(line, col_char); if (c->color < column_colors_max) - strbuf_addstr(sb, column_get_color_code(column_colors_max)); + graph_line_addcolor(line, column_colors_max); } struct git_graph { @@ -175,9 +203,63 @@ struct git_graph { */ int prev_commit_index; /* + * Which layout variant to use to display merge commits. If the + * commit's first parent is known to be in a column to the left of the + * merge, then this value is 0 and we use the layout on the left. + * Otherwise, the value is 1 and the layout on the right is used. This + * field tells us how many columns the first parent occupies. + * + * 0) 1) + * + * | | | *-. | | *---. + * | |_|/|\ \ | | |\ \ \ + * |/| | | | | | | | | | * + */ + int merge_layout; + /* + * The number of columns added to the graph by the current commit. For + * 2-way and octopus merges, this is is usually one less than the + * number of parents: + * + * | | | | | \ + * | * | | *---. \ + * | |\ \ | |\ \ \ \ + * | | | | | | | | | | + * + * num_parents: 2 num_parents: 4 + * edges_added: 1 edges_added: 3 + * + * For left-skewed merges, the first parent fuses with its neighbor and + * so one less column is added: + * + * | | | | | \ + * | * | | *-. \ + * |/| | |/|\ \ \ + * | | | | | | | | + * + * num_parents: 2 num_parents: 4 + * edges_added: 0 edges_added: 2 + * + * This number determines how edges to the right of the merge are + * displayed in commit and post-merge lines; if no columns have been + * added then a vertical line should be used where a right-tracking + * line would otherwise be used. + * + * | * \ | * | + * | |\ \ |/| | + * | | * \ | * | + */ + int edges_added; + /* + * The number of columns added by the previous commit, which is used to + * smooth edges appearing to the right of a commit in a commit line + * following a post-merge line. + */ + int prev_edges_added; + /* * The maximum number of columns that can be stored in the columns * and new_columns arrays. This is also half the number of entries - * that can be stored in the mapping and new_mapping arrays. + * that can be stored in the mapping and old_mapping arrays. */ int column_capacity; /* @@ -215,12 +297,12 @@ struct git_graph { */ int *mapping; /* - * A temporary array for computing the next mapping state - * while we are outputting a mapping line. This is stored as part - * of the git_graph simply so we don't have to allocate a new - * temporary array each time we have to output a collapsing line. + * A copy of the contents of the mapping array from the last commit, + * which we use to improve the display of columns that are tracking + * from right to left through a commit line. We also use this to + * avoid allocating a fresh array when we compute the next mapping. */ - int *new_mapping; + int *old_mapping; /* * The current default column color being used. This is * stored as an index into the array column_colors. @@ -285,6 +367,9 @@ struct git_graph *graph_init(struct rev_info *opt) graph->prev_state = GRAPH_PADDING; graph->commit_index = 0; graph->prev_commit_index = 0; + graph->merge_layout = 0; + graph->edges_added = 0; + graph->prev_edges_added = 0; graph->num_columns = 0; graph->num_new_columns = 0; graph->mapping_size = 0; @@ -303,7 +388,7 @@ struct git_graph *graph_init(struct rev_info *opt) ALLOC_ARRAY(graph->columns, graph->column_capacity); ALLOC_ARRAY(graph->new_columns, graph->column_capacity); ALLOC_ARRAY(graph->mapping, 2 * graph->column_capacity); - ALLOC_ARRAY(graph->new_mapping, 2 * graph->column_capacity); + ALLOC_ARRAY(graph->old_mapping, 2 * graph->column_capacity); /* * The diff output prefix callback, with this we can make @@ -333,7 +418,7 @@ static void graph_ensure_capacity(struct git_graph *graph, int num_columns) REALLOC_ARRAY(graph->columns, graph->column_capacity); REALLOC_ARRAY(graph->new_columns, graph->column_capacity); REALLOC_ARRAY(graph->mapping, graph->column_capacity * 2); - REALLOC_ARRAY(graph->new_mapping, graph->column_capacity * 2); + REALLOC_ARRAY(graph->old_mapping, graph->column_capacity * 2); } /* @@ -432,74 +517,76 @@ static unsigned short graph_find_commit_color(const struct git_graph *graph, return graph_get_current_column_color(graph); } -static void graph_insert_into_new_columns(struct git_graph *graph, - struct commit *commit, - int *mapping_index) +static int graph_find_new_column_by_commit(struct git_graph *graph, + struct commit *commit) { int i; - - /* - * If the commit is already in the new_columns list, we don't need to - * add it. Just update the mapping correctly. - */ for (i = 0; i < graph->num_new_columns; i++) { - if (graph->new_columns[i].commit == commit) { - graph->mapping[*mapping_index] = i; - *mapping_index += 2; - return; - } + if (graph->new_columns[i].commit == commit) + return i; } - - /* - * This commit isn't already in new_columns. Add it. - */ - graph->new_columns[graph->num_new_columns].commit = commit; - graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit); - graph->mapping[*mapping_index] = graph->num_new_columns; - *mapping_index += 2; - graph->num_new_columns++; + return -1; } -static void graph_update_width(struct git_graph *graph, - int is_commit_in_existing_columns) +static void graph_insert_into_new_columns(struct git_graph *graph, + struct commit *commit, + int idx) { - /* - * Compute the width needed to display the graph for this commit. - * This is the maximum width needed for any row. All other rows - * will be padded to this width. - * - * Compute the number of columns in the widest row: - * Count each existing column (graph->num_columns), and each new - * column added by this commit. - */ - int max_cols = graph->num_columns + graph->num_parents; + int i = graph_find_new_column_by_commit(graph, commit); + int mapping_idx; /* - * Even if the current commit has no parents to be printed, it - * still takes up a column for itself. + * If the commit is not already in the new_columns array, then add it + * and record it as being in the final column. */ - if (graph->num_parents < 1) - max_cols++; + if (i < 0) { + i = graph->num_new_columns++; + graph->new_columns[i].commit = commit; + graph->new_columns[i].color = graph_find_commit_color(graph, commit); + } - /* - * We added a column for the current commit as part of - * graph->num_parents. If the current commit was already in - * graph->columns, then we have double counted it. - */ - if (is_commit_in_existing_columns) - max_cols--; + if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) { + /* + * If this is the first parent of a merge, choose a layout for + * the merge line based on whether the parent appears in a + * column to the left of the merge + */ + int dist, shift; - /* - * Each column takes up 2 spaces - */ - graph->width = max_cols * 2; + dist = idx - i; + shift = (dist > 1) ? 2 * dist - 3 : 1; + + graph->merge_layout = (dist > 0) ? 0 : 1; + graph->edges_added = graph->num_parents + graph->merge_layout - 2; + + mapping_idx = graph->width + (graph->merge_layout - 1) * shift; + graph->width += 2 * graph->merge_layout; + + } else if (graph->edges_added > 0 && i == graph->mapping[graph->width - 2]) { + /* + * If some columns have been added by a merge, but this commit + * was found in the last existing column, then adjust the + * numbers so that the two edges immediately join, i.e.: + * + * * | * | + * |\ \ => |\| + * | |/ | * + * | * + */ + mapping_idx = graph->width - 2; + graph->edges_added = -1; + } else { + mapping_idx = graph->width; + graph->width += 2; + } + + graph->mapping[mapping_idx] = i; } static void graph_update_columns(struct git_graph *graph) { struct commit_list *parent; int max_new_columns; - int mapping_idx; int i, seen_this, is_commit_in_columns; /* @@ -532,6 +619,10 @@ static void graph_update_columns(struct git_graph *graph) for (i = 0; i < graph->mapping_size; i++) graph->mapping[i] = -1; + graph->width = 0; + graph->prev_edges_added = graph->edges_added; + graph->edges_added = 0; + /* * Populate graph->new_columns and graph->mapping * @@ -542,7 +633,6 @@ static void graph_update_columns(struct git_graph *graph) * supposed to end up after the collapsing is performed. */ seen_this = 0; - mapping_idx = 0; is_commit_in_columns = 1; for (i = 0; i <= graph->num_columns; i++) { struct commit *col_commit; @@ -556,9 +646,9 @@ static void graph_update_columns(struct git_graph *graph) } if (col_commit == graph->commit) { - int old_mapping_idx = mapping_idx; seen_this = 1; graph->commit_index = i; + graph->merge_layout = -1; for (parent = first_interesting_parent(graph); parent; parent = next_interesting_parent(graph, parent)) { @@ -571,21 +661,18 @@ static void graph_update_columns(struct git_graph *graph) !is_commit_in_columns) { graph_increment_column_color(graph); } - graph_insert_into_new_columns(graph, - parent->item, - &mapping_idx); + graph_insert_into_new_columns(graph, parent->item, i); } /* - * We always need to increment mapping_idx by at + * We always need to increment graph->width by at * least 2, even if it has no interesting parents. * The current commit always takes up at least 2 * spaces. */ - if (mapping_idx == old_mapping_idx) - mapping_idx += 2; + if (graph->num_parents == 0) + graph->width += 2; } else { - graph_insert_into_new_columns(graph, col_commit, - &mapping_idx); + graph_insert_into_new_columns(graph, col_commit, -1); } } @@ -595,11 +682,43 @@ static void graph_update_columns(struct git_graph *graph) while (graph->mapping_size > 1 && graph->mapping[graph->mapping_size - 1] < 0) graph->mapping_size--; +} + +static int graph_num_dashed_parents(struct git_graph *graph) +{ + return graph->num_parents + graph->merge_layout - 3; +} +static int graph_num_expansion_rows(struct git_graph *graph) +{ /* - * Compute graph->width for this commit + * Normally, we need two expansion rows for each dashed parent line from + * an octopus merge: + * + * | * + * | |\ + * | | \ + * | | \ + * | *-. \ + * | |\ \ \ + * + * If the merge is skewed to the left, then its parents occupy one less + * column, and we don't need as many expansion rows to route around it; + * in some cases that means we don't need any expansion rows at all: + * + * | * + * | |\ + * | * \ + * |/|\ \ */ - graph_update_width(graph, is_commit_in_columns); + return graph_num_dashed_parents(graph) * 2; +} + +static int graph_needs_pre_commit_line(struct git_graph *graph) +{ + return graph->num_parents >= 3 && + graph->commit_index < (graph->num_columns - 1) && + graph->expansion_row < graph_num_expansion_rows(graph); } void graph_update(struct git_graph *graph, struct commit *commit) @@ -657,8 +776,7 @@ void graph_update(struct git_graph *graph, struct commit *commit) */ if (graph->state != GRAPH_PADDING) graph->state = GRAPH_SKIP; - else if (graph->num_parents >= 3 && - graph->commit_index < (graph->num_columns - 1)) + else if (graph_needs_pre_commit_line(graph)) graph->state = GRAPH_PRE_COMMIT; else graph->state = GRAPH_COMMIT; @@ -686,8 +804,7 @@ static int graph_is_mapping_correct(struct git_graph *graph) return 1; } -static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, - int chars_written) +static void graph_pad_horizontally(struct git_graph *graph, struct graph_line *line) { /* * Add additional spaces to the end of the strbuf, so that all @@ -696,34 +813,22 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, * This way, fields printed to the right of the graph will remain * aligned for the entire commit. */ - if (chars_written < graph->width) - strbuf_addchars(sb, ' ', graph->width - chars_written); + if (line->width < graph->width) + graph_line_addchars(line, ' ', graph->width - line->width); } static void graph_output_padding_line(struct git_graph *graph, - struct strbuf *sb) + struct graph_line *line) { int i; /* - * We could conceivable be called with a NULL commit - * if our caller has a bug, and invokes graph_next_line() - * immediately after graph_init(), without first calling - * graph_update(). Return without outputting anything in this - * case. - */ - if (!graph->commit) - return; - - /* * Output a padding row, that leaves all branch lines unchanged */ for (i = 0; i < graph->num_new_columns; i++) { - strbuf_write_column(sb, &graph->new_columns[i], '|'); - strbuf_addch(sb, ' '); + graph_line_write_column(line, &graph->new_columns[i], '|'); + graph_line_addch(line, ' '); } - - graph_pad_horizontally(graph, sb, graph->num_new_columns * 2); } @@ -733,28 +838,24 @@ int graph_width(struct git_graph *graph) } -static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_skip_line(struct git_graph *graph, struct graph_line *line) { /* * Output an ellipsis to indicate that a portion * of the graph is missing. */ - strbuf_addstr(sb, "..."); - graph_pad_horizontally(graph, sb, 3); + graph_line_addstr(line, "..."); - if (graph->num_parents >= 3 && - graph->commit_index < (graph->num_columns - 1)) + if (graph_needs_pre_commit_line(graph)) graph_update_state(graph, GRAPH_PRE_COMMIT); else graph_update_state(graph, GRAPH_COMMIT); } static void graph_output_pre_commit_line(struct git_graph *graph, - struct strbuf *sb) + struct graph_line *line) { - int num_expansion_rows; int i, seen_this; - int chars_written; /* * This function formats a row that increases the space around a commit @@ -764,27 +865,24 @@ static void graph_output_pre_commit_line(struct git_graph *graph, * We need 2 extra rows for every parent over 2. */ assert(graph->num_parents >= 3); - num_expansion_rows = (graph->num_parents - 2) * 2; /* * graph->expansion_row tracks the current expansion row we are on. * It should be in the range [0, num_expansion_rows - 1] */ assert(0 <= graph->expansion_row && - graph->expansion_row < num_expansion_rows); + graph->expansion_row < graph_num_expansion_rows(graph)); /* * Output the row */ seen_this = 0; - chars_written = 0; for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; if (col->commit == graph->commit) { seen_this = 1; - strbuf_write_column(sb, col, '|'); - strbuf_addchars(sb, ' ', graph->expansion_row); - chars_written += 1 + graph->expansion_row; + graph_line_write_column(line, col, '|'); + graph_line_addchars(line, ' ', graph->expansion_row); } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -797,33 +895,27 @@ static void graph_output_pre_commit_line(struct git_graph *graph, */ if (graph->prev_state == GRAPH_POST_MERGE && graph->prev_commit_index < i) - strbuf_write_column(sb, col, '\\'); + graph_line_write_column(line, col, '\\'); else - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_line_write_column(line, col, '|'); } else if (seen_this && (graph->expansion_row > 0)) { - strbuf_write_column(sb, col, '\\'); - chars_written++; + graph_line_write_column(line, col, '\\'); } else { - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_line_write_column(line, col, '|'); } - strbuf_addch(sb, ' '); - chars_written++; + graph_line_addch(line, ' '); } - graph_pad_horizontally(graph, sb, chars_written); - /* * Increment graph->expansion_row, * and move to state GRAPH_COMMIT if necessary */ graph->expansion_row++; - if (graph->expansion_row >= num_expansion_rows) + if (!graph_needs_pre_commit_line(graph)) graph_update_state(graph, GRAPH_COMMIT); } -static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) +static void graph_output_commit_char(struct git_graph *graph, struct graph_line *line) { /* * For boundary commits, print 'o' @@ -831,72 +923,67 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) */ if (graph->commit->object.flags & BOUNDARY) { assert(graph->revs->boundary); - strbuf_addch(sb, 'o'); + graph_line_addch(line, 'o'); return; } /* * get_revision_mark() handles all other cases without assert() */ - strbuf_addstr(sb, get_revision_mark(graph->revs, graph->commit)); + graph_line_addstr(line, get_revision_mark(graph->revs, graph->commit)); } /* - * Draw the horizontal dashes of an octopus merge and return the number of - * characters written. + * Draw the horizontal dashes of an octopus merge. */ -static int graph_draw_octopus_merge(struct git_graph *graph, - struct strbuf *sb) +static void graph_draw_octopus_merge(struct git_graph *graph, struct graph_line *line) { /* - * Here dashless_parents represents the number of parents which don't - * need to have dashes (the edges labeled "0" and "1"). And - * dashful_parents are the remaining ones. + * The parents of a merge commit can be arbitrarily reordered as they + * are mapped onto display columns, for example this is a valid merge: * - * | *---. - * | |\ \ \ - * | | | | | - * x 0 1 2 3 + * | | *---. + * | | |\ \ \ + * | | |/ / / + * | |/| | / + * | |_|_|/ + * |/| | | + * 3 1 0 2 * - */ - const int dashless_parents = 2; - int dashful_parents = graph->num_parents - dashless_parents; - - /* - * Usually, we add one new column for each parent (like the diagram - * above) but sometimes the first parent goes into an existing column, - * like this: + * The numbers denote which parent of the merge each visual column + * corresponds to; we can't assume that the parents will initially + * display in the order given by new_columns. * - * | *---. - * | |\ \ \ - * |/ / / / - * x 0 1 2 + * To find the right color for each dash, we need to consult the + * mapping array, starting from the column 2 places to the right of the + * merge commit, and use that to find out which logical column each + * edge will collapse to. * - * In which case the number of parents will be one greater than the - * number of added columns. + * Commits are rendered once all edges have collapsed to their correct + * logcial column, so commit_index gives us the right visual offset for + * the merge commit. */ - int added_cols = (graph->num_new_columns - graph->num_columns); - int parent_in_old_cols = graph->num_parents - added_cols; - /* - * In both cases, commit_index corresponds to the edge labeled "0". - */ - int first_col = graph->commit_index + dashless_parents - - parent_in_old_cols; + int i, j; + struct column *col; - int i; - for (i = 0; i < dashful_parents; i++) { - strbuf_write_column(sb, &graph->new_columns[i+first_col], '-'); - strbuf_write_column(sb, &graph->new_columns[i+first_col], - i == dashful_parents-1 ? '.' : '-'); + int dashed_parents = graph_num_dashed_parents(graph); + + for (i = 0; i < dashed_parents; i++) { + j = graph->mapping[(graph->commit_index + i + 2) * 2]; + col = &graph->new_columns[j]; + + graph_line_write_column(line, col, '-'); + graph_line_write_column(line, col, (i == dashed_parents - 1) ? '.' : '-'); } - return 2 * dashful_parents; + + return; } -static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_commit_line(struct git_graph *graph, struct graph_line *line) { int seen_this = 0; - int i, chars_written; + int i; /* * Output the row containing this commit @@ -906,7 +993,6 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) * children that we have already processed.) */ seen_this = 0; - chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -920,19 +1006,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) if (col_commit == graph->commit) { seen_this = 1; - graph_output_commit_char(graph, sb); - chars_written++; + graph_output_commit_char(graph, line); if (graph->num_parents > 2) - chars_written += graph_draw_octopus_merge(graph, - sb); - } else if (seen_this && (graph->num_parents > 2)) { - strbuf_write_column(sb, col, '\\'); - chars_written++; - } else if (seen_this && (graph->num_parents == 2)) { + graph_draw_octopus_merge(graph, line); + } else if (seen_this && (graph->edges_added > 1)) { + graph_line_write_column(line, col, '\\'); + } else if (seen_this && (graph->edges_added == 1)) { /* - * This is a 2-way merge commit. - * There is no GRAPH_PRE_COMMIT stage for 2-way + * This is either a right-skewed 2-way merge + * commit, or a left-skewed 3-way merge. + * There is no GRAPH_PRE_COMMIT stage for such * merges, so this is the first line of output * for this commit. Check to see what the previous * line of output was. @@ -944,21 +1028,21 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) * makes the output look nicer. */ if (graph->prev_state == GRAPH_POST_MERGE && + graph->prev_edges_added > 0 && graph->prev_commit_index < i) - strbuf_write_column(sb, col, '\\'); + graph_line_write_column(line, col, '\\'); else - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_line_write_column(line, col, '|'); + } else if (graph->prev_state == GRAPH_COLLAPSING && + graph->old_mapping[2 * i + 1] == i && + graph->mapping[2 * i] < i) { + graph_line_write_column(line, col, '/'); } else { - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_line_write_column(line, col, '|'); } - strbuf_addch(sb, ' '); - chars_written++; + graph_line_addch(line, ' '); } - graph_pad_horizontally(graph, sb, chars_written); - /* * Update graph->state */ @@ -970,26 +1054,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) graph_update_state(graph, GRAPH_COLLAPSING); } -static struct column *find_new_column_by_commit(struct git_graph *graph, - struct commit *commit) -{ - int i; - for (i = 0; i < graph->num_new_columns; i++) { - if (graph->new_columns[i].commit == commit) - return &graph->new_columns[i]; - } - return NULL; -} +const char merge_chars[] = {'/', '|', '\\'}; -static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line) { int seen_this = 0; - int i, j, chars_written; + int i, j; + + struct commit_list *first_parent = first_interesting_parent(graph); + int seen_parent = 0; /* * Output the post-merge row */ - chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { struct column *col = &graph->columns[i]; struct commit *col_commit; @@ -1008,37 +1085,44 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf * new_columns and use those to format the * edges. */ - struct commit_list *parents = NULL; - struct column *par_column; + struct commit_list *parents = first_parent; + int par_column; + int idx = graph->merge_layout; + char c; seen_this = 1; - parents = first_interesting_parent(graph); - assert(parents); - par_column = find_new_column_by_commit(graph, parents->item); - assert(par_column); - - strbuf_write_column(sb, par_column, '|'); - chars_written++; - for (j = 0; j < graph->num_parents - 1; j++) { + + for (j = 0; j < graph->num_parents; j++) { + par_column = graph_find_new_column_by_commit(graph, parents->item); + assert(par_column >= 0); + + c = merge_chars[idx]; + graph_line_write_column(line, &graph->new_columns[par_column], c); + if (idx == 2) { + if (graph->edges_added > 0 || j < graph->num_parents - 1) + graph_line_addch(line, ' '); + } else { + idx++; + } parents = next_interesting_parent(graph, parents); - assert(parents); - par_column = find_new_column_by_commit(graph, parents->item); - assert(par_column); - strbuf_write_column(sb, par_column, '\\'); - strbuf_addch(sb, ' '); } - chars_written += j * 2; + if (graph->edges_added == 0) + graph_line_addch(line, ' '); + } else if (seen_this) { - strbuf_write_column(sb, col, '\\'); - strbuf_addch(sb, ' '); - chars_written += 2; + if (graph->edges_added > 0) + graph_line_write_column(line, col, '\\'); + else + graph_line_write_column(line, col, '|'); + graph_line_addch(line, ' '); } else { - strbuf_write_column(sb, col, '|'); - strbuf_addch(sb, ' '); - chars_written += 2; + graph_line_write_column(line, col, '|'); + if (graph->merge_layout != 0 || i != graph->commit_index - 1) + graph_line_addch(line, seen_parent ? '_' : ' '); } - } - graph_pad_horizontally(graph, sb, chars_written); + if (col_commit == first_parent->item) + seen_parent = 1; + } /* * Update graph->state @@ -1049,7 +1133,7 @@ static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf graph_update_state(graph, GRAPH_COLLAPSING); } -static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb) +static void graph_output_collapsing_line(struct git_graph *graph, struct graph_line *line) { int i; short used_horizontal = 0; @@ -1057,13 +1141,18 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf int horizontal_edge_target = -1; /* - * Clear out the new_mapping array + * Swap the mapping and old_mapping arrays + */ + SWAP(graph->mapping, graph->old_mapping); + + /* + * Clear out the mapping array */ for (i = 0; i < graph->mapping_size; i++) - graph->new_mapping[i] = -1; + graph->mapping[i] = -1; for (i = 0; i < graph->mapping_size; i++) { - int target = graph->mapping[i]; + int target = graph->old_mapping[i]; if (target < 0) continue; @@ -1084,14 +1173,14 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * This column is already in the * correct place */ - assert(graph->new_mapping[i] == -1); - graph->new_mapping[i] = target; - } else if (graph->new_mapping[i - 1] < 0) { + assert(graph->mapping[i] == -1); + graph->mapping[i] = target; + } else if (graph->mapping[i - 1] < 0) { /* * Nothing is to the left. * Move to the left by one */ - graph->new_mapping[i - 1] = target; + graph->mapping[i - 1] = target; /* * If there isn't already an edge moving horizontally * select this one. @@ -1107,9 +1196,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * line. */ for (j = (target * 2)+3; j < (i - 2); j += 2) - graph->new_mapping[j] = target; + graph->mapping[j] = target; } - } else if (graph->new_mapping[i - 1] == target) { + } else if (graph->mapping[i - 1] == target) { /* * There is a branch line to our left * already, and it is our target. We @@ -1117,7 +1206,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * the same parent commit. * * We don't have to add anything to the - * output or new_mapping, since the + * output or mapping, since the * existing branch line has already taken * care of it. */ @@ -1133,10 +1222,10 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * The branch to the left of that space * should be our eventual target. */ - assert(graph->new_mapping[i - 1] > target); - assert(graph->new_mapping[i - 2] < 0); - assert(graph->new_mapping[i - 3] == target); - graph->new_mapping[i - 2] = target; + assert(graph->mapping[i - 1] > target); + assert(graph->mapping[i - 2] < 0); + assert(graph->mapping[i - 3] == target); + graph->mapping[i - 2] = target; /* * Mark this branch as the horizontal edge to * prevent any other edges from moving @@ -1148,20 +1237,25 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf } /* + * Copy the current mapping array into old_mapping + */ + COPY_ARRAY(graph->old_mapping, graph->mapping, graph->mapping_size); + + /* * The new mapping may be 1 smaller than the old mapping */ - if (graph->new_mapping[graph->mapping_size - 1] < 0) + if (graph->mapping[graph->mapping_size - 1] < 0) graph->mapping_size--; /* * Output out a line based on the new mapping info */ for (i = 0; i < graph->mapping_size; i++) { - int target = graph->new_mapping[i]; + int target = graph->mapping[i]; if (target < 0) - strbuf_addch(sb, ' '); + graph_line_addch(line, ' '); else if (target * 2 == i) - strbuf_write_column(sb, &graph->new_columns[target], '|'); + graph_line_write_column(line, &graph->new_columns[target], '|'); else if (target == horizontal_edge_target && i != horizontal_edge - 1) { /* @@ -1170,24 +1264,17 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * won't continue into the next line. */ if (i != (target * 2)+3) - graph->new_mapping[i] = -1; + graph->mapping[i] = -1; used_horizontal = 1; - strbuf_write_column(sb, &graph->new_columns[target], '_'); + graph_line_write_column(line, &graph->new_columns[target], '_'); } else { if (used_horizontal && i < horizontal_edge) - graph->new_mapping[i] = -1; - strbuf_write_column(sb, &graph->new_columns[target], '/'); + graph->mapping[i] = -1; + graph_line_write_column(line, &graph->new_columns[target], '/'); } } - graph_pad_horizontally(graph, sb, graph->mapping_size); - - /* - * Swap mapping and new_mapping - */ - SWAP(graph->mapping, graph->new_mapping); - /* * If graph->mapping indicates that all of the branch lines * are already in the correct positions, we are done. @@ -1199,35 +1286,49 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf int graph_next_line(struct git_graph *graph, struct strbuf *sb) { + int shown_commit_line = 0; + struct graph_line line = { .buf = sb, .width = 0 }; + + /* + * We could conceivable be called with a NULL commit + * if our caller has a bug, and invokes graph_next_line() + * immediately after graph_init(), without first calling + * graph_update(). Return without outputting anything in this + * case. + */ + if (!graph->commit) + return -1; + switch (graph->state) { case GRAPH_PADDING: - graph_output_padding_line(graph, sb); - return 0; + graph_output_padding_line(graph, &line); + break; case GRAPH_SKIP: - graph_output_skip_line(graph, sb); - return 0; + graph_output_skip_line(graph, &line); + break; case GRAPH_PRE_COMMIT: - graph_output_pre_commit_line(graph, sb); - return 0; + graph_output_pre_commit_line(graph, &line); + break; case GRAPH_COMMIT: - graph_output_commit_line(graph, sb); - return 1; + graph_output_commit_line(graph, &line); + shown_commit_line = 1; + break; case GRAPH_POST_MERGE: - graph_output_post_merge_line(graph, sb); - return 0; + graph_output_post_merge_line(graph, &line); + break; case GRAPH_COLLAPSING: - graph_output_collapsing_line(graph, sb); - return 0; + graph_output_collapsing_line(graph, &line); + break; } - assert(0); - return 0; + graph_pad_horizontally(graph, &line); + return shown_commit_line; } static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) { int i; - int chars_written = 0; + struct graph_line line = { .buf = sb, .width = 0 }; if (graph->state != GRAPH_COMMIT) { graph_next_line(graph, sb); @@ -1244,20 +1345,17 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; - strbuf_write_column(sb, col, '|'); - chars_written++; + graph_line_write_column(&line, col, '|'); if (col->commit == graph->commit && graph->num_parents > 2) { int len = (graph->num_parents - 2) * 2; - strbuf_addchars(sb, ' ', len); - chars_written += len; + graph_line_addchars(&line, ' ', len); } else { - strbuf_addch(sb, ' '); - chars_written++; + graph_line_addch(&line, ' '); } } - graph_pad_horizontally(graph, sb, chars_written); + graph_pad_horizontally(graph, &line); /* * Update graph->prev_state since we have output a padding line @@ -150,7 +150,7 @@ static unsigned long empty_auth_useless = static struct curl_slist *pragma_header; static struct curl_slist *no_pragma_header; -static struct curl_slist *extra_http_headers; +static struct string_list extra_http_headers = STRING_LIST_INIT_DUP; static struct active_request_slot *active_queue_head; @@ -414,11 +414,9 @@ static int http_options(const char *var, const char *value, void *cb) if (!value) { return config_error_nonbool(var); } else if (!*value) { - curl_slist_free_all(extra_http_headers); - extra_http_headers = NULL; + string_list_clear(&extra_http_headers, 0); } else { - extra_http_headers = - curl_slist_append(extra_http_headers, value); + string_list_append(&extra_http_headers, value); } return 0; } @@ -1202,8 +1200,7 @@ void http_cleanup(void) #endif curl_global_cleanup(); - curl_slist_free_all(extra_http_headers); - extra_http_headers = NULL; + string_list_clear(&extra_http_headers, 0); curl_slist_free_all(pragma_header); pragma_header = NULL; @@ -1627,10 +1624,11 @@ int run_one_slot(struct active_request_slot *slot, struct curl_slist *http_copy_default_headers(void) { - struct curl_slist *headers = NULL, *h; + struct curl_slist *headers = NULL; + const struct string_list_item *item; - for (h = extra_http_headers; h; h = h->next) - headers = curl_slist_append(headers, h->data); + for_each_string_list_item(item, &extra_http_headers) + headers = curl_slist_append(headers, item->string); return headers; } diff --git a/merge-recursive.c b/merge-recursive.c index 42be7c9960..11869ad81c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1951,6 +1951,16 @@ static char *apply_dir_rename(struct dir_rename_entry *entry, return NULL; oldlen = strlen(entry->dir); + if (entry->new_dir.len == 0) + /* + * If someone renamed/merged a subdirectory into the root + * directory (e.g. 'some/subdir' -> ''), then we want to + * avoid returning + * '' + '/filename' + * as the rename; we need to make old_path + oldlen advance + * past the '/' character. + */ + oldlen++; newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1; strbuf_grow(&new_path, newlen); strbuf_addbuf(&new_path, &entry->new_dir); @@ -1963,8 +1973,8 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path, char **old_dir, char **new_dir) { char *end_of_old, *end_of_new; - int old_len, new_len; + /* Default return values: NULL, meaning no rename */ *old_dir = NULL; *new_dir = NULL; @@ -1975,43 +1985,91 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path, * "a/b/c/d" was renamed to "a/b/some/thing/else" * so, for this example, this function returns "a/b/c/d" in * *old_dir and "a/b/some/thing/else" in *new_dir. - * - * Also, if the basename of the file changed, we don't care. We - * want to know which portion of the directory, if any, changed. + */ + + /* + * If the basename of the file changed, we don't care. We want + * to know which portion of the directory, if any, changed. */ end_of_old = strrchr(old_path, '/'); end_of_new = strrchr(new_path, '/'); - if (end_of_old == NULL || end_of_new == NULL) + /* + * If end_of_old is NULL, old_path wasn't in a directory, so there + * could not be a directory rename (our rule elsewhere that a + * directory which still exists is not considered to have been + * renamed means the root directory can never be renamed -- because + * the root directory always exists). + */ + if (end_of_old == NULL) + return; /* Note: *old_dir and *new_dir are still NULL */ + + /* + * If new_path contains no directory (end_of_new is NULL), then we + * have a rename of old_path's directory to the root directory. + */ + if (end_of_new == NULL) { + *old_dir = xstrndup(old_path, end_of_old - old_path); + *new_dir = xstrdup(""); return; + } + + /* Find the first non-matching character traversing backwards */ while (*--end_of_new == *--end_of_old && end_of_old != old_path && end_of_new != new_path) ; /* Do nothing; all in the while loop */ + /* - * We've found the first non-matching character in the directory - * paths. That means the current directory we were comparing - * represents the rename. Move end_of_old and end_of_new back - * to the full directory name. + * If both got back to the beginning of their strings, then the + * directory didn't change at all, only the basename did. */ - if (*end_of_old == '/') - end_of_old++; - if (*end_of_old != '/') - end_of_new++; - end_of_old = strchr(end_of_old, '/'); - end_of_new = strchr(end_of_new, '/'); + if (end_of_old == old_path && end_of_new == new_path && + *end_of_old == *end_of_new) + return; /* Note: *old_dir and *new_dir are still NULL */ /* - * It may have been the case that old_path and new_path were the same - * directory all along. Don't claim a rename if they're the same. + * If end_of_new got back to the beginning of its string, and + * end_of_old got back to the beginning of some subdirectory, then + * we have a rename/merge of a subdirectory into the root, which + * needs slightly special handling. + * + * Note: There is no need to consider the opposite case, with a + * rename/merge of the root directory into some subdirectory + * because as noted above the root directory always exists so it + * cannot be considered to be renamed. */ - old_len = end_of_old - old_path; - new_len = end_of_new - new_path; - - if (old_len != new_len || strncmp(old_path, new_path, old_len)) { - *old_dir = xstrndup(old_path, old_len); - *new_dir = xstrndup(new_path, new_len); + if (end_of_new == new_path && + end_of_old != old_path && end_of_old[-1] == '/') { + *old_dir = xstrndup(old_path, --end_of_old - old_path); + *new_dir = xstrdup(""); + return; } + + /* + * We've found the first non-matching character in the directory + * paths. That means the current characters we were looking at + * were part of the first non-matching subdir name going back from + * the end of the strings. Get the whole name by advancing both + * end_of_old and end_of_new to the NEXT '/' character. That will + * represent the entire directory rename. + * + * The reason for the increment is cases like + * a/b/star/foo/whatever.c -> a/b/tar/foo/random.c + * After dropping the basename and going back to the first + * non-matching character, we're now comparing: + * a/b/s and a/b/ + * and we want to be comparing: + * a/b/star/ and a/b/tar/ + * but without the pre-increment, the one on the right would stay + * a/b/. + */ + end_of_old = strchr(++end_of_old, '/'); + end_of_new = strchr(++end_of_new, '/'); + + /* Copy the old and new directories into *old_dir and *new_dir. */ + *old_dir = xstrndup(old_path, end_of_old - old_path); + *new_dir = xstrndup(new_path, end_of_new - new_path); } static void remove_hashmap_entries(struct hashmap *dir_renames, @@ -448,6 +448,8 @@ struct pack_list { uint32_t nr; uint32_t alloc; struct multi_pack_index *m; + struct progress *progress; + unsigned pack_paths_checked; }; static void add_pack_to_midx(const char *full_path, size_t full_path_len, @@ -456,6 +458,7 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, struct pack_list *packs = (struct pack_list *)data; if (ends_with(file_name, ".idx")) { + display_progress(packs->progress, ++packs->pack_paths_checked); if (packs->m && midx_contains_pack(packs->m, file_name)) return; @@ -785,7 +788,7 @@ static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_off } static int write_midx_internal(const char *object_dir, struct multi_pack_index *m, - struct string_list *packs_to_drop) + struct string_list *packs_to_drop, unsigned flags) { unsigned char cur_chunk, num_chunks = 0; char *midx_name; @@ -799,6 +802,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1]; uint32_t nr_entries, num_large_offsets = 0; struct pack_midx_entry *entries = NULL; + struct progress *progress = NULL; int large_offsets_needed = 0; int pack_name_concat_len = 0; int dropped_packs = 0; @@ -833,7 +837,14 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * } } + packs.pack_paths_checked = 0; + if (flags & MIDX_PROGRESS) + packs.progress = start_progress(_("Adding packfiles to multi-pack-index"), 0); + else + packs.progress = NULL; + for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs); + stop_progress(&packs.progress); if (packs.m && packs.nr == packs.m->num_packs && !packs_to_drop) goto cleanup; @@ -958,6 +969,9 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * written += MIDX_CHUNKLOOKUP_WIDTH; } + if (flags & MIDX_PROGRESS) + progress = start_progress(_("Writing chunks to multi-pack-index"), + num_chunks); for (i = 0; i < num_chunks; i++) { if (written != chunk_offsets[i]) BUG("incorrect chunk offset (%"PRIu64" != %"PRIu64") for chunk id %"PRIx32, @@ -990,7 +1004,10 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * BUG("trying to write unknown chunk id %"PRIx32, chunk_ids[i]); } + + display_progress(progress, i + 1); } + stop_progress(&progress); if (written != chunk_offsets[num_chunks]) BUG("incorrect final offset %"PRIu64" != %"PRIu64, @@ -1016,9 +1033,9 @@ cleanup: return result; } -int write_midx_file(const char *object_dir) +int write_midx_file(const char *object_dir, unsigned flags) { - return write_midx_internal(object_dir, NULL, NULL); + return write_midx_internal(object_dir, NULL, NULL, flags); } void clear_midx_file(struct repository *r) @@ -1076,19 +1093,20 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b) display_progress(progress, _n); \ } while (0) -int verify_midx_file(struct repository *r, const char *object_dir) +int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags) { struct pair_pos_vs_id *pairs = NULL; uint32_t i; - struct progress *progress; + struct progress *progress = NULL; struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); verify_midx_error = 0; if (!m) return 0; - progress = start_progress(_("Looking for referenced packfiles"), - m->num_packs); + if (flags & MIDX_PROGRESS) + progress = start_progress(_("Looking for referenced packfiles"), + m->num_packs); for (i = 0; i < m->num_packs; i++) { if (prepare_midx_pack(r, m, i)) midx_report("failed to load pack in position %d", i); @@ -1106,8 +1124,9 @@ int verify_midx_file(struct repository *r, const char *object_dir) i, oid_fanout1, oid_fanout2, i + 1); } - progress = start_sparse_progress(_("Verifying OID order in MIDX"), - m->num_objects - 1); + if (flags & MIDX_PROGRESS) + progress = start_sparse_progress(_("Verifying OID order in multi-pack-index"), + m->num_objects - 1); for (i = 0; i < m->num_objects - 1; i++) { struct object_id oid1, oid2; @@ -1134,13 +1153,15 @@ int verify_midx_file(struct repository *r, const char *object_dir) pairs[i].pack_int_id = nth_midxed_pack_int_id(m, i); } - progress = start_sparse_progress(_("Sorting objects by packfile"), - m->num_objects); + if (flags & MIDX_PROGRESS) + progress = start_sparse_progress(_("Sorting objects by packfile"), + m->num_objects); display_progress(progress, 0); /* TODO: Measure QSORT() progress */ QSORT(pairs, m->num_objects, compare_pair_pos_vs_id); stop_progress(&progress); - progress = start_sparse_progress(_("Verifying object offsets"), m->num_objects); + if (flags & MIDX_PROGRESS) + progress = start_sparse_progress(_("Verifying object offsets"), m->num_objects); for (i = 0; i < m->num_objects; i++) { struct object_id oid; struct pack_entry e; @@ -1183,23 +1204,34 @@ int verify_midx_file(struct repository *r, const char *object_dir) return verify_midx_error; } -int expire_midx_packs(struct repository *r, const char *object_dir) +int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags) { uint32_t i, *count, result = 0; struct string_list packs_to_drop = STRING_LIST_INIT_DUP; struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + struct progress *progress = NULL; if (!m) return 0; count = xcalloc(m->num_packs, sizeof(uint32_t)); + + if (flags & MIDX_PROGRESS) + progress = start_progress(_("Counting referenced objects"), + m->num_objects); for (i = 0; i < m->num_objects; i++) { int pack_int_id = nth_midxed_pack_int_id(m, i); count[pack_int_id]++; + display_progress(progress, i + 1); } + stop_progress(&progress); + if (flags & MIDX_PROGRESS) + progress = start_progress(_("Finding and deleting unreferenced packfiles"), + m->num_packs); for (i = 0; i < m->num_packs; i++) { char *pack_name; + display_progress(progress, i + 1); if (count[i]) continue; @@ -1217,11 +1249,12 @@ int expire_midx_packs(struct repository *r, const char *object_dir) unlink_pack_path(pack_name, 0); free(pack_name); } + stop_progress(&progress); free(count); if (packs_to_drop.nr) - result = write_midx_internal(object_dir, m, &packs_to_drop); + result = write_midx_internal(object_dir, m, &packs_to_drop, flags); string_list_clear(&packs_to_drop, 0); return result; @@ -1315,7 +1348,7 @@ static int fill_included_packs_batch(struct repository *r, return 0; } -int midx_repack(struct repository *r, const char *object_dir, size_t batch_size) +int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags) { int result = 0; uint32_t i; @@ -1340,6 +1373,12 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size) strbuf_addstr(&base_name, object_dir); strbuf_addstr(&base_name, "/pack/pack"); argv_array_push(&cmd.args, base_name.buf); + + if (flags & MIDX_PROGRESS) + argv_array_push(&cmd.args, "--progress"); + else + argv_array_push(&cmd.args, "-q"); + strbuf_release(&base_name); cmd.git_cmd = 1; @@ -1370,7 +1409,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size) goto cleanup; } - result = write_midx_internal(object_dir, m, NULL); + result = write_midx_internal(object_dir, m, NULL, flags); m = NULL; cleanup: @@ -37,6 +37,8 @@ struct multi_pack_index { char object_dir[FLEX_ARRAY]; }; +#define MIDX_PROGRESS (1 << 0) + struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local); int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id); int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result); @@ -47,11 +49,11 @@ int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pa int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local); -int write_midx_file(const char *object_dir); +int write_midx_file(const char *object_dir, unsigned flags); void clear_midx_file(struct repository *r); -int verify_midx_file(struct repository *r, const char *object_dir); -int expire_midx_packs(struct repository *r, const char *object_dir); -int midx_repack(struct repository *r, const char *object_dir, size_t batch_size); +int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags); +int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags); +int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags); void close_midx(struct multi_pack_index *m); @@ -11,6 +11,7 @@ #include "path.h" #include "packfile.h" #include "object-store.h" +#include "lockfile.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -101,36 +102,36 @@ struct common_dir { /* Not considered garbage for report_linked_checkout_garbage */ unsigned ignore_garbage:1; unsigned is_dir:1; - /* Not common even though its parent is */ - unsigned exclude:1; - const char *dirname; + /* Belongs to the common dir, though it may contain paths that don't */ + unsigned is_common:1; + const char *path; }; static struct common_dir common_list[] = { - { 0, 1, 0, "branches" }, - { 0, 1, 0, "common" }, - { 0, 1, 0, "hooks" }, - { 0, 1, 0, "info" }, - { 0, 0, 1, "info/sparse-checkout" }, - { 1, 1, 0, "logs" }, - { 1, 1, 1, "logs/HEAD" }, - { 0, 1, 1, "logs/refs/bisect" }, - { 0, 1, 1, "logs/refs/rewritten" }, - { 0, 1, 1, "logs/refs/worktree" }, - { 0, 1, 0, "lost-found" }, - { 0, 1, 0, "objects" }, - { 0, 1, 0, "refs" }, - { 0, 1, 1, "refs/bisect" }, - { 0, 1, 1, "refs/rewritten" }, - { 0, 1, 1, "refs/worktree" }, - { 0, 1, 0, "remotes" }, - { 0, 1, 0, "worktrees" }, - { 0, 1, 0, "rr-cache" }, - { 0, 1, 0, "svn" }, - { 0, 0, 0, "config" }, - { 1, 0, 0, "gc.pid" }, - { 0, 0, 0, "packed-refs" }, - { 0, 0, 0, "shallow" }, + { 0, 1, 1, "branches" }, + { 0, 1, 1, "common" }, + { 0, 1, 1, "hooks" }, + { 0, 1, 1, "info" }, + { 0, 0, 0, "info/sparse-checkout" }, + { 1, 1, 1, "logs" }, + { 1, 0, 0, "logs/HEAD" }, + { 0, 1, 0, "logs/refs/bisect" }, + { 0, 1, 0, "logs/refs/rewritten" }, + { 0, 1, 0, "logs/refs/worktree" }, + { 0, 1, 1, "lost-found" }, + { 0, 1, 1, "objects" }, + { 0, 1, 1, "refs" }, + { 0, 1, 0, "refs/bisect" }, + { 0, 1, 0, "refs/rewritten" }, + { 0, 1, 0, "refs/worktree" }, + { 0, 1, 1, "remotes" }, + { 0, 1, 1, "worktrees" }, + { 0, 1, 1, "rr-cache" }, + { 0, 1, 1, "svn" }, + { 0, 0, 1, "config" }, + { 1, 0, 1, "gc.pid" }, + { 0, 0, 1, "packed-refs" }, + { 0, 0, 1, "shallow" }, { 0, 0, 0, NULL } }; @@ -236,30 +237,41 @@ static void *add_to_trie(struct trie *root, const char *key, void *value) return old; } -typedef int (*match_fn)(const char *unmatched, void *data, void *baton); +typedef int (*match_fn)(const char *unmatched, void *value, void *baton); /* * Search a trie for some key. Find the longest /-or-\0-terminated - * prefix of the key for which the trie contains a value. Call fn - * with the unmatched portion of the key and the found value, and - * return its return value. If there is no such prefix, return -1. + * prefix of the key for which the trie contains a value. If there is + * no such prefix, return -1. Otherwise call fn with the unmatched + * portion of the key and the found value. If fn returns 0 or + * positive, then return its return value. If fn returns negative, + * then call fn with the next-longest /-terminated prefix of the key + * (i.e. a parent directory) for which the trie contains a value, and + * handle its return value the same way. If there is no shorter + * /-terminated prefix with a value left, then return the negative + * return value of the most recent fn invocation. * * The key is partially normalized: consecutive slashes are skipped. * - * For example, consider the trie containing only [refs, - * refs/worktree] (both with values). - * - * | key | unmatched | val from node | return value | - * |-----------------|------------|---------------|--------------| - * | a | not called | n/a | -1 | - * | refs | \0 | refs | as per fn | - * | refs/ | / | refs | as per fn | - * | refs/w | /w | refs | as per fn | - * | refs/worktree | \0 | refs/worktree | as per fn | - * | refs/worktree/ | / | refs/worktree | as per fn | - * | refs/worktree/a | /a | refs/worktree | as per fn | - * |-----------------|------------|---------------|--------------| + * For example, consider the trie containing only [logs, + * logs/refs/bisect], both with values, but not logs/refs. * + * | key | unmatched | prefix to node | return value | + * |--------------------|----------------|------------------|--------------| + * | a | not called | n/a | -1 | + * | logstore | not called | n/a | -1 | + * | logs | \0 | logs | as per fn | + * | logs/ | / | logs | as per fn | + * | logs/refs | /refs | logs | as per fn | + * | logs/refs/ | /refs/ | logs | as per fn | + * | logs/refs/b | /refs/b | logs | as per fn | + * | logs/refs/bisected | /refs/bisected | logs | as per fn | + * | logs/refs/bisect | \0 | logs/refs/bisect | as per fn | + * | logs/refs/bisect/ | / | logs/refs/bisect | as per fn | + * | logs/refs/bisect/a | /a | logs/refs/bisect | as per fn | + * | (If fn in the previous line returns -1, then fn is called once more:) | + * | logs/refs/bisect/a | /refs/bisect/a | logs | as per fn | + * |--------------------|----------------|------------------|--------------| */ static int trie_find(struct trie *root, const char *key, match_fn fn, void *baton) @@ -288,9 +300,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn, /* Matched the entire compressed section */ key += i; - if (!*key) + if (!*key) { /* End of key */ - return fn(key, root->value, baton); + if (root->value) + return fn(key, root->value, baton); + else + return -1; + } /* Partial path normalization: skip consecutive slashes */ while (key[0] == '/' && key[1] == '/') @@ -320,8 +336,8 @@ static void init_common_trie(void) if (common_trie_done_setup) return; - for (p = common_list; p->dirname; p++) - add_to_trie(&common_trie, p->dirname, p); + for (p = common_list; p->path; p++) + add_to_trie(&common_trie, p->path, p); common_trie_done_setup = 1; } @@ -334,14 +350,11 @@ static int check_common(const char *unmatched, void *value, void *baton) { struct common_dir *dir = value; - if (!dir) - return 0; - if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/')) - return !dir->exclude; + return dir->is_common; if (!dir->is_dir && unmatched[0] == 0) - return !dir->exclude; + return dir->is_common; return 0; } @@ -350,9 +363,14 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len, const char *common_dir) { char *base = buf->buf + git_dir_len; + int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX); + init_common_trie(); if (trie_find(&common_trie, base, check_common, NULL) > 0) replace_dir(buf, git_dir_len, common_dir); + + if (has_lock_suffix) + strbuf_addstr(buf, LOCK_SUFFIX); } void report_linked_checkout_garbage(void) @@ -365,8 +383,8 @@ void report_linked_checkout_garbage(void) return; strbuf_addf(&sb, "%s/", get_git_dir()); len = sb.len; - for (p = common_list; p->dirname; p++) { - const char *path = p->dirname; + for (p = common_list; p->path; p++) { + const char *path = p->path; if (p->ignore_garbage) continue; strbuf_setlen(&sb, len); @@ -696,7 +696,7 @@ static size_t format_person_part(struct strbuf *sb, char part, mail = s.mail_begin; maillen = s.mail_end - s.mail_begin; - if (part == 'N' || part == 'E') /* mailmap lookup */ + if (part == 'N' || part == 'E' || part == 'L') /* mailmap lookup */ mailmap_name(&mail, &maillen, &name, &namelen); if (part == 'n' || part == 'N') { /* name */ strbuf_add(sb, name, namelen); @@ -706,6 +706,13 @@ static size_t format_person_part(struct strbuf *sb, char part, strbuf_add(sb, mail, maillen); return placeholder_len; } + if (part == 'l' || part == 'L') { /* local-part */ + const char *at = memchr(mail, '@', maillen); + if (at) + maillen = at - mail; + strbuf_add(sb, mail, maillen); + return placeholder_len; + } if (!s.date_begin) goto skip; diff --git a/sequencer.c b/sequencer.c index 9d5964fd81..8952cfa89b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1126,25 +1126,22 @@ static int run_prepare_commit_msg_hook(struct repository *r, struct strbuf *msg, const char *commit) { - struct argv_array hook_env = ARGV_ARRAY_INIT; - int ret; - const char *name; + int ret = 0; + const char *name, *arg1 = NULL, *arg2 = NULL; name = git_path_commit_editmsg(); if (write_message(msg->buf, msg->len, name, 0)) return -1; - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", r->index_file); - argv_array_push(&hook_env, "GIT_EDITOR=:"); - if (commit) - ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name, - "commit", commit, NULL); - else - ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name, - "message", NULL); - if (ret) + if (commit) { + arg1 = "commit"; + arg2 = commit; + } else { + arg1 = "message"; + } + if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name, + arg1, arg2, NULL)) ret = error(_("'prepare-commit-msg' hook failed")); - argv_array_clear(&hook_env); return ret; } @@ -1403,6 +1400,7 @@ static int try_to_commit(struct repository *r, goto out; } + run_commit_hook(0, r->index_file, "post-commit", NULL); if (flags & AMEND_MSG) commit_post_rewrite(r, current_head, oid); diff --git a/sequencer.h b/sequencer.h index 574260f621..9f9ae291e3 100644 --- a/sequencer.h +++ b/sequencer.h @@ -202,11 +202,10 @@ void print_commit_summary(struct repository *repo, int read_author_script(const char *path, char **name, char **email, char **date, int allow_missing); -#endif - void parse_strategy_opts(struct replay_opts *opts, char *raw_opts); int write_basic_state(struct replay_opts *opts, const char *head_name, struct commit *onto, const char *orig_head); void sequencer_post_commit_cleanup(struct repository *r, int verbose); int sequencer_get_last_command(struct repository* r, enum replay_action *action); +#endif /* SEQUENCER_H */ diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 6d87961e41..b72c051f47 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -119,3 +119,31 @@ make_empty () { git commit --allow-empty -m "$1" && git tag "$1" } + +# Call this (inside test_expect_success) at the end of a test file to +# check that no tests have changed editor related environment +# variables or config settings +test_editor_unchanged () { + # We're only interested in exported variables hence 'sh -c' + sh -c 'cat >actual <<-EOF + EDITOR=$EDITOR + FAKE_COMMIT_AMEND=$FAKE_COMMIT_AMEND + FAKE_COMMIT_MESSAGE=$FAKE_COMMIT_MESSAGE + FAKE_LINES=$FAKE_LINES + GIT_EDITOR=$GIT_EDITOR + GIT_SEQUENCE_EDITOR=$GIT_SEQUENCE_EDITOR + core.editor=$(git config core.editor) + sequence.editor=$(git config sequence.editor) + EOF' + cat >expect <<-\EOF + EDITOR=: + FAKE_COMMIT_AMEND= + FAKE_COMMIT_MESSAGE= + FAKE_LINES= + GIT_EDITOR= + GIT_SEQUENCE_EDITOR= + core.editor= + sequence.editor= + EOF + test_cmp expect actual +} diff --git a/t/oid-info/hash-info b/t/oid-info/hash-info index ccdbfdf974..d0736dd1a0 100644 --- a/t/oid-info/hash-info +++ b/t/oid-info/hash-info @@ -6,3 +6,12 @@ hexsz sha256:64 zero sha1:0000000000000000000000000000000000000000 zero sha256:0000000000000000000000000000000000000000000000000000000000000000 + +algo sha1:sha1 +algo sha256:sha256 + +empty_blob sha1:e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +empty_blob sha256:473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813 + +empty_tree sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904 +empty_tree sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index c7b53e494b..553e94bc5a 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -285,9 +285,13 @@ test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2 test_expect_success 'setup common repository' 'git --git-dir=bar init' test_git_path GIT_COMMON_DIR=bar index .git/index +test_git_path GIT_COMMON_DIR=bar index.lock .git/index.lock test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD +test_git_path GIT_COMMON_DIR=bar logs/HEAD.lock .git/logs/HEAD.lock test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo .git/logs/refs/bisect/foo +test_git_path GIT_COMMON_DIR=bar logs/refs bar/logs/refs +test_git_path GIT_COMMON_DIR=bar logs/refs/ bar/logs/refs/ test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo bar/logs/refs/bisec/foo test_git_path GIT_COMMON_DIR=bar logs/refs/bisec bar/logs/refs/bisec test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo bar/logs/refs/bisectfoo diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index d20b4d150d..f1e1b289f9 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -63,7 +63,7 @@ test_expect_success 'listing includes option and expansion' ' test.one=1 EOF git config --list >actual.full && - grep -v ^core actual.full >actual && + grep -v -e ^core -e ^extensions actual.full >actual && test_cmp expect actual ' diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 1fbd940408..69a7f27311 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" ' test_cmp expect .git/logs/$m ' -git update-ref $m $D -cat >.git/logs/$m <<EOF -$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500 -$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500 -$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500 -$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500 -$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500 -EOF +test_expect_success 'set up for querying the reflog' ' + git update-ref $m $D && + cat >.git/logs/$m <<-EOF + $Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500 + $C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500 + $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500 + $F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500 + $Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500 + EOF +' ed="Thu, 26 May 2005 18:32:00 -0500" gd="Thu, 26 May 2005 18:33:00 -0500" diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 50d28e6fdb..7c7ff7e961 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -616,7 +616,7 @@ test_expect_success 'fsck --name-objects' ' remove_object $(git rev-parse julius:caesar.t) && test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && - test_i18ngrep -E "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out + test_i18ngrep "$tree (refs/tags/julius:" out ) ' diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 01abee533d..0177fd815c 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -59,6 +59,7 @@ test_rev_parse () { ROOT=$(pwd) test_expect_success 'setup' ' + test_oid_init && mkdir -p sub/dir work && cp -R .git repo.git ' @@ -131,6 +132,20 @@ test_expect_success 'rev-parse --is-shallow-repository in non-shallow repo' ' test_cmp expect actual ' +test_expect_success 'rev-parse --show-object-format in repo' ' + echo "$(test_oid algo)" >expect && + git rev-parse --show-object-format >actual && + test_cmp expect actual && + git rev-parse --show-object-format=storage >actual && + test_cmp expect actual && + git rev-parse --show-object-format=input >actual && + test_cmp expect actual && + git rev-parse --show-object-format=output >actual && + test_cmp expect actual && + test_must_fail git rev-parse --show-object-format=squeamish-ossifrage 2>err && + grep "unknown mode for --show-object-format: squeamish-ossifrage" err +' + test_expect_success 'showing the superproject correctly' ' git rev-parse --show-superproject-working-tree >out && test_must_be_empty out && diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index e819ba741e..8a9831413c 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -587,4 +587,28 @@ test_expect_success '"add" should not fail because of another bad worktree' ' ) ' +test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' ' + test_create_repo submodule && + test_commit -C submodule first && + test_create_repo project && + git -C project submodule add ../submodule && + git -C project add submodule && + test_tick && + git -C project commit -m add_sub && + git clone project project-clone && + git -C project-clone worktree add ../project-2 +' +test_expect_success '"add" with uninitialized submodule, with submodule.recurse set' ' + git -C project-clone -c submodule.recurse worktree add ../project-3 +' + +test_expect_success '"add" with initialized submodule, with submodule.recurse unset' ' + git -C project-clone submodule update --init && + git -C project-clone worktree add ../project-4 +' + +test_expect_success '"add" with initialized submodule, with submodule.recurse set' ' + git -C project-clone -c submodule.recurse worktree add ../project-5 +' + test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index d3fa298c6a..d66a5f6faa 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -864,6 +864,24 @@ test_expect_success 'append to note from other note with "git notes append -c"' ' test_expect_success 'copy note with "git notes copy"' ' + commit=$(git rev-parse 4th) && + cat >expect <<-EOF && + commit $commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:16:13 2005 -0700 + + ${indent}4th + + Notes: + ${indent}This is a blob object + EOF + git notes copy 8th 4th && + git log 3rd..4th >actual && + test_cmp expect actual && + test "$(git note list 4th)" = "$(git note list 8th)" +' + +test_expect_success 'copy note with "git notes copy" with default' ' test_commit 11th && commit=$(git rev-parse HEAD) && cat >expect <<-EOF && @@ -878,7 +896,7 @@ test_expect_success 'copy note with "git notes copy"' ' ${indent} ${indent}yet another note EOF - git notes copy HEAD^ HEAD && + git notes copy HEAD^ && git log -1 >actual && test_cmp expect actual && test "$(git notes list HEAD)" = "$(git notes list HEAD^)" @@ -901,11 +919,29 @@ test_expect_success 'allow overwrite with "git notes copy -f"' ' ${indent}11th Notes: + ${indent}This is a blob object + EOF + git notes copy -f HEAD~3 HEAD && + git log -1 >actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD~3)" +' + +test_expect_success 'allow overwrite with "git notes copy -f" with default' ' + commit=$(git rev-parse HEAD) && + cat >expect <<-EOF && + commit $commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:23:13 2005 -0700 + + ${indent}11th + + Notes: ${indent}yet another note ${indent} ${indent}yet another note EOF - git notes copy -f HEAD~2 HEAD && + git notes copy -f HEAD~2 && git log -1 >actual && test_cmp expect actual && test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" @@ -1167,8 +1203,10 @@ test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' ' ' test_expect_success 'git notes copy diagnoses too many or too few parameters' ' - test_must_fail git notes copy && - test_must_fail git notes copy one two three + test_must_fail git notes copy 2>error && + test_i18ngrep "too few parameters" error && + test_must_fail git notes copy one two three 2>error && + test_i18ngrep "too many parameters" error ' test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index d2dfbe46b9..bf0dc756d2 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -76,8 +76,11 @@ test_expect_success 'rebase -i with empty HEAD' ' cat >expect <<-\EOF && error: nothing to do EOF - set_fake_editor && - test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 exec_true" \ + git rebase -i HEAD^ >actual 2>&1 + ) && test_i18ncmp expect actual ' @@ -136,8 +139,11 @@ test_expect_success 'rebase -i sets work tree properly' ' test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git checkout master && - set_fake_editor && - test_must_fail env FAKE_LINES="exec_echo_foo_>file1 1" git rebase -i HEAD^ && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="exec_echo_foo_>file1 1" \ + git rebase -i HEAD^ + ) && test_cmp_rev master^ HEAD && git reset --hard && git rebase --continue @@ -163,9 +169,11 @@ test_expect_success 'rebase -x with newline in command fails' ' test_expect_success 'rebase -i with exec of inexistent command' ' git checkout master && test_when_finished "git rebase --abort" && - set_fake_editor && - test_must_fail env FAKE_LINES="exec_this-command-does-not-exist 1" \ - git rebase -i HEAD^ >actual 2>&1 && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="exec_this-command-does-not-exist 1" \ + git rebase -i HEAD^ >actual 2>&1 + ) && ! grep "Maybe git-rebase is broken" actual ' @@ -176,7 +184,6 @@ test_expect_success 'implicit interactive rebase does not invoke sequence editor test_expect_success 'no changes are a nop' ' git checkout branch2 && - set_fake_editor && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse HEAD) @@ -186,7 +193,6 @@ test_expect_success 'test the [branch] option' ' git checkout -b dead-end && git rm file6 && git commit -m "stop here" && - set_fake_editor && git rebase -i F branch2 && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse branch2) && @@ -195,7 +201,6 @@ test_expect_success 'test the [branch] option' ' test_expect_success 'test --onto <branch>' ' git checkout -b test-onto branch2 && - set_fake_editor && git rebase -i --onto branch1 F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" && test $(git rev-parse HEAD^) = $(git rev-parse branch1) && @@ -205,7 +210,6 @@ test_expect_success 'test --onto <branch>' ' test_expect_success 'rebase on top of a non-conflicting commit' ' git checkout branch1 && git tag original-branch1 && - set_fake_editor && git rebase -i branch2 && test file6 = $(git diff --name-only original-branch1) && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" && @@ -225,8 +229,10 @@ test_expect_success 'reflog for the branch shows correct finish message' ' ' test_expect_success 'exchange two commits' ' - set_fake_editor && - FAKE_LINES="2 1" git rebase -i HEAD~2 && + ( + set_fake_editor && + FAKE_LINES="2 1" git rebase -i HEAD~2 + ) && test H = $(git cat-file commit HEAD^ | sed -ne \$p) && test G = $(git cat-file commit HEAD | sed -ne \$p) && blob1=$(git rev-parse --short HEAD^:file1) && @@ -252,7 +258,6 @@ test_expect_success 'stop on conflicting pick' ' >>>>>>> $commit... G EOF git tag new-branch1 && - set_fake_editor && test_must_fail git rebase -i master && test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" && test_cmp expect .git/rebase-merge/patch && @@ -281,7 +286,6 @@ test_expect_success 'abort' ' test_expect_success 'abort with error when new base cannot be checked out' ' git rm --cached file1 && git commit -m "remove file in base" && - set_fake_editor && test_must_fail git rebase -i master > output 2>&1 && test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" \ output && @@ -296,7 +300,6 @@ test_expect_success 'retain authorship' ' test_tick && GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" && git tag twerp && - set_fake_editor && git rebase -i --onto master HEAD^ && git show HEAD | grep "^Author: Twerp Snog" ' @@ -314,7 +317,6 @@ test_expect_success 'retain authorship w/ conflicts' ' test_commit b conflict b conflict-b && GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME && - set_fake_editor && test_must_fail git rebase -i conflict-a && echo resolved >conflict && git add conflict && @@ -330,9 +332,11 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - set_fake_editor && - FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ - git rebase -i --onto master HEAD~2 && + ( + set_fake_editor && + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ + git rebase -i --onto master HEAD~2 + ) && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) ' @@ -343,7 +347,6 @@ test_expect_success 'retain authorship when squashing' ' test_expect_success REBASE_P '-p handles "no changes" gracefully' ' HEAD=$(git rev-parse HEAD) && - set_fake_editor && git rebase -i -p HEAD^ && git update-index --refresh && git diff-files --quiet && @@ -353,8 +356,10 @@ test_expect_success REBASE_P '-p handles "no changes" gracefully' ' test_expect_failure REBASE_P 'exchange two commits with -p' ' git checkout H && - set_fake_editor && - FAKE_LINES="2 1" git rebase -i -p HEAD~2 && + ( + set_fake_editor && + FAKE_LINES="2 1" git rebase -i -p HEAD~2 + ) && test H = $(git cat-file commit HEAD^ | sed -ne \$p) && test G = $(git cat-file commit HEAD | sed -ne \$p) ' @@ -388,7 +393,6 @@ test_expect_success REBASE_P 'preserve merges with -p' ' git commit -m M file1 && git checkout -b to-be-rebased && test_tick && - set_fake_editor && git rebase -i -p --onto branch1 master && git update-index --refresh && git diff-files --quiet && @@ -403,8 +407,10 @@ test_expect_success REBASE_P 'preserve merges with -p' ' ' test_expect_success REBASE_P 'edit ancestor with -p' ' - set_fake_editor && - FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 && + ( + set_fake_editor && + FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 + ) && echo 2 > unrelated-file && test_tick && git commit -m L2-modified --amend unrelated-file && @@ -418,11 +424,13 @@ test_expect_success REBASE_P 'edit ancestor with -p' ' test_expect_success '--continue tries to commit' ' git reset --hard D && test_tick && - set_fake_editor && - test_must_fail git rebase -i --onto new-branch1 HEAD^ && - echo resolved > file1 && - git add file1 && - FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue && + ( + set_fake_editor && + test_must_fail git rebase -i --onto new-branch1 HEAD^ && + echo resolved > file1 && + git add file1 && + FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue + ) && test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) && git show HEAD | grep chouette ' @@ -430,7 +438,6 @@ test_expect_success '--continue tries to commit' ' test_expect_success 'verbose flag is heeded, even after --continue' ' git reset --hard master@{1} && test_tick && - set_fake_editor && test_must_fail git rebase -v -i --onto new-branch1 HEAD^ && echo resolved > file1 && git add file1 && @@ -440,10 +447,13 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && - set_fake_editor && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ - EXPECT_HEADER_COUNT=4 \ - git rebase -i $base && + ( + set_fake_editor && + FAKE_COMMIT_AMEND="ONCE" \ + FAKE_LINES="1 squash 2 squash 3 squash 4" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base + ) && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) ' @@ -451,9 +461,12 @@ test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' ' test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && - set_fake_editor && - FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ - git rebase -i $base && + ( + set_fake_editor && + FAKE_COMMIT_AMEND="NEVER" \ + FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + git rebase -i $base + ) && test $base = $(git rev-parse HEAD^) && test 0 = $(git show | grep NEVER | wc -l) && git checkout @{-1} && @@ -463,12 +476,15 @@ test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' ' test_expect_success 'commit message used after conflict' ' git checkout -b conflict-fixup conflict-branch && base=$(git rev-parse HEAD~4) && - set_fake_editor && - test_must_fail env FAKE_LINES="1 fixup 3 fixup 4" git rebase -i $base && - echo three > conflict && - git add conflict && - FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ - git rebase --continue && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 fixup 3 fixup 4" \ + git rebase -i $base && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue + ) && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && git checkout @{-1} && @@ -478,12 +494,15 @@ test_expect_success 'commit message used after conflict' ' test_expect_success 'commit message retained after conflict' ' git checkout -b conflict-squash conflict-branch && base=$(git rev-parse HEAD~4) && - set_fake_editor && - test_must_fail env FAKE_LINES="1 fixup 3 squash 4" git rebase -i $base && - echo three > conflict && - git add conflict && - FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ - git rebase --continue && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 fixup 3 squash 4" \ + git rebase -i $base && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue + ) && test $base = $(git rev-parse HEAD^) && test 2 = $(git show | grep TWICE | wc -l) && git checkout @{-1} && @@ -500,10 +519,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa EOF git checkout -b squash-fixup E && base=$(git rev-parse HEAD~4) && - set_fake_editor && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ - EXPECT_HEADER_COUNT=4 \ - git rebase -i $base && + ( + set_fake_editor && + FAKE_COMMIT_AMEND="ONCE" \ + FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base + ) && git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && test_cmp expect-squash-fixup actual-squash-fixup && git cat-file commit HEAD@{2} | @@ -517,10 +539,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' ' git checkout -b skip-comments E && base=$(git rev-parse HEAD~4) && - set_fake_editor && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \ - EXPECT_HEADER_COUNT=4 \ - git rebase -i $base && + ( + set_fake_editor && + FAKE_COMMIT_AMEND="ONCE" \ + FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base + ) && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && git checkout @{-1} && @@ -530,10 +555,13 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' ' test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' ' git checkout -b skip-blank-lines E && base=$(git rev-parse HEAD~4) && - set_fake_editor && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \ - EXPECT_HEADER_COUNT=4 \ - git rebase -i $base && + ( + set_fake_editor && + FAKE_COMMIT_AMEND="ONCE" \ + FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base + ) && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && git checkout @{-1} && @@ -543,17 +571,21 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' ' test_expect_success 'squash works as expected' ' git checkout -b squash-works no-conflict-branch && one=$(git rev-parse HEAD~3) && - set_fake_editor && - FAKE_LINES="1 s 3 2" EXPECT_HEADER_COUNT=2 \ - git rebase -i HEAD~3 && + ( + set_fake_editor && + FAKE_LINES="1 s 3 2" EXPECT_HEADER_COUNT=2 git rebase -i HEAD~3 + ) && test $one = $(git rev-parse HEAD~2) ' test_expect_success 'interrupted squash works as expected' ' git checkout -b interrupted-squash conflict-branch && one=$(git rev-parse HEAD~3) && - set_fake_editor && - test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 3 2" \ + git rebase -i HEAD~3 + ) && test_write_lines one two four > conflict && git add conflict && test_must_fail git rebase --continue && @@ -566,8 +598,11 @@ test_expect_success 'interrupted squash works as expected' ' test_expect_success 'interrupted squash works as expected (case 2)' ' git checkout -b interrupted-squash2 conflict-branch && one=$(git rev-parse HEAD~3) && - set_fake_editor && - test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="3 squash 1 2" \ + git rebase -i HEAD~3 + ) && test_write_lines one four > conflict && git add conflict && test_must_fail git rebase --continue && @@ -587,11 +622,13 @@ test_expect_success '--continue tries to commit, even for "edit"' ' git commit -m "unrelated change" && parent=$(git rev-parse HEAD^) && test_tick && - set_fake_editor && - FAKE_LINES="edit 1" git rebase -i HEAD^ && - echo edited > file7 && - git add file7 && - FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue && + ( + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo edited > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue + ) && test edited = $(git show HEAD:file7) && git show HEAD | grep chouette && test $parent = $(git rev-parse HEAD^) @@ -600,34 +637,41 @@ test_expect_success '--continue tries to commit, even for "edit"' ' test_expect_success 'aborted --continue does not squash commits after "edit"' ' old=$(git rev-parse HEAD) && test_tick && - set_fake_editor && - FAKE_LINES="edit 1" git rebase -i HEAD^ && - echo "edited again" > file7 && - git add file7 && - test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue && + ( + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo "edited again" > file7 && + git add file7 && + test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue + ) && test $old = $(git rev-parse HEAD) && git rebase --abort ' test_expect_success 'auto-amend only edited commits after "edit"' ' test_tick && - set_fake_editor && - FAKE_LINES="edit 1" git rebase -i HEAD^ && - echo "edited again" > file7 && - git add file7 && - FAKE_COMMIT_MESSAGE="edited file7 again" git commit && - echo "and again" > file7 && - git add file7 && - test_tick && - test_must_fail env FAKE_COMMIT_MESSAGE="and again" git rebase --continue && + ( + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo "edited again" > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="edited file7 again" git commit && + echo "and again" > file7 && + git add file7 && + test_tick && + test_must_fail env FAKE_COMMIT_MESSAGE="and again" \ + git rebase --continue + ) && git rebase --abort ' test_expect_success 'clean error after failed "exec"' ' test_tick && test_when_finished "git rebase --abort || :" && - set_fake_editor && - test_must_fail env FAKE_LINES="1 exec_false" git rebase -i HEAD^ && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 exec_false" git rebase -i HEAD^ + ) && echo "edited again" > file7 && git add file7 && test_must_fail git rebase --continue 2>error && @@ -638,8 +682,10 @@ test_expect_success 'rebase a detached HEAD' ' grandparent=$(git rev-parse HEAD~2) && git checkout $(git rev-parse HEAD) && test_tick && - set_fake_editor && - FAKE_LINES="2 1" git rebase -i HEAD~2 && + ( + set_fake_editor && + FAKE_LINES="2 1" git rebase -i HEAD~2 + ) && test $grandparent = $(git rev-parse HEAD~2) ' @@ -654,9 +700,10 @@ test_expect_success 'rebase a commit violating pre-commit' ' test_must_fail git commit -m doesnt-verify file1 && git commit -m doesnt-verify --no-verify file1 && test_tick && - set_fake_editor && - FAKE_LINES=2 git rebase -i HEAD~2 - + ( + set_fake_editor && + FAKE_LINES=2 git rebase -i HEAD~2 + ) ' test_expect_success 'rebase with a file named HEAD in worktree' ' @@ -676,8 +723,10 @@ test_expect_success 'rebase with a file named HEAD in worktree' ' git commit -m "Add body" ) && - set_fake_editor && - FAKE_LINES="1 squash 2" git rebase -i @{-1} && + ( + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase -i @{-1} + ) && test "$(git show -s --pretty=format:%an)" = "Squashed Away" ' @@ -688,7 +737,6 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' ' GIT_EDITOR=: git commit --amend \ --author="Somebody else <somebody@else.com>" && test $(git rev-parse branch3) != $(git rev-parse branch4) && - set_fake_editor && git rebase -i branch3 && test $(git rev-parse branch3) = $(git rev-parse branch4) @@ -713,13 +761,14 @@ test_expect_success 'submodule rebase setup' ' git commit -a -m "submodule second" ) && test_tick && - set_fake_editor && git commit -a -m "Three changes submodule" ' test_expect_success 'submodule rebase -i' ' - set_fake_editor && - FAKE_LINES="1 squash 2 3" git rebase -i A + ( + set_fake_editor && + FAKE_LINES="1 squash 2 3" git rebase -i A + ) ' test_expect_success 'submodule conflict setup' ' @@ -736,7 +785,6 @@ test_expect_success 'submodule conflict setup' ' ' test_expect_success 'rebase -i continue with only submodule staged' ' - set_fake_editor && test_must_fail git rebase -i submodule-base && git add sub && git rebase --continue && @@ -746,7 +794,6 @@ test_expect_success 'rebase -i continue with only submodule staged' ' test_expect_success 'rebase -i continue with unstaged submodule' ' git checkout submodule-topic && git reset --hard && - set_fake_editor && test_must_fail git rebase -i submodule-base && git reset && git rebase --continue && @@ -759,7 +806,6 @@ test_expect_success 'avoid unnecessary reset' ' test-tool chmtime =123456789 file3 && git update-index --refresh && HEAD=$(git rev-parse HEAD) && - set_fake_editor && git rebase -i HEAD~4 && test $HEAD = $(git rev-parse HEAD) && MTIME=$(test-tool chmtime --get file3) && @@ -768,16 +814,22 @@ test_expect_success 'avoid unnecessary reset' ' test_expect_success 'reword' ' git checkout -b reword-branch master && - set_fake_editor && - FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A && - git show HEAD | grep "E changed" && - test $(git rev-parse master) != $(git rev-parse HEAD) && - test $(git rev-parse master^) = $(git rev-parse HEAD^) && - FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A && - git show HEAD^ | grep "D changed" && - FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A && - git show HEAD~3 | grep "B changed" && - FAKE_LINES="1 r 2 pick 3 p 4" FAKE_COMMIT_MESSAGE="C changed" git rebase -i A && + ( + set_fake_editor && + FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" \ + git rebase -i A && + git show HEAD | grep "E changed" && + test $(git rev-parse master) != $(git rev-parse HEAD) && + test $(git rev-parse master^) = $(git rev-parse HEAD^) && + FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" \ + git rebase -i A && + git show HEAD^ | grep "D changed" && + FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" \ + git rebase -i A && + git show HEAD~3 | grep "B changed" && + FAKE_LINES="1 r 2 pick 3 p 4" FAKE_COMMIT_MESSAGE="C changed" \ + git rebase -i A + ) && git show HEAD~2 | grep "C changed" ' @@ -788,7 +840,6 @@ test_expect_success 'rebase -i can copy notes' ' test_commit n2 && test_commit n3 && git notes add -m"a note" n3 && - set_fake_editor && git rebase -i --onto n1 n2 && test "a note" = "$(git notes show HEAD)" ' @@ -801,8 +852,11 @@ test_expect_success 'rebase -i can copy notes over a fixup' ' EOF git reset --hard n3 && git notes add -m"an earlier note" n2 && - set_fake_editor && - GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 f 2" git rebase -i n1 && + ( + set_fake_editor && + GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 f 2" \ + git rebase -i n1 + ) && git notes show > output && test_cmp expect output ' @@ -811,8 +865,10 @@ test_expect_success 'rebase while detaching HEAD' ' git symbolic-ref HEAD && grandparent=$(git rev-parse HEAD~2) && test_tick && - set_fake_editor && - FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 && + ( + set_fake_editor && + FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 + ) && test $grandparent = $(git rev-parse HEAD~2) && test_must_fail git symbolic-ref HEAD ' @@ -821,7 +877,6 @@ test_tick # Ensure that the rebased commits get a different timestamp. test_expect_success 'always cherry-pick with --no-ff' ' git checkout no-ff-branch && git tag original-no-ff-branch && - set_fake_editor && git rebase -i --no-ff A && for p in 0 1 2 do @@ -853,8 +908,10 @@ test_expect_success 'set up commits with funny messages' ' test_expect_success 'rebase-i history with funny messages' ' git rev-list A..funny >expect && test_tick && - set_fake_editor && - FAKE_LINES="1 2 3 4" git rebase -i A && + ( + set_fake_editor && + FAKE_LINES="1 2 3 4" git rebase -i A + ) && git rev-list A.. >actual && test_cmp expect actual ' @@ -868,9 +925,9 @@ test_expect_success 'prepare for rebase -i --exec' ' ' test_expect_success 'running "git rebase -i --exec git show HEAD"' ' - set_fake_editor && - git rebase -i --exec "git show HEAD" HEAD~2 >actual && ( + set_fake_editor && + git rebase -i --exec "git show HEAD" HEAD~2 >actual && FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && git rebase -i HEAD~2 >expect @@ -881,9 +938,9 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' test_expect_success 'running "git rebase --exec git show HEAD -i"' ' git reset --hard execute && - set_fake_editor && - git rebase --exec "git show HEAD" -i HEAD~2 >actual && ( + set_fake_editor && + git rebase --exec "git show HEAD" -i HEAD~2 >actual && FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && git rebase -i HEAD~2 >expect @@ -894,9 +951,9 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' test_expect_success 'running "git rebase -ix git show HEAD"' ' git reset --hard execute && - set_fake_editor && - git rebase -ix "git show HEAD" HEAD~2 >actual && ( + set_fake_editor && + git rebase -ix "git show HEAD" HEAD~2 >actual && FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && git rebase -i HEAD~2 >expect @@ -908,9 +965,9 @@ test_expect_success 'running "git rebase -ix git show HEAD"' ' test_expect_success 'rebase -ix with several <CMD>' ' git reset --hard execute && - set_fake_editor && - git rebase -ix "git show HEAD; pwd" HEAD~2 >actual && ( + set_fake_editor && + git rebase -ix "git show HEAD; pwd" HEAD~2 >actual && FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" && export FAKE_LINES && git rebase -i HEAD~2 >expect @@ -921,9 +978,9 @@ test_expect_success 'rebase -ix with several <CMD>' ' test_expect_success 'rebase -ix with several instances of --exec' ' git reset --hard execute && - set_fake_editor && - git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual && ( + set_fake_editor && + git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual && FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2 exec_git_show_HEAD exec_pwd" && export FAKE_LINES && @@ -942,13 +999,11 @@ test_expect_success C_LOCALE_OUTPUT 'rebase -ix with --autosquash' ' echo bis >bis.txt && git add bis.txt && git commit -m "fixup! two_exec" && - set_fake_editor && - ( - git checkout -b autosquash_actual && - git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual - ) && + git checkout -b autosquash_actual && + git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual && git checkout autosquash && ( + set_fake_editor && git checkout -b autosquash_expected && FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && @@ -969,7 +1024,6 @@ test_expect_success 'rebase --exec works without -i ' ' test_expect_success 'rebase -i --exec without <CMD>' ' git reset --hard execute && - set_fake_editor && test_must_fail git rebase -i --exec 2>actual && test_i18ngrep "requires a value" actual && git checkout master @@ -977,8 +1031,10 @@ test_expect_success 'rebase -i --exec without <CMD>' ' test_expect_success 'rebase -i --root re-order and drop commits' ' git checkout E && - set_fake_editor && - FAKE_LINES="3 1 2 5" git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="3 1 2 5" git rebase -i --root + ) && test E = $(git cat-file commit HEAD | sed -ne \$p) && test B = $(git cat-file commit HEAD^ | sed -ne \$p) && test A = $(git cat-file commit HEAD^^ | sed -ne \$p) && @@ -991,24 +1047,30 @@ test_expect_success 'rebase -i --root retain root commit author and message' ' echo B >file7 && git add file7 && GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" && - set_fake_editor && - FAKE_LINES="2" git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="2" git rebase -i --root + ) && git cat-file commit HEAD | grep -q "^author Twerp Snog" && git cat-file commit HEAD | grep -q "^different author$" ' test_expect_success 'rebase -i --root temporary sentinel commit' ' git checkout B && - set_fake_editor && - test_must_fail env FAKE_LINES="2" git rebase -i --root && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="2" git rebase -i --root + ) && git cat-file commit HEAD | grep "^tree $EMPTY_TREE" && git rebase --abort ' test_expect_success 'rebase -i --root fixup root commit' ' git checkout B && - set_fake_editor && - FAKE_LINES="1 fixup 2" git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase -i --root + ) && test A = $(git cat-file commit HEAD | sed -ne \$p) && test B = $(git show HEAD:file1) && test 0 = $(git cat-file commit HEAD | grep -c ^parent\ ) @@ -1017,9 +1079,11 @@ test_expect_success 'rebase -i --root fixup root commit' ' test_expect_success 'rebase -i --root reword original root commit' ' test_when_finished "test_might_fail git rebase --abort" && git checkout -b reword-original-root-branch master && - set_fake_editor && - FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \ - git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \ + git rebase -i --root + ) && git show HEAD^ | grep "A changed" && test -z "$(git show -s --format=%p HEAD^)" ' @@ -1027,9 +1091,11 @@ test_expect_success 'rebase -i --root reword original root commit' ' test_expect_success 'rebase -i --root reword new root commit' ' test_when_finished "test_might_fail git rebase --abort" && git checkout -b reword-now-root-branch master && - set_fake_editor && - FAKE_LINES="reword 3 1" FAKE_COMMIT_MESSAGE="C changed" \ - git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="reword 3 1" FAKE_COMMIT_MESSAGE="C changed" \ + git rebase -i --root + ) && git show HEAD^ | grep "C changed" && test -z "$(git show -s --format=%p HEAD^)" ' @@ -1041,8 +1107,10 @@ test_expect_success 'rebase -i --root when root has untracked file conflict' ' git rm file1 && git commit -m "remove file 1 add file 2" && echo z >file1 && - set_fake_editor && - test_must_fail env FAKE_LINES="1 2" git rebase -i --root && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 2" git rebase -i --root + ) && rm file1 && git rebase --continue && test "$(git log -1 --format=%B)" = "remove file 1 add file 2" && @@ -1052,11 +1120,13 @@ test_expect_success 'rebase -i --root when root has untracked file conflict' ' test_expect_success 'rebase -i --root reword root when root has untracked file conflict' ' test_when_finished "reset_rebase" && echo z>file1 && - set_fake_editor && - test_must_fail env FAKE_LINES="reword 1 2" \ - FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root && - rm file1 && - FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1 2" \ + FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root && + rm file1 && + FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue + ) && test "$(git log -1 --format=%B HEAD^)" = "Reworded A" && test "$(git rev-list --count HEAD)" = 2 ' @@ -1065,19 +1135,23 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git checkout reword-original-root-branch && git reset --hard && git checkout conflict-branch && - set_fake_editor && - test_must_fail git rebase -f --onto HEAD~2 HEAD~ && - test_must_fail git rebase --edit-todo && + ( + set_fake_editor && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && + test_must_fail git rebase --edit-todo + ) && git rebase --abort ' test_expect_success 'rebase --edit-todo can be used to modify todo' ' git reset --hard && git checkout no-conflict-branch^0 && - set_fake_editor && - FAKE_LINES="edit 1 2 3" git rebase -i HEAD~3 && - FAKE_LINES="2 1" git rebase --edit-todo && - git rebase --continue && + ( + set_fake_editor && + FAKE_LINES="edit 1 2 3" git rebase -i HEAD~3 && + FAKE_LINES="2 1" git rebase --edit-todo && + git rebase --continue + ) && test M = $(git cat-file commit HEAD^ | sed -ne \$p) && test L = $(git cat-file commit HEAD | sed -ne \$p) ' @@ -1085,7 +1159,6 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' ' test_expect_success 'rebase -i produces readable reflog' ' git reset --hard && git branch -f branch-reflog-test H && - set_fake_editor && git rebase -i --onto I F branch-reflog-test && cat >expect <<-\EOF && rebase -i (finish): returning to refs/heads/branch-reflog-test @@ -1106,8 +1179,10 @@ test_expect_success 'rebase -i respects core.commentchar' ' sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" && mv "$1.tmp" "$1" EOF - test_set_editor "$(pwd)/remove-all-but-first.sh" && - git rebase -i B && + ( + test_set_editor "$(pwd)/remove-all-but-first.sh" && + git rebase -i B + ) && test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' @@ -1116,9 +1191,11 @@ test_expect_success 'rebase -i respects core.commentchar=auto' ' write_script copy-edit-script.sh <<-\EOF && cp "$1" edit-script EOF - test_set_editor "$(pwd)/copy-edit-script.sh" && test_when_finished "git rebase --abort || :" && - git rebase -i HEAD^ && + ( + test_set_editor "$(pwd)/copy-edit-script.sh" && + git rebase -i HEAD^ + ) && test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)" ' @@ -1153,8 +1230,11 @@ test_expect_success 'interrupted rebase -i with --strategy and -X' ' echo five >conflict && echo Z >file1 && git commit -a -m "one file conflict" && - set_fake_editor && - FAKE_LINES="edit 1 2" git rebase -i --strategy=recursive -Xours conflict-branch && + ( + set_fake_editor && + FAKE_LINES="edit 1 2" git rebase -i --strategy=recursive \ + -Xours conflict-branch + ) && git rebase --continue && test $(git show conflict-branch:conflict) = $(cat conflict) && test $(cat file1) = Z @@ -1195,8 +1275,10 @@ test_expect_success SHA1 'short SHA-1 collide' ' test_expect_success 'respect core.abbrev' ' git config core.abbrev 12 && - set_cat_todo_editor && - test_must_fail git rebase -i HEAD~4 >todo-list && + ( + set_cat_todo_editor && + test_must_fail git rebase -i HEAD~4 >todo-list + ) && test 4 = $(grep -c "pick [0-9a-f]\{12,\}" todo-list) ' @@ -1204,16 +1286,20 @@ test_expect_success 'todo count' ' write_script dump-raw.sh <<-\EOF && cat "$1" EOF - test_set_editor "$(pwd)/dump-raw.sh" && - git rebase -i HEAD~4 >actual && + ( + test_set_editor "$(pwd)/dump-raw.sh" && + git rebase -i HEAD~4 >actual + ) && test_i18ngrep "^# Rebase ..* onto ..* ([0-9]" actual ' test_expect_success 'rebase -i commits that overwrite untracked files (pick)' ' git checkout --force branch2 && git clean -f && - set_fake_editor && - FAKE_LINES="edit 1 2" git rebase -i A && + ( + set_fake_editor && + FAKE_LINES="edit 1 2" git rebase -i A + ) && test_cmp_rev HEAD F && test_path_is_missing file6 && >file6 && @@ -1228,8 +1314,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)' git checkout --force branch2 && git clean -f && git tag original-branch2 && - set_fake_editor && - FAKE_LINES="edit 1 squash 2" git rebase -i A && + ( + set_fake_editor && + FAKE_LINES="edit 1 squash 2" git rebase -i A + ) && test_cmp_rev HEAD F && test_path_is_missing file6 && >file6 && @@ -1244,8 +1332,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)' test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' ' git checkout --force branch2 && git clean -f && - set_fake_editor && - FAKE_LINES="edit 1 2" git rebase -i --no-ff A && + ( + set_fake_editor && + FAKE_LINES="edit 1 2" git rebase -i --no-ff A + ) && test $(git cat-file commit HEAD | sed -ne \$p) = F && test_path_is_missing file6 && >file6 && @@ -1268,8 +1358,10 @@ test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' ' git tag seq-onto && git reset --hard HEAD~2 && git cherry-pick seq-onto && - set_fake_editor && - test_must_fail env FAKE_LINES= git rebase -i seq-onto && + ( + set_fake_editor && + test_must_fail env FAKE_LINES= git rebase -i seq-onto + ) && test -d .git/rebase-merge && git rebase --continue && git diff --exit-code seq-onto && @@ -1288,8 +1380,10 @@ rebase_setup_and_clean () { test_expect_success 'drop' ' rebase_setup_and_clean drop-test && - set_fake_editor && - FAKE_LINES="1 drop 2 3 d 4 5" git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="1 drop 2 3 d 4 5" git rebase -i --root + ) && test E = $(git cat-file commit HEAD | sed -ne \$p) && test C = $(git cat-file commit HEAD^ | sed -ne \$p) && test A = $(git cat-file commit HEAD^^ | sed -ne \$p) @@ -1298,9 +1392,10 @@ test_expect_success 'drop' ' test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' ' test_config rebase.missingCommitsCheck ignore && rebase_setup_and_clean missing-commit && - set_fake_editor && - FAKE_LINES="1 2 3 4" \ - git rebase -i --root 2>actual && + ( + set_fake_editor && + FAKE_LINES="1 2 3 4" git rebase -i --root 2>actual + ) && test D = $(git cat-file commit HEAD | sed -ne \$p) && test_i18ngrep \ "Successfully rebased and updated refs/heads/missing-commit" \ @@ -1316,9 +1411,10 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' ' EOF test_config rebase.missingCommitsCheck warn && rebase_setup_and_clean missing-commit && - set_fake_editor && - FAKE_LINES="1 2 3 4" \ - git rebase -i --root 2>actual.2 && + ( + set_fake_editor && + FAKE_LINES="1 2 3 4" git rebase -i --root 2>actual.2 + ) && head -n4 actual.2 >actual && test_i18ncmp expect actual && test D = $(git cat-file commit HEAD | sed -ne \$p) @@ -1340,14 +1436,15 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' EOF test_config rebase.missingCommitsCheck error && rebase_setup_and_clean missing-commit && - set_fake_editor && - test_must_fail env FAKE_LINES="1 2 4" \ - git rebase -i --root 2>actual && - test_i18ncmp expect actual && - cp .git/rebase-merge/git-rebase-todo.backup \ - .git/rebase-merge/git-rebase-todo && - FAKE_LINES="1 2 drop 3 4 drop 5" \ - git rebase --edit-todo && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 4" \ + git rebase -i --root 2>actual && + test_i18ncmp expect actual && + cp .git/rebase-merge/git-rebase-todo.backup \ + .git/rebase-merge/git-rebase-todo && + FAKE_LINES="1 2 drop 3 4 drop 5" git rebase --edit-todo + ) && git rebase --continue && test D = $(git cat-file commit HEAD | sed -ne \$p) && test B = $(git cat-file commit HEAD^ | sed -ne \$p) @@ -1368,21 +1465,27 @@ test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and e x git show HEAD EOF git checkout abbrevcmd && - set_cat_todo_editor && test_config rebase.abbreviateCommands true && - test_must_fail git rebase -i --exec "git show HEAD" \ - --autosquash master >actual && + ( + set_cat_todo_editor && + test_must_fail git rebase -i --exec "git show HEAD" \ + --autosquash master >actual + ) && test_cmp expected actual ' test_expect_success 'static check of bad command' ' rebase_setup_and_clean bad-cmd && - set_fake_editor && - test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \ + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \ git rebase -i --root 2>actual && - test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual && - test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && - FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo && + test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" \ + actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." \ + actual && + FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo + ) && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) && test C = $(git cat-file commit HEAD^ | sed -ne \$p) @@ -1398,19 +1501,24 @@ test_expect_success 'tabs and spaces are accepted in the todolist' ' ) >"$1.new" mv "$1.new" "$1" EOF - test_set_editor "$(pwd)/add-indent.sh" && - git rebase -i HEAD^^^ && + ( + test_set_editor "$(pwd)/add-indent.sh" && + git rebase -i HEAD^^^ + ) && test E = $(git cat-file commit HEAD | sed -ne \$p) ' test_expect_success 'static check of bad SHA-1' ' rebase_setup_and_clean bad-sha && - set_fake_editor && - test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \ - git rebase -i --root 2>actual && - test_i18ngrep "edit XXXXXXX False commit" actual && - test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && - FAKE_LINES="1 2 4 5 6" git rebase --edit-todo && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \ + git rebase -i --root 2>actual && + test_i18ngrep "edit XXXXXXX False commit" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." \ + actual && + FAKE_LINES="1 2 4 5 6" git rebase --edit-todo + ) && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) ' @@ -1429,39 +1537,71 @@ test_expect_success 'editor saves as CR/LF' ' test_expect_success 'rebase -i --gpg-sign=<key-id>' ' test_when_finished "test_might_fail git rebase --abort" && - set_fake_editor && - FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \ - >out 2>err && + ( + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" \ + HEAD^ >out 2>err + ) && test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err ' test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' ' test_when_finished "test_might_fail git rebase --abort" && test_config commit.gpgsign true && - set_fake_editor && - FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \ - >out 2>err && + ( + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" \ + HEAD^ >out 2>err + ) && test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err ' test_expect_success 'valid author header after --root swap' ' rebase_setup_and_clean author-header no-conflict-branch && - set_fake_editor && git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit && git cat-file commit HEAD | grep ^author >expected && - FAKE_LINES="5 1" git rebase -i --root && + ( + set_fake_editor && + FAKE_LINES="5 1" git rebase -i --root + ) && git cat-file commit HEAD^ | grep ^author >actual && test_cmp expected actual ' test_expect_success 'valid author header when author contains single quote' ' rebase_setup_and_clean author-header no-conflict-branch && - set_fake_editor && git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit && git cat-file commit HEAD | grep ^author >expected && - FAKE_LINES="2" git rebase -i HEAD~2 && + ( + set_fake_editor && + FAKE_LINES="2" git rebase -i HEAD~2 + ) && git cat-file commit HEAD | grep ^author >actual && test_cmp expected actual ' +test_expect_success 'post-commit hook is called' ' + test_when_finished "rm -f .git/hooks/post-commit" && + >actual && + mkdir -p .git/hooks && + write_script .git/hooks/post-commit <<-\EOS && + git rev-parse HEAD >>actual + EOS + ( + set_fake_editor && + FAKE_LINES="edit 4 1 reword 2 fixup 3" git rebase -i A E && + echo x>file3 && + git add file3 && + FAKE_COMMIT_MESSAGE=edited git rebase --continue + ) && + git rev-parse HEAD@{5} HEAD@{4} HEAD@{3} HEAD@{2} HEAD@{1} HEAD \ + >expect && + test_cmp expect actual +' + +# This must be the last test in this file +test_expect_success '$EDITOR and friends are unchanged' ' + test_editor_unchanged +' + test_done diff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh index 8739cb60a7..aaeac6eade 100755 --- a/t/t3429-rebase-edit-todo.sh +++ b/t/t3429-rebase-edit-todo.sh @@ -17,7 +17,7 @@ test_expect_success 'rebase exec modifies rebase-todo' ' test -e F ' -test_expect_success SHA1 'loose object cache vs re-reading todo list' ' +test_expect_success 'loose object cache vs re-reading todo list' ' GIT_REBASE_TODO=.git/rebase-merge/git-rebase-todo && export GIT_REBASE_TODO && write_script append-todo.sh <<-\EOS && diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index 9efcf4808a..a30d27e9f3 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -408,7 +408,7 @@ test_expect_success 'octopus merges' ' | | * three | * | two | |/ - * | one + * / one |/ o before-octopus EOF diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 580bfbdc23..a4da72f0ab 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1269,4 +1269,15 @@ test_expect_success 'stash apply should succeed with unmodified file' ' git stash apply ' +test_expect_success 'stash handles skip-worktree entries nicely' ' + test_commit A && + echo changed >A.t && + git add A.t && + git update-index --skip-worktree A.t && + rm A.t && + git stash && + + git rev-parse --verify refs/stash:A.t +' + test_done diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 281f8fad0c..e5ca359edf 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -17,11 +17,15 @@ test_expect_success \ 'echo frotz >file0 && mkdir path1 && echo rezrov >path1/file1 && + before0=$(git hash-object file0) && + before1=$(git hash-object path1/file1) && git update-index --add file0 path1/file1 && tree=$(git write-tree) && echo "$tree" && echo nitfol >file0 && echo yomin >path1/file1 && + after0=$(git hash-object file0) && + after1=$(git hash-object path1/file1) && git update-index file0 path1/file1' cat >expected <<\EOF @@ -31,32 +35,32 @@ test_expect_success \ 'git diff-index --cached $tree -- path >current && compare_diff_raw current expected' -cat >expected <<\EOF -:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1 +cat >expected <<EOF +:100644 100644 $before1 $after1 M path1/file1 EOF test_expect_success \ 'limit to path1 should show path1/file1' \ 'git diff-index --cached $tree -- path1 >current && compare_diff_raw current expected' -cat >expected <<\EOF -:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1 +cat >expected <<EOF +:100644 100644 $before1 $after1 M path1/file1 EOF test_expect_success \ 'limit to path1/ should show path1/file1' \ 'git diff-index --cached $tree -- path1/ >current && compare_diff_raw current expected' -cat >expected <<\EOF -:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1 +cat >expected <<EOF +:100644 100644 $before1 $after1 M path1/file1 EOF test_expect_success \ '"*file1" should show path1/file1' \ 'git diff-index --cached $tree -- "*file1" >current && compare_diff_raw current expected' -cat >expected <<\EOF -:100644 100644 8e4020bb5a8d8c873b25de15933e75cc0fc275df dca6b92303befc93086aa025d90a5facd7eb2812 M file0 +cat >expected <<EOF +:100644 100644 $before0 $after0 M file0 EOF test_expect_success \ 'limit to file0 should show file0' \ diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 5ae19b987d..717034bb50 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -9,11 +9,24 @@ test_description='Test diff of symlinks. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh +# Print the short OID of a symlink with the given name. +symlink_oid () { + local oid=$(printf "%s" "$1" | git hash-object --stdin) && + git rev-parse --short "$oid" +} + +# Print the short OID of the given file. +short_oid () { + local oid=$(git hash-object "$1") && + git rev-parse --short "$oid" +} + test_expect_success 'diff new symlink and file' ' - cat >expected <<-\EOF && + symlink=$(symlink_oid xyzzy) && + cat >expected <<-EOF && diff --git a/frotz b/frotz new file mode 120000 - index 0000000..7c465af + index 0000000..$symlink --- /dev/null +++ b/frotz @@ -0,0 +1 @@ @@ -21,7 +34,7 @@ test_expect_success 'diff new symlink and file' ' \ No newline at end of file diff --git a/nitfol b/nitfol new file mode 100644 - index 0000000..7c465af + index 0000000..$symlink --- /dev/null +++ b/nitfol @@ -0,0 +1 @@ @@ -46,10 +59,10 @@ test_expect_success 'diff unchanged symlink and file' ' ' test_expect_success 'diff removed symlink and file' ' - cat >expected <<-\EOF && + cat >expected <<-EOF && diff --git a/frotz b/frotz deleted file mode 120000 - index 7c465af..0000000 + index $symlink..0000000 --- a/frotz +++ /dev/null @@ -1 +0,0 @@ @@ -57,7 +70,7 @@ test_expect_success 'diff removed symlink and file' ' \ No newline at end of file diff --git a/nitfol b/nitfol deleted file mode 100644 - index 7c465af..0000000 + index $symlink..0000000 --- a/nitfol +++ /dev/null @@ -1 +0,0 @@ @@ -90,9 +103,10 @@ test_expect_success 'diff identical, but newly created symlink and file' ' ' test_expect_success 'diff different symlink and file' ' - cat >expected <<-\EOF && + new=$(symlink_oid yxyyz) && + cat >expected <<-EOF && diff --git a/frotz b/frotz - index 7c465af..df1db54 120000 + index $symlink..$new 120000 --- a/frotz +++ b/frotz @@ -1 +1 @@ @@ -101,7 +115,7 @@ test_expect_success 'diff different symlink and file' ' +yxyyz \ No newline at end of file diff --git a/nitfol b/nitfol - index 7c465af..df1db54 100644 + index $symlink..$new 100644 --- a/nitfol +++ b/nitfol @@ -1 +1 @@ @@ -137,14 +151,16 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' ' ' test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' ' - cat >expect <<-\EOF && + file=$(short_oid file.bin) && + link=$(symlink_oid file.bin) && + cat >expect <<-EOF && diff --git a/file.bin b/file.bin new file mode 100644 - index 0000000..d95f3ad + index 0000000..$file Binary files /dev/null and b/file.bin differ diff --git a/link.bin b/link.bin new file mode 120000 - index 0000000..dce41ec + index 0000000..$link --- /dev/null +++ b/link.bin @@ -0,0 +1 @@ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index b8969998b5..69267b16f0 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1517,6 +1517,178 @@ test_expect_success 'format patch ignores color.ui' ' test_cmp expect actual ' +test_expect_success 'cover letter with invalid --cover-from-description and config' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_must_fail git format-patch --cover-letter --cover-from-description garbage master && + test_config format.coverFromDescription garbage && + test_must_fail git format-patch --cover-letter master +' + +test_expect_success 'cover letter with format.coverFromDescription = default' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription default && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description default' ' + test_config branch.rebuild-1.description "config subject + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description default master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with format.coverFromDescription = none' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription none && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + ! grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description none' ' + test_config branch.rebuild-1.description "config subject + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description none master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + ! grep "^body$" actual +' + +test_expect_success 'cover letter with format.coverFromDescription = message' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription message && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description message' ' + test_config branch.rebuild-1.description "config subject + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description message master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with format.coverFromDescription = subject' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription subject && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] config subject$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description subject' ' + test_config branch.rebuild-1.description "config subject + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description subject master >actual && + grep "^Subject: \[PATCH 0/2\] config subject$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription auto && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] config subject$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description auto (short subject line)' ' + test_config branch.rebuild-1.description "config subject + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description auto master >actual && + grep "^Subject: \[PATCH 0/2\] config subject$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with format.coverFromDescription = auto (long subject line)' ' + test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects + +body" && + test_config format.coverFromDescription auto && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with --cover-from-description auto (long subject line)' ' + test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description auto master >actual && + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual && + grep "^body$" actual +' + +test_expect_success 'cover letter with command-line --cover-from-description overrides config' ' + test_config branch.rebuild-1.description "config subject + +body" && + test_config format.coverFromDescription none && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description subject master >actual && + grep "^Subject: \[PATCH 0/2\] config subject$" actual && + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual && + ! grep "^config subject$" actual && + grep "^body$" actual +' + test_expect_success 'cover letter using branch description (1)' ' git checkout rebuild-1 && test_config branch.rebuild-1.description hello && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 6b087df3dc..eadaf57262 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -16,6 +16,7 @@ test_expect_success "Ray Lehtiniemi's example" ' } while (0); EOF git update-index --add x && + before=$(git rev-parse --short $(git hash-object x)) && cat <<-\EOF >x && do @@ -24,10 +25,11 @@ test_expect_success "Ray Lehtiniemi's example" ' } while (0); EOF + after=$(git rev-parse --short $(git hash-object x)) && - cat <<-\EOF >expect && + cat <<-EOF >expect && diff --git a/x b/x - index adf3937..6edc172 100644 + index $before..$after 100644 --- a/x +++ b/x @@ -1,3 +1,5 @@ @@ -61,6 +63,7 @@ test_expect_success 'another test, without options' ' EOF git update-index x && + before=$(git rev-parse --short $(git hash-object x)) && tr "_" " " <<-\EOF >x && _ whitespace at beginning @@ -70,10 +73,11 @@ test_expect_success 'another test, without options' ' unchanged line CR at end EOF + after=$(git rev-parse --short $(git hash-object x)) && - tr "Q_" "\015 " <<-\EOF >expect && + tr "Q_" "\015 " <<-EOF >expect && diff --git a/x b/x - index d99af23..22d9f73 100644 + index $before..$after 100644 --- a/x +++ b/x @@ -1,6 +1,6 @@ @@ -108,9 +112,9 @@ test_expect_success 'another test, without options' ' git diff -w --ignore-cr-at-eol >out && test_must_be_empty out && - tr "Q_" "\015 " <<-\EOF >expect && + tr "Q_" "\015 " <<-EOF >expect && diff --git a/x b/x - index d99af23..22d9f73 100644 + index $before..$after 100644 --- a/x +++ b/x @@ -1,6 +1,6 @@ @@ -132,9 +136,9 @@ test_expect_success 'another test, without options' ' git diff -b --ignore-cr-at-eol >out && test_cmp expect out && - tr "Q_" "\015 " <<-\EOF >expect && + tr "Q_" "\015 " <<-EOF >expect && diff --git a/x b/x - index d99af23..22d9f73 100644 + index $before..$after 100644 --- a/x +++ b/x @@ -1,6 +1,6 @@ @@ -154,9 +158,9 @@ test_expect_success 'another test, without options' ' git diff --ignore-space-at-eol --ignore-cr-at-eol >out && test_cmp expect out && - tr "Q_" "\015 " <<-\EOF >expect && + tr "Q_" "\015 " <<-EOF >expect && diff --git a/x b/x - index_d99af23..22d9f73 100644 + index_$before..$after 100644 --- a/x +++ b/x @@ -1,6 +1,6 @@ @@ -786,23 +790,25 @@ test_expect_success 'whitespace-only changes not reported' ' test_must_be_empty actual ' -cat <<EOF >expect -diff --git a/x b/z -similarity index NUM% -rename from x -rename to z -index 380c32a..a97b785 100644 -EOF test_expect_success 'whitespace-only changes reported across renames' ' git reset --hard && for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x && git add x && + before=$(git rev-parse --short $(git hash-object x)) && git commit -m "base" && sed -e "5s/^/ /" x >z && git rm x && git add z && + after=$(git rev-parse --short $(git hash-object z)) && git diff -w -M --cached | sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual && + cat <<-EOF >expect && + diff --git a/x b/z + similarity index NUM% + rename from x + rename to z + index $before..$after 100644 + EOF test_cmp expect actual ' @@ -858,13 +864,15 @@ test_expect_success 'diff that introduces a line with only tabs' ' git config core.whitespace blank-at-eol && git reset --hard && echo "test" >x && + before=$(git rev-parse --short $(git hash-object x)) && git commit -m "initial" x && echo "{NTN}" | tr "NT" "\n\t" >>x && + after=$(git rev-parse --short $(git hash-object x)) && git diff --color | test_decode_color >current && - cat >expected <<-\EOF && + cat >expected <<-EOF && <BOLD>diff --git a/x b/x<RESET> - <BOLD>index 9daeafb..2874b91 100644<RESET> + <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> <CYAN>@@ -1 +1,4 @@<RESET> @@ -883,19 +891,21 @@ test_expect_success 'diff that introduces and removes ws breakages' ' echo "0. blank-at-eol " && echo "1. blank-at-eol " } >x && + before=$(git rev-parse --short $(git hash-object x)) && git commit -a --allow-empty -m preimage && { echo "0. blank-at-eol " && echo "1. still-blank-at-eol " && echo "2. and a new line " } >x && + after=$(git rev-parse --short $(git hash-object x)) && git diff --color | test_decode_color >current && - cat >expected <<-\EOF && + cat >expected <<-EOF && <BOLD>diff --git a/x b/x<RESET> - <BOLD>index d0233a2..700886e 100644<RESET> + <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> <CYAN>@@ -1,2 +1,3 @@<RESET> @@ -915,16 +925,18 @@ test_expect_success 'ws-error-highlight test setup' ' echo "0. blank-at-eol " && echo "1. blank-at-eol " } >x && + before=$(git rev-parse --short $(git hash-object x)) && git commit -a --allow-empty -m preimage && { echo "0. blank-at-eol " && echo "1. still-blank-at-eol " && echo "2. and a new line " } >x && + after=$(git rev-parse --short $(git hash-object x)) && - cat >expect.default-old <<-\EOF && + cat >expect.default-old <<-EOF && <BOLD>diff --git a/x b/x<RESET> - <BOLD>index d0233a2..700886e 100644<RESET> + <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> <CYAN>@@ -1,2 +1,3 @@<RESET> @@ -934,9 +946,9 @@ test_expect_success 'ws-error-highlight test setup' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - cat >expect.all <<-\EOF && + cat >expect.all <<-EOF && <BOLD>diff --git a/x b/x<RESET> - <BOLD>index d0233a2..700886e 100644<RESET> + <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> <CYAN>@@ -1,2 +1,3 @@<RESET> @@ -946,9 +958,9 @@ test_expect_success 'ws-error-highlight test setup' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - cat >expect.none <<-\EOF + cat >expect.none <<-EOF <BOLD>diff --git a/x b/x<RESET> - <BOLD>index d0233a2..700886e 100644<RESET> + <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> <CYAN>@@ -1,2 +1,3 @@<RESET> @@ -1022,14 +1034,15 @@ test_expect_success 'detect moved code, complete file' ' EOF git add test.c && git commit -m "add main function" && + file=$(git rev-parse --short HEAD:test.c) && git mv test.c main.c && test_config color.diff.oldMoved "normal red" && test_config color.diff.newMoved "normal green" && git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual && - cat >expected <<-\EOF && + cat >expected <<-EOF && <BOLD>diff --git a/main.c b/main.c<RESET> <BOLD>new file mode 100644<RESET> - <BOLD>index 0000000..a986c57<RESET> + <BOLD>index 0000000..$file<RESET> <BOLD>--- /dev/null<RESET> <BOLD>+++ b/main.c<RESET> <CYAN>@@ -0,0 +1,5 @@<RESET> @@ -1040,7 +1053,7 @@ test_expect_success 'detect moved code, complete file' ' <BGREEN>+<RESET><BGREEN>}<RESET> <BOLD>diff --git a/test.c b/test.c<RESET> <BOLD>deleted file mode 100644<RESET> - <BOLD>index a986c57..0000000<RESET> + <BOLD>index $file..0000000<RESET> <BOLD>--- a/test.c<RESET> <BOLD>+++ /dev/null<RESET> <CYAN>@@ -1,5 +0,0 @@<RESET> @@ -1094,6 +1107,8 @@ test_expect_success 'detect malicious moved code, inside file' ' EOF git add main.c test.c && git commit -m "add main and test file" && + before_main=$(git rev-parse --short HEAD:main.c) && + before_test=$(git rev-parse --short HEAD:test.c) && cat <<-\EOF >main.c && #include<stdio.h> int stuff() @@ -1126,10 +1141,12 @@ test_expect_success 'detect malicious moved code, inside file' ' bar(); } EOF + after_main=$(git rev-parse --short $(git hash-object main.c)) && + after_test=$(git rev-parse --short $(git hash-object test.c)) && git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual && - cat <<-\EOF >expected && + cat <<-EOF >expected && <BOLD>diff --git a/main.c b/main.c<RESET> - <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>index $before_main..$after_main 100644<RESET> <BOLD>--- a/main.c<RESET> <BOLD>+++ b/main.c<RESET> <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> @@ -1147,7 +1164,7 @@ test_expect_success 'detect malicious moved code, inside file' ' {<RESET> foo();<RESET> <BOLD>diff --git a/test.c b/test.c<RESET> - <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>index $before_test..$after_test 100644<RESET> <BOLD>--- a/test.c<RESET> <BOLD>+++ b/test.c<RESET> <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> @@ -1176,9 +1193,9 @@ test_expect_success 'plain moved code, inside file' ' test_config color.diff.newMovedAlternative "yellow" && # needs previous test as setup git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual && - cat <<-\EOF >expected && + cat <<-EOF >expected && <BOLD>diff --git a/main.c b/main.c<RESET> - <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>index $before_main..$after_main 100644<RESET> <BOLD>--- a/main.c<RESET> <BOLD>+++ b/main.c<RESET> <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> @@ -1196,7 +1213,7 @@ test_expect_success 'plain moved code, inside file' ' {<RESET> foo();<RESET> <BOLD>diff --git a/test.c b/test.c<RESET> - <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>index $before_test..$after_test 100644<RESET> <BOLD>--- a/test.c<RESET> <BOLD>+++ b/test.c<RESET> <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 9aa8e2b39b..e29deaf4a5 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -6,6 +6,7 @@ test_description='difference in submodules' . "$TEST_DIRECTORY"/diff-lib.sh test_expect_success setup ' + test_oid_init && test_tick && test_create_repo sub && ( @@ -36,7 +37,8 @@ test_expect_success setup ' ' test_expect_success 'git diff --raw HEAD' ' - git diff --raw --abbrev=40 HEAD >actual && + hexsz=$(test_oid hexsz) && + git diff --raw --abbrev=$hexsz HEAD >actual && test_cmp expect actual ' @@ -245,23 +247,21 @@ test_expect_success 'git diff (empty submodule dir)' ' ' test_expect_success 'conflicted submodule setup' ' - - # 39 efs - c=fffffffffffffffffffffffffffffffffffffff && + c=$(test_oid ff_1) && ( echo "000000 $ZERO_OID 0 sub" && echo "160000 1$c 1 sub" && echo "160000 2$c 2 sub" && echo "160000 3$c 3 sub" ) | git update-index --index-info && - echo >expect.nosub '\''diff --cc sub + echo >expect.nosub "diff --cc sub index 2ffffff,3ffffff..0000000 --- a/sub +++ b/sub @@@ -1,1 -1,1 +1,1 @@@ -- Subproject commit 2fffffffffffffffffffffffffffffffffffffff - -Subproject commit 3fffffffffffffffffffffffffffffffffffffff -++Subproject commit 0000000000000000000000000000000000000000'\'' && +- Subproject commit 2$c + -Subproject commit 3$c +++Subproject commit $ZERO_OID" && hh=$(git rev-parse HEAD) && sed -e "s/$ZERO_OID/$hh/" expect.nosub >expect.withsub diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 9a93c2a3e0..fb145aa173 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -19,9 +19,11 @@ cat >post.simple <<-\EOF aeff = aeff * ( aaa ) EOF -cat >expect.letter-runs-are-words <<-\EOF +pre=$(git rev-parse --short $(git hash-object pre.simple)) +post=$(git rev-parse --short $(git hash-object post.simple)) +cat >expect.letter-runs-are-words <<-EOF <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -33,9 +35,9 @@ cat >expect.letter-runs-are-words <<-\EOF <GREEN>aeff = aeff * ( aaa<RESET> ) EOF -cat >expect.non-whitespace-is-word <<-\EOF +cat >expect.non-whitespace-is-word <<-EOF <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -49,9 +51,12 @@ cat >expect.non-whitespace-is-word <<-\EOF EOF word_diff () { + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && test_must_fail git diff --no-index "$@" pre post >output && test_decode_color <output >output.decrypted && - test_cmp expect output.decrypted + sed -e "2s/index [^ ]*/index $pre..$post/" expect >expected + test_cmp expected output.decrypted } test_language_driver () { @@ -77,9 +82,9 @@ test_expect_success 'set up pre and post with runs of whitespace' ' ' test_expect_success 'word diff with runs of whitespace' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -97,9 +102,9 @@ test_expect_success 'word diff with runs of whitespace' ' ' test_expect_success '--word-diff=porcelain' ' - sed 's/#.*$//' >expect <<-\EOF && + sed 's/#.*$//' >expect <<-EOF && diff --git a/pre b/post - index 330b04f..5ed8eff 100644 + index $pre..$post 100644 --- a/pre +++ b/post @@ -1,3 +1,7 @@ @@ -121,9 +126,9 @@ test_expect_success '--word-diff=porcelain' ' ' test_expect_success '--word-diff=plain' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && diff --git a/pre b/post - index 330b04f..5ed8eff 100644 + index $pre..$post 100644 --- a/pre +++ b/post @@ -1,3 +1,7 @@ @@ -140,9 +145,9 @@ test_expect_success '--word-diff=plain' ' ' test_expect_success '--word-diff=plain --color' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -158,9 +163,9 @@ test_expect_success '--word-diff=plain --color' ' ' test_expect_success 'word diff without context' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1 +1 @@<RESET> @@ -207,9 +212,9 @@ test_expect_success 'command-line overrides config' ' ' test_expect_success 'command-line overrides config: --word-diff-regex' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -234,9 +239,9 @@ test_expect_success 'setup: remove diff driver regex' ' ' test_expect_success 'use configured regex' ' - cat >expect <<-\EOF && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 330b04f..5ed8eff 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1,3 +1,7 @@<RESET> @@ -254,9 +259,11 @@ test_expect_success 'use configured regex' ' test_expect_success 'test parsing words for newline' ' echo "aaa (aaa)" >pre && echo "aaa (aaa) aaa" >post && - cat >expect <<-\EOF && + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index c29453b..be22f37 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1 +1 @@<RESET> @@ -268,9 +275,11 @@ test_expect_success 'test parsing words for newline' ' test_expect_success 'test when words are only removed at the end' ' echo "(:" >pre && echo "(" >post && - cat >expect <<-\EOF && + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && + cat >expect <<-EOF && <BOLD>diff --git a/pre b/post<RESET> - <BOLD>index 289cb9d..2d06f37 100644<RESET> + <BOLD>index $pre..$post 100644<RESET> <BOLD>--- a/pre<RESET> <BOLD>+++ b/post<RESET> <CYAN>@@ -1 +1 @@<RESET> @@ -282,9 +291,11 @@ test_expect_success 'test when words are only removed at the end' ' test_expect_success '--word-diff=none' ' echo "(:" >pre && echo "(" >post && - cat >expect <<-\EOF && + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && + cat >expect <<-EOF && diff --git a/pre b/post - index 289cb9d..2d06f37 100644 + index $pre..$post 100644 --- a/pre +++ b/post @@ -1 +1 @@ @@ -317,16 +328,6 @@ test_language_driver ruby test_language_driver tex test_expect_success 'word-diff with diff.sbe' ' - cat >expect <<-\EOF && - diff --git a/pre b/post - index a1a53b5..bc8fe6d 100644 - --- a/pre - +++ b/post - @@ -1,3 +1,3 @@ - a - - [-b-]{+c+} - EOF cat >pre <<-\EOF && a @@ -337,21 +338,35 @@ test_expect_success 'word-diff with diff.sbe' ' c EOF + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && + cat >expect <<-EOF && + diff --git a/pre b/post + index $pre..$post 100644 + --- a/pre + +++ b/post + @@ -1,3 +1,3 @@ + a + + [-b-]{+c+} + EOF test_config diff.suppress-blank-empty true && word_diff --word-diff=plain ' test_expect_success 'word-diff with no newline at EOF' ' - cat >expect <<-\EOF && + printf "%s" "a a a a a" >pre && + printf "%s" "a a ab a a" >post && + pre=$(git rev-parse --short $(git hash-object pre)) && + post=$(git rev-parse --short $(git hash-object post)) && + cat >expect <<-EOF && diff --git a/pre b/post - index 7bf316e..3dd0303 100644 + index $pre..$post 100644 --- a/pre +++ b/post @@ -1 +1 @@ a a [-a-]{+ab+} a a EOF - printf "%s" "a a a a a" >pre && - printf "%s" "a a ab a a" >post && word_diff --word-diff=plain ' diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index b9d876efa2..b5a56895e8 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -440,11 +440,13 @@ test_expect_success 'setup for --combined-all-paths' ' git branch side2c && git checkout side1c && test_seq 1 10 >filename-side1c && + side1cf=$(git hash-object filename-side1c) && git add filename-side1c && git commit -m with && git checkout side2c && test_seq 1 9 >filename-side2c && echo ten >>filename-side2c && + side2cf=$(git hash-object filename-side2c) && git add filename-side2c && git commit -m iam && git checkout -b mergery side1c && @@ -452,13 +454,14 @@ test_expect_success 'setup for --combined-all-paths' ' git rm filename-side1c && echo eleven >>filename-side2c && git mv filename-side2c filename-merged && + mergedf=$(git hash-object filename-merged) && git add filename-merged && git commit ' test_expect_success '--combined-all-paths and --raw' ' - cat <<-\EOF >expect && - ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR filename-side1c filename-side2c filename-merged + cat <<-EOF >expect && + ::100644 100644 100644 $side1cf $side2cf $mergedf RR filename-side1c filename-side2c filename-merged EOF git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp && sed 1d <actual.tmp >actual && @@ -482,11 +485,13 @@ test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' git checkout side1d && test_seq 1 10 >"$(printf "file\twith\ttabs")" && git add file* && + side1df=$(git hash-object *tabs) && git commit -m with && git checkout side2d && test_seq 1 9 >"$(printf "i\tam\ttabbed")" && echo ten >>"$(printf "i\tam\ttabbed")" && git add *tabbed && + side2df=$(git hash-object *tabbed) && git commit -m iam && git checkout -b funny-names-mergery side1d && git merge --no-commit side2d && @@ -494,12 +499,14 @@ test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' echo eleven >>"$(printf "i\tam\ttabbed")" && git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" && git add fickle* && - git commit + headf=$(git hash-object fickle*) && + git commit && + head=$(git rev-parse HEAD) ' test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' ' - cat <<-\EOF >expect && - ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR "file\twith\ttabs" "i\tam\ttabbed" "fickle\tnaming" + cat <<-EOF >expect && + ::100644 100644 100644 $side1df $side2df $headf RR "file\twith\ttabs" "i\tam\ttabbed" "fickle\tnaming" EOF git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp && sed 1d <actual.tmp >actual && @@ -507,7 +514,7 @@ test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' ' test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' ' - printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect && + printf "$head\0::100644 100644 100644 $side1df $side2df $headf RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect && git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual && test_cmp expect actual ' diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh index 53ac44b0f0..0eb0314a8b 100755 --- a/t/t4039-diff-assume-unchanged.sh +++ b/t/t4039-diff-assume-unchanged.sh @@ -12,6 +12,7 @@ test_expect_success 'setup' ' git commit -m zero && echo one > one && echo two > two && + blob=$(git hash-object one) && git add one two && git commit -m onetwo && git update-index --assume-unchanged one && @@ -20,7 +21,7 @@ test_expect_success 'setup' ' ' test_expect_success 'diff-index does not examine assume-unchanged entries' ' - git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171 + git diff-index HEAD^ -- one | grep -q $blob ' test_expect_success 'diff-files does not examine assume-unchanged entries' ' diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh index 647905e01f..4701796d10 100755 --- a/t/t4044-diff-index-unique-abbrev.sh +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -3,34 +3,48 @@ test_description='test unique sha1 abbreviation on "index from..to" line' . ./test-lib.sh -if ! test_have_prereq SHA1 -then - skip_all='not using SHA-1 for objects' - test_done -fi - -cat >expect_initial <<EOF -100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1 foo -EOF +test_expect_success 'setup' ' + test_oid_cache <<-EOF && + val1 sha1:4827 + val1 sha256:5664 -cat >expect_update <<EOF -100644 blob 51d2738efb4ad8a1e40bed839ab8e116f0a15e47 foo -EOF + val2 sha1:11742 + val2 sha256:10625 -test_expect_success 'setup' ' - echo 4827 > foo && + hash1 sha1:51d2738463ea4ca66f8691c91e33ce64b7d41bb1 + hash1 sha256:ae31dfff0af93b2c62b0098a039b38569c43b0a7e97b873000ca42d128f27350 + + hasht1 sha1:51d27384 + hasht1 sha256:ae31dfff + + hash2 sha1:51d2738efb4ad8a1e40bed839ab8e116f0a15e47 + hash2 sha256:ae31dffada88a46fd5f53c7ed5aa25a7a8951f1d5e88456c317c8d5484d263e5 + + hasht2 sha1:51d2738e + hasht2 sha256:ae31dffa + EOF + + cat >expect_initial <<-EOF && + 100644 blob $(test_oid hash1) foo + EOF + + cat >expect_update <<-EOF && + 100644 blob $(test_oid hash2) foo + EOF + + echo "$(test_oid val1)" > foo && git add foo && git commit -m "initial" && git cat-file -p HEAD: > actual && test_cmp expect_initial actual && - echo 11742 > foo && + echo "$(test_oid val2)" > foo && git commit -a -m "update" && git cat-file -p HEAD: > actual && test_cmp expect_update actual ' cat >expect <<EOF -index 51d27384..51d2738e 100644 +index $(test_oid hasht1)..$(test_oid hasht2) 100644 EOF test_expect_success 'diff does not produce ambiguous index line' ' diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh index 36f8ed8a81..258808708e 100755 --- a/t/t4045-diff-relative.sh +++ b/t/t4045-diff-relative.sh @@ -70,7 +70,7 @@ check_raw () { expect=$1 shift cat >expected <<-EOF - :000000 100644 0000000000000000000000000000000000000000 $blob A $expect + :000000 100644 $ZERO_OID $blob A $expect EOF test_expect_success "--raw $*" " git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual && diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh index 87a8949500..7f9ad9fa3d 100755 --- a/t/t4048-diff-combined-binary.sh +++ b/t/t4048-diff-combined-binary.sh @@ -9,24 +9,27 @@ test_expect_success 'setup binary merge conflict' ' git commit -m one && echo twoQ2 | q_to_nul >binary && git commit -a -m two && + two=$(git rev-parse --short HEAD:binary) && git checkout -b branch-binary HEAD^ && echo threeQ3 | q_to_nul >binary && git commit -a -m three && + three=$(git rev-parse --short HEAD:binary) && test_must_fail git merge master && echo resolvedQhooray | q_to_nul >binary && - git commit -a -m resolved + git commit -a -m resolved && + res=$(git rev-parse --short HEAD:binary) ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --git a/binary b/binary -index 7ea6ded..9563691 100644 +index $three..$res 100644 Binary files a/binary and b/binary differ resolved diff --git a/binary b/binary -index 6197570..9563691 100644 +index $two..$res 100644 Binary files a/binary and b/binary differ EOF test_expect_success 'diff -m indicates binary-ness' ' @@ -34,11 +37,11 @@ test_expect_success 'diff -m indicates binary-ness' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --combined binary -index 7ea6ded,6197570..9563691 +index $three,$two..$res Binary files differ EOF test_expect_success 'diff -c indicates binary-ness' ' @@ -46,11 +49,11 @@ test_expect_success 'diff -c indicates binary-ness' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --cc binary -index 7ea6ded,6197570..9563691 +index $three,$two..$res Binary files differ EOF test_expect_success 'diff --cc indicates binary-ness' ' @@ -62,23 +65,26 @@ test_expect_success 'setup non-binary with binary attribute' ' git checkout master && test_commit one text && test_commit two text && + two=$(git rev-parse --short HEAD:text) && git checkout -b branch-text HEAD^ && test_commit three text && + three=$(git rev-parse --short HEAD:text) && test_must_fail git merge master && test_commit resolved text && + res=$(git rev-parse --short HEAD:text) && echo text -diff >.gitattributes ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --git a/text b/text -index 2bdf67a..2ab19ae 100644 +index $three..$res 100644 Binary files a/text and b/text differ resolved diff --git a/text b/text -index f719efd..2ab19ae 100644 +index $two..$res 100644 Binary files a/text and b/text differ EOF test_expect_success 'diff -m respects binary attribute' ' @@ -86,11 +92,11 @@ test_expect_success 'diff -m respects binary attribute' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --combined text -index 2bdf67a,f719efd..2ab19ae +index $three,$two..$res Binary files differ EOF test_expect_success 'diff -c respects binary attribute' ' @@ -98,11 +104,11 @@ test_expect_success 'diff -c respects binary attribute' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --cc text -index 2bdf67a,f719efd..2ab19ae +index $three,$two..$res Binary files differ EOF test_expect_success 'diff --cc respects binary attribute' ' @@ -115,11 +121,11 @@ test_expect_success 'setup textconv attribute' ' git config diff.upcase.textconv "tr a-z A-Z <" ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --git a/text b/text -index 2bdf67a..2ab19ae 100644 +index $three..$res 100644 --- a/text +++ b/text @@ -1 +1 @@ @@ -128,7 +134,7 @@ index 2bdf67a..2ab19ae 100644 resolved diff --git a/text b/text -index f719efd..2ab19ae 100644 +index $two..$res 100644 --- a/text +++ b/text @@ -1 +1 @@ @@ -140,11 +146,11 @@ test_expect_success 'diff -m respects textconv attribute' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --combined text -index 2bdf67a,f719efd..2ab19ae +index $three,$two..$res --- a/text +++ b/text @@@ -1,1 -1,1 +1,1 @@@ @@ -157,11 +163,11 @@ test_expect_success 'diff -c respects textconv attribute' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF resolved diff --cc text -index 2bdf67a,f719efd..2ab19ae +index $three,$two..$res --- a/text +++ b/text @@@ -1,1 -1,1 +1,1 @@@ @@ -174,9 +180,9 @@ test_expect_success 'diff --cc respects textconv attribute' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF diff --combined text -index 2bdf67a,f719efd..2ab19ae +index $three,$two..$res --- a/text +++ b/text @@@ -1,1 -1,1 +1,1 @@@ @@ -190,9 +196,9 @@ test_expect_success 'diff-tree plumbing does not respect textconv' ' test_cmp expect actual ' -cat >expect <<'EOF' +cat >expect <<EOF diff --cc text -index 2bdf67a,f719efd..0000000 +index $three,$two..0000000 --- a/text +++ b/text @@@ -1,1 -1,1 +1,5 @@@ diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh index fa5d4efb89..d7349ced6b 100755 --- a/t/t4108-apply-threeway.sh +++ b/t/t4108-apply-threeway.sh @@ -4,23 +4,17 @@ test_description='git apply --3way' . ./test-lib.sh -create_file () { - for i - do - echo "$i" - done -} - -sanitize_conflicted_diff () { +print_sanitized_conflicted_diff () { + git diff HEAD >diff.raw && sed -e ' /^index /d - s/^\(+[<>][<>][<>][<>]*\) .*/\1/ - ' + s/^\(+[<>|][<>|][<>|][<>|]*\) .*/\1/ + ' diff.raw } test_expect_success setup ' test_tick && - create_file >one 1 2 3 4 5 6 7 && + test_write_lines 1 2 3 4 5 6 7 >one && cat one >two && git add one two && git commit -m initial && @@ -28,13 +22,13 @@ test_expect_success setup ' git branch side && test_tick && - create_file >one 1 two 3 4 5 six 7 && - create_file >two 1 two 3 4 5 6 7 && + test_write_lines 1 two 3 4 5 six 7 >one && + test_write_lines 1 two 3 4 5 6 7 >two && git commit -a -m master && git checkout side && - create_file >one 1 2 3 4 five 6 7 && - create_file >two 1 2 3 4 five 6 7 && + test_write_lines 1 2 3 4 five 6 7 >one && + test_write_lines 1 2 3 4 five 6 7 >two && git commit -a -m side && git checkout master @@ -52,7 +46,7 @@ test_expect_success 'apply without --3way' ' git diff-index --exit-code --cached HEAD ' -test_expect_success 'apply with --3way' ' +test_apply_with_3way () { # Merging side should be similar to applying this patch git diff ...side >P.diff && @@ -61,22 +55,31 @@ test_expect_success 'apply with --3way' ' git checkout master^0 && test_must_fail git merge --no-commit side && git ls-files -s >expect.ls && - git diff HEAD | sanitize_conflicted_diff >expect.diff && + print_sanitized_conflicted_diff >expect.diff && # should fail to apply git reset --hard && git checkout master^0 && test_must_fail git apply --index --3way P.diff && git ls-files -s >actual.ls && - git diff HEAD | sanitize_conflicted_diff >actual.diff && + print_sanitized_conflicted_diff >actual.diff && # The result should resemble the corresponding merge test_cmp expect.ls actual.ls && test_cmp expect.diff actual.diff +} + +test_expect_success 'apply with --3way' ' + test_apply_with_3way +' + +test_expect_success 'apply with --3way with merge.conflictStyle = diff3' ' + test_config merge.conflictStyle diff3 && + test_apply_with_3way ' test_expect_success 'apply with --3way with rerere enabled' ' - git config rerere.enabled true && + test_config rerere.enabled true && # Merging side should be similar to applying this patch git diff ...side >P.diff && @@ -87,7 +90,7 @@ test_expect_success 'apply with --3way with rerere enabled' ' test_must_fail git merge --no-commit side && # Manually resolve and record the resolution - create_file 1 two 3 4 five six 7 >one && + test_write_lines 1 two 3 4 five six 7 >one && git rerere && cat one >expect && @@ -104,14 +107,14 @@ test_expect_success 'apply -3 with add/add conflict setup' ' git reset --hard && git checkout -b adder && - create_file 1 2 3 4 5 6 7 >three && - create_file 1 2 3 4 5 6 7 >four && + test_write_lines 1 2 3 4 5 6 7 >three && + test_write_lines 1 2 3 4 5 6 7 >four && git add three four && git commit -m "add three and four" && git checkout -b another adder^ && - create_file 1 2 3 4 5 6 7 >three && - create_file 1 2 3 four 5 6 7 >four && + test_write_lines 1 2 3 4 5 6 7 >three && + test_write_lines 1 2 3 four 5 6 7 >four && git add three four && git commit -m "add three and four" && @@ -121,7 +124,7 @@ test_expect_success 'apply -3 with add/add conflict setup' ' git checkout adder^0 && test_must_fail git merge --no-commit another && git ls-files -s >expect.ls && - git diff HEAD | sanitize_conflicted_diff >expect.diff + print_sanitized_conflicted_diff >expect.diff ' test_expect_success 'apply -3 with add/add conflict' ' @@ -131,7 +134,7 @@ test_expect_success 'apply -3 with add/add conflict' ' test_must_fail git apply --index --3way P.diff && # ... and leave conflicts in the index and in the working tree git ls-files -s >actual.ls && - git diff HEAD | sanitize_conflicted_diff >actual.diff && + print_sanitized_conflicted_diff >actual.diff && # The result should resemble the corresponding merge test_cmp expect.ls actual.ls && diff --git a/t/t4202-log.sh b/t/t4202-log.sh index e803ba402e..ab0d021365 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -667,7 +667,7 @@ cat > expect <<\EOF * | | fifth * | | fourth |/ / -* | third +* / third |/ * second * initial diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 918ada69eb..586c3a86b1 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -13,8 +13,8 @@ fuzz_blame () { } test_expect_success setup ' - cat >contacts <<-\EOF && - A U Thor <author@example.com> + cat >contacts <<- EOF && + $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> nick1 <bugs@company.xx> EOF @@ -33,19 +33,19 @@ test_expect_success 'check-mailmap no arguments' ' ' test_expect_success 'check-mailmap arguments' ' - cat >expect <<-\EOF && - A U Thor <author@example.com> + cat >expect <<- EOF && + $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> nick1 <bugs@company.xx> EOF git check-mailmap \ - "A U Thor <author@example.com>" \ + "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" \ "nick1 <bugs@company.xx>" >actual && test_cmp expect actual ' test_expect_success 'check-mailmap --stdin' ' - cat >expect <<-\EOF && - A U Thor <author@example.com> + cat >expect <<- EOF && + $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> nick1 <bugs@company.xx> EOF git check-mailmap --stdin <contacts >actual && @@ -66,8 +66,8 @@ test_expect_success 'check-mailmap bogus contact' ' test_must_fail git check-mailmap bogus ' -cat >expect <<\EOF -A U Thor (1): +cat >expect << EOF +$GIT_AUTHOR_NAME (1): initial nick1 (1): @@ -90,7 +90,7 @@ nick1 (1): EOF test_expect_success 'default .mailmap' ' - echo "Repo Guy <author@example.com>" > .mailmap && + echo "Repo Guy <$GIT_AUTHOR_EMAIL>" > .mailmap && git shortlog HEAD >actual && test_cmp expect actual ' @@ -122,7 +122,7 @@ Internal Guy (1): EOF test_expect_success 'mailmap.file override' ' - echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap && + echo "External Guy <$GIT_AUTHOR_EMAIL>" >> internal_mailmap/.mailmap && git config mailmap.file internal_mailmap/.mailmap && git shortlog HEAD >actual && test_cmp expect actual @@ -178,8 +178,8 @@ test_expect_success 'name entry after email entry, case-insensitive' ' test_cmp expect actual ' -cat >expect <<\EOF -A U Thor (1): +cat >expect << EOF +$GIT_AUTHOR_NAME (1): initial nick1 (1): @@ -195,18 +195,18 @@ test_expect_success 'No mailmap files, but configured' ' test_expect_success 'setup mailmap blob tests' ' git checkout -b map && test_when_finished "git checkout master" && - cat >just-bugs <<-\EOF && + cat >just-bugs <<- EOF && Blob Guy <bugs@company.xx> EOF - cat >both <<-\EOF && - Blob Guy <author@example.com> + cat >both <<- EOF && + Blob Guy <$GIT_AUTHOR_EMAIL> Blob Guy <bugs@company.xx> EOF - printf "Tricky Guy <author@example.com>" >no-newline && + printf "Tricky Guy <$GIT_AUTHOR_EMAIL>" >no-newline && git add just-bugs both no-newline && git commit -m "my mailmaps" && - echo "Repo Guy <author@example.com>" >.mailmap && - echo "Internal Guy <author@example.com>" >internal.map + echo "Repo Guy <$GIT_AUTHOR_EMAIL>" >.mailmap && + echo "Internal Guy <$GIT_AUTHOR_EMAIL>" >internal.map ' test_expect_success 'mailmap.blob set' ' @@ -266,12 +266,12 @@ test_expect_success 'mailmap.blob defaults to off in non-bare repo' ' git init non-bare && ( cd non-bare && - test_commit one .mailmap "Fake Name <author@example.com>" && + test_commit one .mailmap "Fake Name <$GIT_AUTHOR_EMAIL>" && echo " 1 Fake Name" >expect && git shortlog -ns HEAD >actual && test_cmp expect actual && rm .mailmap && - echo " 1 A U Thor" >expect && + echo " 1 $GIT_AUTHOR_NAME" >expect && git shortlog -ns HEAD >actual && test_cmp expect actual ) @@ -305,26 +305,26 @@ test_expect_success 'cleanup after mailmap.blob tests' ' ' test_expect_success 'single-character name' ' - echo " 1 A <author@example.com>" >expect && + echo " 1 A <$GIT_AUTHOR_EMAIL>" >expect && echo " 1 nick1 <bugs@company.xx>" >>expect && - echo "A <author@example.com>" >.mailmap && + echo "A <$GIT_AUTHOR_EMAIL>" >.mailmap && test_when_finished "rm .mailmap" && git shortlog -es HEAD >actual && test_cmp expect actual ' test_expect_success 'preserve canonical email case' ' - echo " 1 A U Thor <AUTHOR@example.com>" >expect && + echo " 1 $GIT_AUTHOR_NAME <AUTHOR@example.com>" >expect && echo " 1 nick1 <bugs@company.xx>" >>expect && - echo "<AUTHOR@example.com> <author@example.com>" >.mailmap && + echo "<AUTHOR@example.com> <$GIT_AUTHOR_EMAIL>" >.mailmap && test_when_finished "rm .mailmap" && git shortlog -es HEAD >actual && test_cmp expect actual ' # Extended mailmap configurations should give us the following output for shortlog -cat >expect <<\EOF -A U Thor <author@example.com> (1): +cat >expect << EOF +$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> (1): initial CTO <cto@company.xx> (1): @@ -370,7 +370,7 @@ test_expect_success 'Shortlog output (complex mapping)' ' git commit --author "CTO <cto@coompany.xx>" -m seventh && mkdir -p internal_mailmap && - echo "Committed <committer@example.com>" > internal_mailmap/.mailmap && + echo "Committed <$GIT_COMMITTER_EMAIL>" > internal_mailmap/.mailmap && echo "<cto@company.xx> <cto@coompany.xx>" >> internal_mailmap/.mailmap && echo "Some Dude <some@dude.xx> nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap && echo "Other Author <other@author.xx> nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap && @@ -384,27 +384,27 @@ test_expect_success 'Shortlog output (complex mapping)' ' ' # git log with --pretty format which uses the name and email mailmap placemarkers -cat >expect <<\EOF +cat >expect << EOF Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> -Author A U Thor <author@example.com> maps to A U Thor <author@example.com> -Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +Author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> maps to $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> +Committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> maps to Committed <$GIT_COMMITTER_EMAIL> EOF test_expect_success 'Log output (complex mapping)' ' @@ -412,14 +412,42 @@ test_expect_success 'Log output (complex mapping)' ' test_cmp expect actual ' -cat >expect <<\EOF +cat >expect << EOF +Author email cto@coompany.xx has local-part cto +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email me@company.xx has local-part me +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email me@company.xx has local-part me +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email nick2@company.xx has local-part nick2 +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email bugs@company.xx has local-part bugs +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email bugs@company.xx has local-part bugs +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME + +Author email author@example.com has local-part author +Committer email $GIT_COMMITTER_EMAIL has local-part $TEST_COMMITTER_LOCALNAME +EOF + +test_expect_success 'Log output (local-part email address)' ' + git log --pretty=format:"Author email %ae has local-part %al%nCommitter email %ce has local-part %cl%n" >actual && + test_cmp expect actual +' + +cat >expect << EOF Author: CTO <cto@company.xx> Author: Santa Claus <santa.claus@northpole.xx> Author: Santa Claus <santa.claus@northpole.xx> Author: Other Author <other@author.xx> Author: Other Author <other@author.xx> Author: Some Dude <some@dude.xx> -Author: A U Thor <author@example.com> +Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> EOF test_expect_success 'Log output with --use-mailmap' ' @@ -427,14 +455,14 @@ test_expect_success 'Log output with --use-mailmap' ' test_cmp expect actual ' -cat >expect <<\EOF +cat >expect << EOF Author: CTO <cto@company.xx> Author: Santa Claus <santa.claus@northpole.xx> Author: Santa Claus <santa.claus@northpole.xx> Author: Other Author <other@author.xx> Author: Other Author <other@author.xx> Author: Some Dude <some@dude.xx> -Author: A U Thor <author@example.com> +Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> EOF test_expect_success 'Log output with log.mailmap' ' @@ -443,28 +471,28 @@ test_expect_success 'Log output with log.mailmap' ' ' test_expect_success 'log.mailmap=false disables mailmap' ' - cat >expect <<-\EOF && + cat >expect <<- EOF && Author: CTO <cto@coompany.xx> Author: claus <me@company.xx> Author: santa <me@company.xx> Author: nick2 <nick2@company.xx> Author: nick2 <bugs@company.xx> Author: nick1 <bugs@company.xx> - Author: A U Thor <author@example.com> + Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> EOF git -c log.mailmap=False log | grep Author > actual && test_cmp expect actual ' test_expect_success '--no-use-mailmap disables mailmap' ' - cat >expect <<-\EOF && + cat >expect <<- EOF && Author: CTO <cto@coompany.xx> Author: claus <me@company.xx> Author: santa <me@company.xx> Author: nick2 <nick2@company.xx> Author: nick2 <bugs@company.xx> Author: nick1 <bugs@company.xx> - Author: A U Thor <author@example.com> + Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> EOF git log --no-use-mailmap | grep Author > actual && test_cmp expect actual @@ -500,8 +528,8 @@ test_expect_success 'Only grep replaced author with --use-mailmap' ' ' # git blame -cat >expect <<\EOF -^OBJI (A U Thor DATE 1) one +cat >expect <<EOF +^OBJI ($GIT_AUTHOR_NAME DATE 1) one OBJID (Some Dude DATE 2) two OBJID (Other Author DATE 3) three OBJID (Other Author DATE 4) four diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh index 3ae8e51e50..40d27db674 100755 --- a/t/t4214-log-graph-octopus.sh +++ b/t/t4214-log-graph-octopus.sh @@ -26,15 +26,14 @@ test_expect_success 'set up merge history' ' test_expect_success 'log --graph with tricky octopus merge, no color' ' cat >expect.uncolored <<-\EOF && * left - | *---. octopus-merge - | |\ \ \ - |/ / / / + | *-. octopus-merge + |/|\ \ | | | * 4 | | * | 3 | | |/ - | * | 2 + | * / 2 | |/ - * | 1 + * / 1 |/ * initial EOF @@ -47,15 +46,14 @@ test_expect_success 'log --graph with tricky octopus merge with colors' ' test_config log.graphColors red,green,yellow,blue,magenta,cyan && cat >expect.colors <<-\EOF && * left - <RED>|<RESET> *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET> octopus-merge - <RED>|<RESET> <RED>|<RESET><YELLOW>\<RESET> <BLUE>\<RESET> <MAGENTA>\<RESET> - <RED>|<RESET><RED>/<RESET> <YELLOW>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> + <RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>.<RESET> octopus-merge + <RED>|<RESET><RED>/<RESET><YELLOW>|<RESET><BLUE>\<RESET> <MAGENTA>\<RESET> <RED>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4 <RED>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3 <RED>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET> - <RED>|<RESET> * <MAGENTA>|<RESET> 2 + <RED>|<RESET> * <MAGENTA>/<RESET> 2 <RED>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET> - * <MAGENTA>|<RESET> 1 + * <MAGENTA>/<RESET> 1 <MAGENTA>|<RESET><MAGENTA>/<RESET> * initial EOF @@ -74,9 +72,9 @@ test_expect_success 'log --graph with normal octopus merge, no color' ' | | | * 4 | | * | 3 | | |/ - | * | 2 + | * / 2 | |/ - * | 1 + * / 1 |/ * initial EOF @@ -92,9 +90,9 @@ test_expect_success 'log --graph with normal octopus merge with colors' ' <RED>|<RESET> <GREEN>|<RESET> <YELLOW>|<RESET> * 4 <RED>|<RESET> <GREEN>|<RESET> * <BLUE>|<RESET> 3 <RED>|<RESET> <GREEN>|<RESET> <BLUE>|<RESET><BLUE>/<RESET> - <RED>|<RESET> * <BLUE>|<RESET> 2 + <RED>|<RESET> * <BLUE>/<RESET> 2 <RED>|<RESET> <BLUE>|<RESET><BLUE>/<RESET> - * <BLUE>|<RESET> 1 + * <BLUE>/<RESET> 1 <BLUE>|<RESET><BLUE>/<RESET> * initial EOF @@ -112,9 +110,9 @@ test_expect_success 'log --graph with normal octopus merge and child, no color' | | | * 4 | | * | 3 | | |/ - | * | 2 + | * / 2 | |/ - * | 1 + * / 1 |/ * initial EOF @@ -123,7 +121,7 @@ test_expect_success 'log --graph with normal octopus merge and child, no color' test_cmp expect.uncolored actual ' -test_expect_failure 'log --graph with normal octopus and child merge with colors' ' +test_expect_success 'log --graph with normal octopus and child merge with colors' ' cat >expect.colors <<-\EOF && * after-merge *<BLUE>-<RESET><BLUE>-<RESET><MAGENTA>-<RESET><MAGENTA>.<RESET> octopus-merge @@ -131,9 +129,9 @@ test_expect_failure 'log --graph with normal octopus and child merge with colors <GREEN>|<RESET> <YELLOW>|<RESET> <BLUE>|<RESET> * 4 <GREEN>|<RESET> <YELLOW>|<RESET> * <MAGENTA>|<RESET> 3 <GREEN>|<RESET> <YELLOW>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET> - <GREEN>|<RESET> * <MAGENTA>|<RESET> 2 + <GREEN>|<RESET> * <MAGENTA>/<RESET> 2 <GREEN>|<RESET> <MAGENTA>|<RESET><MAGENTA>/<RESET> - * <MAGENTA>|<RESET> 1 + * <MAGENTA>/<RESET> 1 <MAGENTA>|<RESET><MAGENTA>/<RESET> * initial EOF @@ -147,15 +145,14 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col cat >expect.uncolored <<-\EOF && * left | * after-merge - | *---. octopus-merge - | |\ \ \ - |/ / / / + | *-. octopus-merge + |/|\ \ | | | * 4 | | * | 3 | | |/ - | * | 2 + | * / 2 | |/ - * | 1 + * / 1 |/ * initial EOF @@ -164,20 +161,19 @@ test_expect_success 'log --graph with tricky octopus merge and its child, no col test_cmp expect.uncolored actual ' -test_expect_failure 'log --graph with tricky octopus merge and its child with colors' ' +test_expect_success 'log --graph with tricky octopus merge and its child with colors' ' test_config log.graphColors red,green,yellow,blue,magenta,cyan && cat >expect.colors <<-\EOF && * left <RED>|<RESET> * after-merge - <RED>|<RESET> *<MAGENTA>-<RESET><MAGENTA>-<RESET><CYAN>-<RESET><CYAN>.<RESET> octopus-merge - <RED>|<RESET> <RED>|<RESET><BLUE>\<RESET> <MAGENTA>\<RESET> <CYAN>\<RESET> - <RED>|<RESET><RED>/<RESET> <BLUE>/<RESET> <MAGENTA>/<RESET> <CYAN>/<RESET> + <RED>|<RESET> *<CYAN>-<RESET><CYAN>.<RESET> octopus-merge + <RED>|<RESET><RED>/<RESET><BLUE>|<RESET><MAGENTA>\<RESET> <CYAN>\<RESET> <RED>|<RESET> <BLUE>|<RESET> <MAGENTA>|<RESET> * 4 <RED>|<RESET> <BLUE>|<RESET> * <CYAN>|<RESET> 3 <RED>|<RESET> <BLUE>|<RESET> <CYAN>|<RESET><CYAN>/<RESET> - <RED>|<RESET> * <CYAN>|<RESET> 2 + <RED>|<RESET> * <CYAN>/<RESET> 2 <RED>|<RESET> <CYAN>|<RESET><CYAN>/<RESET> - * <CYAN>|<RESET> 1 + * <CYAN>/<RESET> 1 <CYAN>|<RESET><CYAN>/<RESET> * initial EOF @@ -209,7 +205,7 @@ test_expect_success 'log --graph with crossover in octopus merge, no color' ' test_cmp expect.uncolored actual ' -test_expect_failure 'log --graph with crossover in octopus merge with colors' ' +test_expect_success 'log --graph with crossover in octopus merge with colors' ' test_config log.graphColors red,green,yellow,blue,magenta,cyan && cat >expect.colors <<-\EOF && * after-4 @@ -257,7 +253,7 @@ test_expect_success 'log --graph with crossover in octopus merge and its child, test_cmp expect.uncolored actual ' -test_expect_failure 'log --graph with crossover in octopus merge and its child with colors' ' +test_expect_success 'log --graph with crossover in octopus merge and its child with colors' ' test_config log.graphColors red,green,yellow,blue,magenta,cyan && cat >expect.colors <<-\EOF && * after-4 @@ -353,7 +349,7 @@ test_expect_success 'log --graph with unrelated commit and octopus child, no col test_cmp expect.uncolored actual ' -test_expect_failure 'log --graph with unrelated commit and octopus child with colors' ' +test_expect_success 'log --graph with unrelated commit and octopus child with colors' ' test_config log.graphColors red,green,yellow,blue,magenta,cyan && cat >expect.colors <<-\EOF && * after-initial diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh new file mode 100755 index 0000000000..18709a723e --- /dev/null +++ b/t/t4215-log-skewed-merges.sh @@ -0,0 +1,243 @@ +#!/bin/sh + +test_description='git log --graph of skewed merges' + +. ./test-lib.sh + +check_graph () { + cat >expect && + git log --graph --pretty=tformat:%s "$@" >actual.raw && + sed "s/ *$//" actual.raw >actual && + test_cmp expect actual +} + +test_expect_success 'log --graph with merge fusing with its left and right neighbors' ' + git checkout --orphan _p && + test_commit A && + test_commit B && + git checkout -b _q @^ && test_commit C && + git checkout -b _r @^ && test_commit D && + git checkout _p && git merge --no-ff _q _r -m E && + git checkout _r && test_commit F && + git checkout _p && git merge --no-ff _r -m G && + git checkout @^^ && git merge --no-ff _p -m H && + + check_graph <<-\EOF + * H + |\ + | * G + | |\ + | | * F + | * | E + |/|\| + | | * D + | * | C + | |/ + * / B + |/ + * A + EOF +' + +test_expect_success 'log --graph with left-skewed merge' ' + git checkout --orphan 0_p && test_commit 0_A && + git checkout -b 0_q 0_p && test_commit 0_B && + git checkout -b 0_r 0_p && + test_commit 0_C && + test_commit 0_D && + git checkout -b 0_s 0_p && test_commit 0_E && + git checkout -b 0_t 0_p && git merge --no-ff 0_r^ 0_s -m 0_F && + git checkout 0_p && git merge --no-ff 0_s -m 0_G && + git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H && + + check_graph <<-\EOF + *-----. 0_H + |\ \ \ \ + | | | | * 0_G + | |_|_|/| + |/| | | | + | | | * | 0_F + | |_|/|\| + |/| | | | + | | | | * 0_E + | |_|_|/ + |/| | | + | | * | 0_D + | | |/ + | | * 0_C + | |/ + |/| + | * 0_B + |/ + * 0_A + EOF +' + +test_expect_success 'log --graph with nested left-skewed merge' ' + git checkout --orphan 1_p && + test_commit 1_A && + test_commit 1_B && + test_commit 1_C && + git checkout -b 1_q @^ && test_commit 1_D && + git checkout 1_p && git merge --no-ff 1_q -m 1_E && + git checkout -b 1_r @~3 && test_commit 1_F && + git checkout 1_p && git merge --no-ff 1_r -m 1_G && + git checkout @^^ && git merge --no-ff 1_p -m 1_H && + + check_graph <<-\EOF + * 1_H + |\ + | * 1_G + | |\ + | | * 1_F + | * | 1_E + |/| | + | * | 1_D + * | | 1_C + |/ / + * / 1_B + |/ + * 1_A + EOF +' + +test_expect_success 'log --graph with nested left-skewed merge following normal merge' ' + git checkout --orphan 2_p && + test_commit 2_A && + test_commit 2_B && + test_commit 2_C && + git checkout -b 2_q @^^ && + test_commit 2_D && + test_commit 2_E && + git checkout -b 2_r @^ && test_commit 2_F && + git checkout 2_q && + git merge --no-ff 2_r -m 2_G && + git merge --no-ff 2_p^ -m 2_H && + git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J && + git checkout 2_p && git merge --no-ff 2_s -m 2_K && + + check_graph <<-\EOF + * 2_K + |\ + | * 2_J + | |\ + | | * 2_H + | | |\ + | | * | 2_G + | |/| | + | | * | 2_F + | * | | 2_E + | |/ / + | * | 2_D + * | | 2_C + | |/ + |/| + * | 2_B + |/ + * 2_A + EOF +' + +test_expect_success 'log --graph with nested right-skewed merge following left-skewed merge' ' + git checkout --orphan 3_p && + test_commit 3_A && + git checkout -b 3_q && + test_commit 3_B && + test_commit 3_C && + git checkout -b 3_r @^ && + test_commit 3_D && + git checkout 3_q && git merge --no-ff 3_r -m 3_E && + git checkout 3_p && git merge --no-ff 3_q -m 3_F && + git checkout 3_r && test_commit 3_G && + git checkout 3_p && git merge --no-ff 3_r -m 3_H && + git checkout @^^ && git merge --no-ff 3_p -m 3_J && + + check_graph <<-\EOF + * 3_J + |\ + | * 3_H + | |\ + | | * 3_G + | * | 3_F + |/| | + | * | 3_E + | |\| + | | * 3_D + | * | 3_C + | |/ + | * 3_B + |/ + * 3_A + EOF +' + +test_expect_success 'log --graph with right-skewed merge following a left-skewed one' ' + git checkout --orphan 4_p && + test_commit 4_A && + test_commit 4_B && + test_commit 4_C && + git checkout -b 4_q @^^ && test_commit 4_D && + git checkout -b 4_r 4_p^ && git merge --no-ff 4_q -m 4_E && + git checkout -b 4_s 4_p^^ && + git merge --no-ff 4_r -m 4_F && + git merge --no-ff 4_p -m 4_G && + git checkout @^^ && git merge --no-ff 4_s -m 4_H && + + check_graph --date-order <<-\EOF + * 4_H + |\ + | * 4_G + | |\ + | * | 4_F + |/| | + | * | 4_E + | |\ \ + | | * | 4_D + | |/ / + |/| | + | | * 4_C + | |/ + | * 4_B + |/ + * 4_A + EOF +' + +test_expect_success 'log --graph with octopus merge with column joining its penultimate parent' ' + git checkout --orphan 5_p && + test_commit 5_A && + git branch 5_q && + git branch 5_r && + test_commit 5_B && + git checkout 5_q && test_commit 5_C && + git checkout 5_r && test_commit 5_D && + git checkout 5_p && + git merge --no-ff 5_q 5_r -m 5_E && + git checkout 5_q && test_commit 5_F && + git checkout -b 5_s 5_p^ && + git merge --no-ff 5_p 5_q -m 5_G && + git checkout 5_r && + git merge --no-ff 5_s -m 5_H && + + check_graph <<-\EOF + * 5_H + |\ + | *-. 5_G + | |\ \ + | | | * 5_F + | | * | 5_E + | |/|\ \ + | |_|/ / + |/| | / + | | |/ + * | | 5_D + | | * 5_C + | |/ + |/| + | * 5_B + |/ + * 5_A + EOF +' + +test_done diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index d42b3efe39..127b404856 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -660,7 +660,7 @@ test_expect_success 'corrupt commit-graph write (missing tree)' ' git commit-tree -p "$broken" -m "good" "$tree" >good && test_must_fail git commit-graph write --stdin-commits \ <good 2>test_err && - test_i18ngrep "unable to get tree for" test_err + test_i18ngrep "unable to parse commit" test_err ) ' diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index c72ca04399..cd2f87be6a 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -147,6 +147,21 @@ test_expect_success 'write midx with two packs' ' compare_results_with_midx "two packs" +test_expect_success 'write progress off for redirected stderr' ' + git multi-pack-index --object-dir=$objdir write 2>err && + test_line_count = 0 err +' + +test_expect_success 'write force progress on for stderr' ' + git multi-pack-index --object-dir=$objdir --progress write 2>err && + test_file_not_empty err +' + +test_expect_success 'write with the --no-progress option' ' + git multi-pack-index --object-dir=$objdir --no-progress write 2>err && + test_line_count = 0 err +' + test_expect_success 'add more packs' ' for j in $(test_seq 11 20) do @@ -169,6 +184,21 @@ test_expect_success 'verify multi-pack-index success' ' git multi-pack-index verify --object-dir=$objdir ' +test_expect_success 'verify progress off for redirected stderr' ' + git multi-pack-index verify --object-dir=$objdir 2>err && + test_line_count = 0 err +' + +test_expect_success 'verify force progress on for stderr' ' + git multi-pack-index verify --object-dir=$objdir --progress 2>err && + test_file_not_empty err +' + +test_expect_success 'verify with the --no-progress option' ' + git multi-pack-index verify --object-dir=$objdir --no-progress 2>err && + test_line_count = 0 err +' + # usage: corrupt_midx_and_verify <pos> <data> <objdir> <string> corrupt_midx_and_verify() { POS=$1 && @@ -284,6 +314,21 @@ test_expect_success 'git-fsck incorrect offset' ' "git -c core.multipackindex=true fsck" ' +test_expect_success 'repack progress off for redirected stderr' ' + git multi-pack-index --object-dir=$objdir repack 2>err && + test_line_count = 0 err +' + +test_expect_success 'repack force progress on for stderr' ' + git multi-pack-index --object-dir=$objdir --progress repack 2>err && + test_file_not_empty err +' + +test_expect_success 'repack with the --no-progress option' ' + git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && + test_line_count = 0 err +' + test_expect_success 'repack removes multi-pack-index' ' test_path_is_file $objdir/pack/multi-pack-index && GIT_TEST_MULTI_PACK_INDEX=0 git repack -adf && @@ -413,6 +458,30 @@ test_expect_success 'expire does not remove any packs' ' ) ' +test_expect_success 'expire progress off for redirected stderr' ' + ( + cd dup && + git multi-pack-index expire 2>err && + test_line_count = 0 err + ) +' + +test_expect_success 'expire force progress on for stderr' ' + ( + cd dup && + git multi-pack-index --progress expire 2>err && + test_file_not_empty err + ) +' + +test_expect_success 'expire with the --no-progress option' ' + ( + cd dup && + git multi-pack-index --no-progress expire 2>err && + test_line_count = 0 err + ) +' + test_expect_success 'expire removes unreferenced packs' ' ( cd dup && diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 79f7b65f8c..eaa33a852b 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -46,6 +46,14 @@ test_expect_success 'do partial clone 1' ' test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none" ' +test_expect_success 'verify that .promisor file contains refs fetched' ' + ls pc1/.git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 1 promisorlist && + git -C srv.bare rev-list HEAD >headhash && + grep "$(cat headhash) HEAD" $(cat promisorlist) && + grep "$(cat headhash) refs/heads/master" $(cat promisorlist) +' + # checkout master to force dynamic object fetch of blobs at HEAD. test_expect_success 'verify checkout with dynamic object fetch' ' git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed && diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index cfb74d0e03..ebdc49c496 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -109,31 +109,35 @@ commit $head1 EOF # we don't test relative here -test_format author %an%n%ae%n%ad%n%aD%n%at <<EOF +test_format author %an%n%ae%n%al%n%ad%n%aD%n%at <<EOF commit $head2 -A U Thor -author@example.com +$GIT_AUTHOR_NAME +$GIT_AUTHOR_EMAIL +$TEST_AUTHOR_LOCALNAME Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 commit $head1 -A U Thor -author@example.com +$GIT_AUTHOR_NAME +$GIT_AUTHOR_EMAIL +$TEST_AUTHOR_LOCALNAME Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 EOF -test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<EOF +test_format committer %cn%n%ce%n%cl%n%cd%n%cD%n%ct <<EOF commit $head2 -C O Mitter -committer@example.com +$GIT_COMMITTER_NAME +$GIT_COMMITTER_EMAIL +$TEST_COMMITTER_LOCALNAME Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 commit $head1 -C O Mitter -committer@example.com +$GIT_COMMITTER_NAME +$GIT_COMMITTER_EMAIL +$TEST_COMMITTER_LOCALNAME Thu Apr 7 15:13:13 2005 -0700 Thu, 7 Apr 2005 15:13:13 -0700 1112911993 @@ -410,7 +414,7 @@ test_expect_success 'empty email' ' test_tick && C=$(GIT_AUTHOR_EMAIL= git commit-tree HEAD^{tree} </dev/null) && A=$(git show --pretty=format:%an,%ae,%ad%n -s $C) && - verbose test "$A" = "A U Thor,,Thu Apr 7 15:14:13 2005 -0700" + verbose test "$A" = "$GIT_AUTHOR_NAME,,Thu Apr 7 15:14:13 2005 -0700" ' test_expect_success 'del LF before empty (1)' ' @@ -495,7 +499,7 @@ test_expect_success '%gd shortens ref name' ' ' test_expect_success 'reflog identity' ' - echo "C O Mitter:committer@example.com" >expect && + echo "$GIT_COMMITTER_NAME:$GIT_COMMITTER_EMAIL" >expect && git log -g -1 --format="%gn:%ge" >actual && test_cmp expect actual ' diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh index f7181d1d6a..f5e6e92f5b 100755 --- a/t/t6016-rev-list-graph-simplify-history.sh +++ b/t/t6016-rev-list-graph-simplify-history.sh @@ -67,11 +67,10 @@ test_expect_success '--graph --all' ' echo "| * $C4" >> expected && echo "| * $C3" >> expected && echo "* | $A5" >> expected && - echo "| | " >> expected && - echo "| \\ " >> expected && - echo "*-. \\ $A4" >> expected && - echo "|\\ \\ \\ " >> expected && - echo "| | |/ " >> expected && + echo "| | " >> expected && + echo "| \\ " >> expected && + echo "*-. | $A4" >> expected && + echo "|\\ \\| " >> expected && echo "| | * $C2" >> expected && echo "| | * $C1" >> expected && echo "| * | $B2" >> expected && @@ -97,11 +96,10 @@ test_expect_success '--graph --simplify-by-decoration' ' echo "| * $C4" >> expected && echo "| * $C3" >> expected && echo "* | $A5" >> expected && - echo "| | " >> expected && - echo "| \\ " >> expected && - echo "*-. \\ $A4" >> expected && - echo "|\\ \\ \\ " >> expected && - echo "| | |/ " >> expected && + echo "| | " >> expected && + echo "| \\ " >> expected && + echo "*-. | $A4" >> expected && + echo "|\\ \\| " >> expected && echo "| | * $C2" >> expected && echo "| | * $C1" >> expected && echo "| * | $B2" >> expected && @@ -131,9 +129,8 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' ' echo "| * $C4" >> expected && echo "| * $C3" >> expected && echo "* | $A5" >> expected && - echo "* | $A4" >> expected && - echo "|\\ \\ " >> expected && - echo "| |/ " >> expected && + echo "* | $A4" >> expected && + echo "|\\| " >> expected && echo "| * $C2" >> expected && echo "| * $C1" >> expected && echo "* | $A3" >> expected && @@ -151,9 +148,8 @@ test_expect_success '--graph --full-history -- bar.txt' ' echo "|\\ " >> expected && echo "| * $C4" >> expected && echo "* | $A5" >> expected && - echo "* | $A4" >> expected && - echo "|\\ \\ " >> expected && - echo "| |/ " >> expected && + echo "* | $A4" >> expected && + echo "|\\| " >> expected && echo "* | $A3" >> expected && echo "|/ " >> expected && echo "* $A2" >> expected && @@ -255,7 +251,7 @@ test_expect_success '--graph --boundary ^C3' ' echo "* | | | $A3" >> expected && echo "o | | | $A2" >> expected && echo "|/ / / " >> expected && - echo "o | | $A1" >> expected && + echo "o / / $A1" >> expected && echo " / / " >> expected && echo "| o $C3" >> expected && echo "|/ " >> expected && diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index c5b57f40c3..b047cf1c1c 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -5,7 +5,7 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses" . ./test-lib.sh -test_expect_success 'setup rename/delete + untracked file' ' +test_setup_rename_delete_untracked () { test_create_repo rename-delete-untracked && ( cd rename-delete-untracked && @@ -29,9 +29,10 @@ test_expect_success 'setup rename/delete + untracked file' ' git commit -m track-people-instead-of-objects && echo "Myyy PRECIOUSSS" >ring ) -' +} test_expect_success "Does git preserve Gollum's precious artifact?" ' + test_setup_rename_delete_untracked && ( cd rename-delete-untracked && @@ -49,7 +50,7 @@ test_expect_success "Does git preserve Gollum's precious artifact?" ' # # We should be able to merge B & C cleanly -test_expect_success 'setup rename/modify/add-source conflict' ' +test_setup_rename_modify_add_source () { test_create_repo rename-modify-add-source && ( cd rename-modify-add-source && @@ -70,9 +71,10 @@ test_expect_success 'setup rename/modify/add-source conflict' ' git add a && git commit -m C ) -' +} test_expect_failure 'rename/modify/add-source conflict resolvable' ' + test_setup_rename_modify_add_source && ( cd rename-modify-add-source && @@ -88,7 +90,7 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' ' ) ' -test_expect_success 'setup resolvable conflict missed if rename missed' ' +test_setup_break_detection_1 () { test_create_repo break-detection-1 && ( cd break-detection-1 && @@ -110,9 +112,10 @@ test_expect_success 'setup resolvable conflict missed if rename missed' ' git add a && git commit -m C ) -' +} test_expect_failure 'conflict caused if rename not detected' ' + test_setup_break_detection_1 && ( cd break-detection-1 && @@ -135,7 +138,7 @@ test_expect_failure 'conflict caused if rename not detected' ' ) ' -test_expect_success 'setup conflict resolved wrong if rename missed' ' +test_setup_break_detection_2 () { test_create_repo break-detection-2 && ( cd break-detection-2 && @@ -160,9 +163,10 @@ test_expect_success 'setup conflict resolved wrong if rename missed' ' git add a && git commit -m E ) -' +} test_expect_failure 'missed conflict if rename not detected' ' + test_setup_break_detection_2 && ( cd break-detection-2 && @@ -182,7 +186,7 @@ test_expect_failure 'missed conflict if rename not detected' ' # Commit B: rename a->b # Commit C: rename a->b, add unrelated a -test_expect_success 'setup undetected rename/add-source causes data loss' ' +test_setup_break_detection_3 () { test_create_repo break-detection-3 && ( cd break-detection-3 && @@ -202,9 +206,10 @@ test_expect_success 'setup undetected rename/add-source causes data loss' ' git add a && git commit -m C ) -' +} test_expect_failure 'detect rename/add-source and preserve all data' ' + test_setup_break_detection_3 && ( cd break-detection-3 && @@ -231,6 +236,7 @@ test_expect_failure 'detect rename/add-source and preserve all data' ' ' test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' + test_setup_break_detection_3 && ( cd break-detection-3 && @@ -256,10 +262,10 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other ) ' -test_expect_success 'setup content merge + rename/directory conflict' ' - test_create_repo rename-directory-1 && +test_setup_rename_directory () { + test_create_repo rename-directory-$1 && ( - cd rename-directory-1 && + cd rename-directory-$1 && printf "1\n2\n3\n4\n5\n6\n" >file && git add file && @@ -290,11 +296,12 @@ test_expect_success 'setup content merge + rename/directory conflict' ' test_tick && git commit -m left ) -' +} test_expect_success 'rename/directory conflict + clean content merge' ' + test_setup_rename_directory 1a && ( - cd rename-directory-1 && + cd rename-directory-1a && git checkout left-clean^0 && @@ -320,8 +327,9 @@ test_expect_success 'rename/directory conflict + clean content merge' ' ' test_expect_success 'rename/directory conflict + content merge conflict' ' + test_setup_rename_directory 1b && ( - cd rename-directory-1 && + cd rename-directory-1b && git reset --hard && git clean -fdqx && @@ -358,7 +366,7 @@ test_expect_success 'rename/directory conflict + content merge conflict' ' ) ' -test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' ' +test_setup_rename_directory_2 () { test_create_repo rename-directory-2 && ( cd rename-directory-2 && @@ -385,9 +393,10 @@ test_expect_success 'setup content merge + rename/directory conflict w/ disappea test_tick && git commit -m left ) -' +} test_expect_success 'disappearing dir in rename/directory conflict handled' ' + test_setup_rename_directory_2 && ( cd rename-directory-2 && @@ -416,10 +425,10 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' ' # Commit A: rename a->b, modifying b too # Commit B: modify a, add different b -test_expect_success 'setup rename-with-content-merge vs. add' ' - test_create_repo rename-with-content-merge-and-add && +test_setup_rename_with_content_merge_and_add () { + test_create_repo rename-with-content-merge-and-add-$1 && ( - cd rename-with-content-merge-and-add && + cd rename-with-content-merge-and-add-$1 && test_seq 1 5 >a && git add a && @@ -438,11 +447,12 @@ test_expect_success 'setup rename-with-content-merge vs. add' ' git add a b && git commit -m B ) -' +} test_expect_success 'handle rename-with-content-merge vs. add' ' + test_setup_rename_with_content_merge_and_add AB && ( - cd rename-with-content-merge-and-add && + cd rename-with-content-merge-and-add-AB && git checkout A^0 && @@ -483,8 +493,9 @@ test_expect_success 'handle rename-with-content-merge vs. add' ' ' test_expect_success 'handle rename-with-content-merge vs. add, merge other way' ' + test_setup_rename_with_content_merge_and_add BA && ( - cd rename-with-content-merge-and-add && + cd rename-with-content-merge-and-add-BA && git reset --hard && git clean -fdx && @@ -539,7 +550,7 @@ test_expect_success 'handle rename-with-content-merge vs. add, merge other way' # * The working copy should have two files, both of form c~<unique>; does it? # * Nothing else should be present. Is anything? -test_expect_success 'setup rename/rename (2to1) + modify/modify' ' +test_setup_rename_rename_2to1 () { test_create_repo rename-rename-2to1 && ( cd rename-rename-2to1 && @@ -562,9 +573,10 @@ test_expect_success 'setup rename/rename (2to1) + modify/modify' ' git add a && git commit -m C ) -' +} test_expect_success 'handle rename/rename (2to1) conflict correctly' ' + test_setup_rename_rename_2to1 && ( cd rename-rename-2to1 && @@ -610,7 +622,7 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' # Commit A: new file: a # Commit B: rename a->b # Commit C: rename a->c -test_expect_success 'setup simple rename/rename (1to2) conflict' ' +test_setup_rename_rename_1to2 () { test_create_repo rename-rename-1to2 && ( cd rename-rename-1to2 && @@ -631,9 +643,10 @@ test_expect_success 'setup simple rename/rename (1to2) conflict' ' test_tick && git commit -m C ) -' +} test_expect_success 'merge has correct working tree contents' ' + test_setup_rename_rename_1to2 && ( cd rename-rename-1to2 && @@ -667,7 +680,7 @@ test_expect_success 'merge has correct working tree contents' ' # # Merging of B & C should NOT be clean; there's a rename/rename conflict -test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' +test_setup_rename_rename_1to2_add_source_1 () { test_create_repo rename-rename-1to2-add-source-1 && ( cd rename-rename-1to2-add-source-1 && @@ -687,9 +700,10 @@ test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' git add a && git commit -m C ) -' +} test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' + test_setup_rename_rename_1to2_add_source_1 && ( cd rename-rename-1to2-add-source-1 && @@ -714,7 +728,7 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ) ' -test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' +test_setup_rename_rename_1to2_add_source_2 () { test_create_repo rename-rename-1to2-add-source-2 && ( cd rename-rename-1to2-add-source-2 && @@ -737,9 +751,10 @@ test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' test_tick && git commit -m two ) -' +} test_expect_failure 'rename/rename/add-source still tracks new a file' ' + test_setup_rename_rename_1to2_add_source_2 && ( cd rename-rename-1to2-add-source-2 && @@ -759,7 +774,7 @@ test_expect_failure 'rename/rename/add-source still tracks new a file' ' ) ' -test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' +test_setup_rename_rename_1to2_add_dest () { test_create_repo rename-rename-1to2-add-dest && ( cd rename-rename-1to2-add-dest && @@ -784,9 +799,10 @@ test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' test_tick && git commit -m two ) -' +} test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' + test_setup_rename_rename_1to2_add_dest && ( cd rename-rename-1to2-add-dest && @@ -838,7 +854,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting # Commit B: rename foo->bar # Expected: CONFLICT (rename/add/delete), two-way merged bar -test_expect_success 'rad-setup: rename/add/delete conflict' ' +test_setup_rad () { test_create_repo rad && ( cd rad && @@ -860,9 +876,10 @@ test_expect_success 'rad-setup: rename/add/delete conflict' ' git mv foo bar && git commit -m "rename foo to bar" ) -' +} test_expect_failure 'rad-check: rename/add/delete conflict' ' + test_setup_rad && ( cd rad && @@ -904,7 +921,7 @@ test_expect_failure 'rad-check: rename/add/delete conflict' ' # Commit B: rename bar->baz, rm foo # Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz -test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' ' +test_setup_rrdd () { test_create_repo rrdd && ( cd rrdd && @@ -927,9 +944,10 @@ test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' ' git rm foo && git commit -m "Rename bar, remove foo" ) -' +} test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' + test_setup_rrdd && ( cd rrdd && @@ -973,7 +991,7 @@ test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' # Expected: six CONFLICT(rename/rename) messages, each path in two of the # multi-way merged contents found in two, four, six -test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' ' +test_setup_mod6 () { test_create_repo mod6 && ( cd mod6 && @@ -1009,9 +1027,10 @@ test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename test_tick && git commit -m "B" ) -' +} test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' + test_setup_mod6 && ( cd mod6 && @@ -1108,7 +1127,8 @@ test_conflicts_with_adds_and_renames() { # files. Is it present? # 4) There should not be any three~* files in the working # tree - test_expect_success "setup simple $sideL/$sideR conflict" ' + test_setup_collision_conflict () { + #test_expect_success "setup simple $sideL/$sideR conflict" ' test_create_repo simple_${sideL}_${sideR} && ( cd simple_${sideL}_${sideR} && @@ -1185,9 +1205,11 @@ test_conflicts_with_adds_and_renames() { fi && test_tick && git commit -m R ) - ' + #' + } test_expect_success "check simple $sideL/$sideR conflict" ' + test_setup_collision_conflict && ( cd simple_${sideL}_${sideR} && @@ -1254,7 +1276,7 @@ test_conflicts_with_adds_and_renames add add # # So, we have four different conflicting files that all end up at path # 'three'. -test_expect_success 'setup nested conflicts from rename/rename(2to1)' ' +test_setup_nested_conflicts_from_rename_rename () { test_create_repo nested_conflicts_from_rename_rename && ( cd nested_conflicts_from_rename_rename && @@ -1305,9 +1327,10 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' ' git add one three && test_tick && git commit -m german ) -' +} test_expect_success 'check nested conflicts from rename/rename(2to1)' ' + test_setup_nested_conflicts_from_rename_rename && ( cd nested_conflicts_from_rename_rename && diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index c966147d5d..bd2f97ba95 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -38,7 +38,7 @@ test_description="recursive merge with directory renames" # Commit B: z/{b,c,d,e/f} # Expected: y/{b,c,d,e/f} -test_expect_success '1a-setup: Simple directory rename detection' ' +test_setup_1a () { test_create_repo 1a && ( cd 1a && @@ -67,9 +67,10 @@ test_expect_success '1a-setup: Simple directory rename detection' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '1a-check: Simple directory rename detection' ' +test_expect_success '1a: Simple directory rename detection' ' + test_setup_1a && ( cd 1a && @@ -103,7 +104,7 @@ test_expect_success '1a-check: Simple directory rename detection' ' # Commit B: y/{b,c,d} # Expected: y/{b,c,d,e} -test_expect_success '1b-setup: Merge a directory with another' ' +test_setup_1b () { test_create_repo 1b && ( cd 1b && @@ -134,9 +135,10 @@ test_expect_success '1b-setup: Merge a directory with another' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '1b-check: Merge a directory with another' ' +test_expect_success '1b: Merge a directory with another' ' + test_setup_1b && ( cd 1b && @@ -165,7 +167,7 @@ test_expect_success '1b-check: Merge a directory with another' ' # Commit B: z/{b,c,d} # Expected: y/{b,c,d} (because x/d -> z/d -> y/d) -test_expect_success '1c-setup: Transitive renaming' ' +test_setup_1c () { test_create_repo 1c && ( cd 1c && @@ -193,9 +195,10 @@ test_expect_success '1c-setup: Transitive renaming' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '1c-check: Transitive renaming' ' +test_expect_success '1c: Transitive renaming' ' + test_setup_1c && ( cd 1c && @@ -227,7 +230,7 @@ test_expect_success '1c-check: Transitive renaming' ' # Note: y/m & z/n should definitely move into x. By the same token, both # y/wham_1 & z/wham_2 should too...giving us a conflict. -test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' ' +test_setup_1d () { test_create_repo 1d && ( cd 1d && @@ -262,9 +265,10 @@ test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) con test_tick && git commit -m "B" ) -' +} -test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' ' +test_expect_success '1d: Directory renames cause a rename/rename(2to1) conflict' ' + test_setup_1d && ( cd 1d && @@ -313,7 +317,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con # Commit B: z/{oldb,oldc,d} # Expected: y/{newb,newc,d} -test_expect_success '1e-setup: Renamed directory, with all files being renamed too' ' +test_setup_1e () { test_create_repo 1e && ( cd 1e && @@ -342,9 +346,10 @@ test_expect_success '1e-setup: Renamed directory, with all files being renamed t test_tick && git commit -m "B" ) -' +} -test_expect_success '1e-check: Renamed directory, with all files being renamed too' ' +test_expect_success '1e: Renamed directory, with all files being renamed too' ' + test_setup_1e && ( cd 1e && @@ -371,7 +376,7 @@ test_expect_success '1e-check: Renamed directory, with all files being renamed t # Commit B: y/{b,c}, x/{d,e,f} # Expected: y/{b,c}, x/{d,e,f,g} -test_expect_success '1f-setup: Split a directory into two other directories' ' +test_setup_1f () { test_create_repo 1f && ( cd 1f && @@ -408,9 +413,10 @@ test_expect_success '1f-setup: Split a directory into two other directories' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '1f-check: Split a directory into two other directories' ' +test_expect_success '1f: Split a directory into two other directories' ' + test_setup_1f && ( cd 1f && @@ -459,7 +465,7 @@ test_expect_success '1f-check: Split a directory into two other directories' ' # Commit A: y/b, w/c # Commit B: z/{b,c,d} # Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict -test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' ' +test_setup_2a () { test_create_repo 2a && ( cd 2a && @@ -489,9 +495,10 @@ test_expect_success '2a-setup: Directory split into two on one side, with equal test_tick && git commit -m "B" ) -' +} -test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' ' +test_expect_success '2a: Directory split into two on one side, with equal numbers of paths' ' + test_setup_2a && ( cd 2a && @@ -520,7 +527,7 @@ test_expect_success '2a-check: Directory split into two on one side, with equal # Commit A: y/b, w/c # Commit B: z/{b,c}, x/d # Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict -test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' ' +test_setup_2b () { test_create_repo 2b && ( cd 2b && @@ -551,9 +558,10 @@ test_expect_success '2b-setup: Directory split into two on one side, with equal test_tick && git commit -m "B" ) -' +} -test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' ' +test_expect_success '2b: Directory split into two on one side, with equal numbers of paths' ' + test_setup_2b && ( cd 2b && @@ -601,7 +609,7 @@ test_expect_success '2b-check: Directory split into two on one side, with equal # Commit A: z/{b,c,d} (no change) # Commit B: y/{b,c}, x/d # Expected: y/{b,c}, x/d -test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' ' +test_setup_3a () { test_create_repo 3a && ( cd 3a && @@ -632,9 +640,10 @@ test_expect_success '3a-setup: Avoid implicit rename if involved as source on ot test_tick && git commit -m "B" ) -' +} -test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' ' +test_expect_success '3a: Avoid implicit rename if involved as source on other side' ' + test_setup_3a && ( cd 3a && @@ -664,7 +673,7 @@ test_expect_success '3a-check: Avoid implicit rename if involved as source on ot # get it involved in directory rename detection. If it were, we might # end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a # rename/rename/rename(1to3) conflict, which is just weird. -test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' ' +test_setup_3b () { test_create_repo 3b && ( cd 3b && @@ -697,9 +706,10 @@ test_expect_success '3b-setup: Avoid implicit rename if involved as source on cu test_tick && git commit -m "B" ) -' +} -test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' ' +test_expect_success '3b: Avoid implicit rename if involved as source on current side' ' + test_setup_3b && ( cd 3b && @@ -786,7 +796,7 @@ test_expect_success '3b-check: Avoid implicit rename if involved as source on cu # Expected: y/{b,c,d}, z/{e,f} # NOTE: Even though most files from z moved to y, we don't want f to follow. -test_expect_success '4a-setup: Directory split, with original directory still present' ' +test_setup_4a () { test_create_repo 4a && ( cd 4a && @@ -818,9 +828,10 @@ test_expect_success '4a-setup: Directory split, with original directory still pr test_tick && git commit -m "B" ) -' +} -test_expect_success '4a-check: Directory split, with original directory still present' ' +test_expect_success '4a: Directory split, with original directory still present' ' + test_setup_4a && ( cd 4a && @@ -874,7 +885,7 @@ test_expect_success '4a-check: Directory split, with original directory still pr # of history, giving us no way to represent this conflict in the # index. -test_expect_success '5a-setup: Merge directories, other side adds files to original and target' ' +test_setup_5a () { test_create_repo 5a && ( cd 5a && @@ -907,9 +918,10 @@ test_expect_success '5a-setup: Merge directories, other side adds files to origi test_tick && git commit -m "B" ) -' +} -test_expect_success '5a-check: Merge directories, other side adds files to original and target' ' +test_expect_success '5a: Merge directories, other side adds files to original and target' ' + test_setup_5a && ( cd 5a && @@ -948,7 +960,7 @@ test_expect_success '5a-check: Merge directories, other side adds files to origi # cause us to bail on directory rename detection for that path, falling # back to git behavior without the directory rename detection. -test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' ' +test_setup_5b () { test_create_repo 5b && ( cd 5b && @@ -981,9 +993,10 @@ test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflic test_tick && git commit -m "B" ) -' +} -test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' ' +test_expect_success '5b: Rename/delete in order to get add/add/add conflict' ' + test_setup_5b && ( cd 5b && @@ -1024,7 +1037,7 @@ test_expect_success '5b-check: Rename/delete in order to get add/add/add conflic # y/d are y/d_2 and y/d_4. We still do the move from z/e to y/e, # though, because it doesn't have anything in the way. -test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' ' +test_setup_5c () { test_create_repo 5c && ( cd 5c && @@ -1061,9 +1074,10 @@ test_expect_success '5c-setup: Transitive rename would cause rename/rename/renam test_tick && git commit -m "B" ) -' +} -test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' ' +test_expect_success '5c: Transitive rename would cause rename/rename/rename/add/add/add' ' + test_setup_5c && ( cd 5c && @@ -1113,7 +1127,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam # detection for z/d_2, but that doesn't prevent us from applying the # directory rename detection for z/f -> y/f. -test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' ' +test_setup_5d () { test_create_repo 5d && ( cd 5d && @@ -1145,9 +1159,10 @@ test_expect_success '5d-setup: Directory/file/file conflict due to directory ren test_tick && git commit -m "B" ) -' +} -test_expect_success '5d-check: Directory/file/file conflict due to directory rename' ' +test_expect_success '5d: Directory/file/file conflict due to directory rename' ' + test_setup_5d && ( cd 5d && @@ -1205,7 +1220,7 @@ test_expect_success '5d-check: Directory/file/file conflict due to directory ren # them under y/ doesn't accidentally catch z/d and make it look like # it is also involved in a rename/delete conflict. -test_expect_success '6a-setup: Tricky rename/delete' ' +test_setup_6a () { test_create_repo 6a && ( cd 6a && @@ -1235,9 +1250,10 @@ test_expect_success '6a-setup: Tricky rename/delete' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '6a-check: Tricky rename/delete' ' +test_expect_success '6a: Tricky rename/delete' ' + test_setup_6a && ( cd 6a && @@ -1271,7 +1287,7 @@ test_expect_success '6a-check: Tricky rename/delete' ' # but B did that rename and still decided to put the file into z/, # so we probably shouldn't apply directory rename detection for it. -test_expect_success '6b-setup: Same rename done on both sides' ' +test_setup_6b () { test_create_repo 6b && ( cd 6b && @@ -1300,9 +1316,10 @@ test_expect_success '6b-setup: Same rename done on both sides' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '6b-check: Same rename done on both sides' ' +test_expect_success '6b: Same rename done on both sides' ' + test_setup_6b && ( cd 6b && @@ -1334,7 +1351,7 @@ test_expect_success '6b-check: Same rename done on both sides' ' # NOTE: Seems obvious, but just checking that the implementation doesn't # "accidentally detect a rename" and give us y/{b,c,d}. -test_expect_success '6c-setup: Rename only done on same side' ' +test_setup_6c () { test_create_repo 6c && ( cd 6c && @@ -1362,9 +1379,10 @@ test_expect_success '6c-setup: Rename only done on same side' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '6c-check: Rename only done on same side' ' +test_expect_success '6c: Rename only done on same side' ' + test_setup_6c && ( cd 6c && @@ -1396,7 +1414,7 @@ test_expect_success '6c-check: Rename only done on same side' ' # NOTE: Again, this seems obvious but just checking that the implementation # doesn't "accidentally detect a rename" and give us y/{b,c,d}. -test_expect_success '6d-setup: We do not always want transitive renaming' ' +test_setup_6d () { test_create_repo 6d && ( cd 6d && @@ -1424,9 +1442,10 @@ test_expect_success '6d-setup: We do not always want transitive renaming' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '6d-check: We do not always want transitive renaming' ' +test_expect_success '6d: We do not always want transitive renaming' ' + test_setup_6d && ( cd 6d && @@ -1458,7 +1477,7 @@ test_expect_success '6d-check: We do not always want transitive renaming' ' # doesn't "accidentally detect a rename" and give us y/{b,c} + # add/add conflict on y/d_1 vs y/d_2. -test_expect_success '6e-setup: Add/add from one side' ' +test_setup_6e () { test_create_repo 6e && ( cd 6e && @@ -1487,9 +1506,10 @@ test_expect_success '6e-setup: Add/add from one side' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '6e-check: Add/add from one side' ' +test_expect_success '6e: Add/add from one side' ' + test_setup_6e && ( cd 6e && @@ -1552,7 +1572,7 @@ test_expect_success '6e-check: Add/add from one side' ' # Expected: y/d, CONFLICT(rename/rename for both z/b and z/c) # NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d. -test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' +test_setup_7a () { test_create_repo 7a && ( cd 7a && @@ -1583,9 +1603,10 @@ test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS test_tick && git commit -m "B" ) -' +} -test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' +test_expect_success '7a: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + test_setup_7a && ( cd 7a && @@ -1623,7 +1644,7 @@ test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS # Commit B: z/{b,c,d_1}, w/d_2 # Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d) -test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' ' +test_setup_7b () { test_create_repo 7b && ( cd 7b && @@ -1655,9 +1676,10 @@ test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive r test_tick && git commit -m "B" ) -' +} -test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' ' +test_expect_success '7b: rename/rename(2to1), but only due to transitive rename' ' + test_setup_7b && ( cd 7b && @@ -1702,7 +1724,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r # neither CONFLICT(x/d -> w/d vs. z/d) # nor CONFLiCT x/d -> w/d vs. y/d vs. z/d) -test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' ' +test_setup_7c () { test_create_repo 7c && ( cd 7c && @@ -1732,9 +1754,10 @@ test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may test_tick && git commit -m "B" ) -' +} -test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' ' +test_expect_success '7c: rename/rename(1to...2or3); transitive rename may add complexity' ' + test_setup_7c && ( cd 7c && @@ -1766,7 +1789,7 @@ test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may # Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d) # NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d) -test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' ' +test_setup_7d () { test_create_repo 7d && ( cd 7d && @@ -1796,9 +1819,10 @@ test_expect_success '7d-setup: transitive rename involved in rename/delete; how test_tick && git commit -m "B" ) -' +} -test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' ' +test_expect_success '7d: transitive rename involved in rename/delete; how is it reported?' ' + test_setup_7d && ( cd 7d && @@ -1851,7 +1875,7 @@ test_expect_success '7d-check: transitive rename involved in rename/delete; how # see testcases 9c and 9d for further discussion of this issue and # how it's resolved. -test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' ' +test_setup_7e () { test_create_repo 7e && ( cd 7e && @@ -1886,9 +1910,10 @@ test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in th test_tick && git commit -m "B" ) -' +} -test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' ' +test_expect_success '7e: transitive rename in rename/delete AND dirs in the way' ' + test_setup_7e && ( cd 7e && @@ -1945,7 +1970,7 @@ test_expect_success '7e-check: transitive rename in rename/delete AND dirs in th # simple rule from section 5 prevents me from handling this as optimally as # we potentially could. -test_expect_success '8a-setup: Dual-directory rename, one into the others way' ' +test_setup_8a () { test_create_repo 8a && ( cd 8a && @@ -1977,9 +2002,10 @@ test_expect_success '8a-setup: Dual-directory rename, one into the others way' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '8a-check: Dual-directory rename, one into the others way' ' +test_expect_success '8a: Dual-directory rename, one into the others way' ' + test_setup_8a && ( cd 8a && @@ -2023,7 +2049,7 @@ test_expect_success '8a-check: Dual-directory rename, one into the others way' ' # making us fall back to pre-directory-rename-detection behavior for both # e_1 and e_2. -test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' ' +test_setup_8b () { test_create_repo 8b && ( cd 8b && @@ -2055,9 +2081,10 @@ test_expect_success '8b-setup: Dual-directory rename, one into the others way, w test_tick && git commit -m "B" ) -' +} -test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' ' +test_expect_success '8b: Dual-directory rename, one into the others way, with conflicting filenames' ' + test_setup_8b && ( cd 8b && @@ -2096,7 +2123,7 @@ test_expect_success '8b-check: Dual-directory rename, one into the others way, w # rename/rename(1to2) conflicts -- see testcase 9h. See also # notes in 8d. -test_expect_success '8c-setup: modify/delete or rename+modify/delete?' ' +test_setup_8c () { test_create_repo 8c && ( cd 8c && @@ -2127,9 +2154,10 @@ test_expect_success '8c-setup: modify/delete or rename+modify/delete?' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '8c-check: modify/delete or rename+modify/delete' ' +test_expect_success '8c: modify/delete or rename+modify/delete' ' + test_setup_8c && ( cd 8c && @@ -2175,7 +2203,7 @@ test_expect_success '8c-check: modify/delete or rename+modify/delete' ' # during merging are supposed to be about opposite sides doing things # differently. -test_expect_success '8d-setup: rename/delete...or not?' ' +test_setup_8d () { test_create_repo 8d && ( cd 8d && @@ -2204,9 +2232,10 @@ test_expect_success '8d-setup: rename/delete...or not?' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '8d-check: rename/delete...or not?' ' +test_expect_success '8d: rename/delete...or not?' ' + test_setup_8d && ( cd 8d && @@ -2250,7 +2279,7 @@ test_expect_success '8d-check: rename/delete...or not?' ' # about the ramifications of doing that, I didn't know how to rule out # that opening other weird edge and corner cases so I just punted. -test_expect_success '8e-setup: Both sides rename, one side adds to original directory' ' +test_setup_8e () { test_create_repo 8e && ( cd 8e && @@ -2279,9 +2308,10 @@ test_expect_success '8e-setup: Both sides rename, one side adds to original dire test_tick && git commit -m "B" ) -' +} -test_expect_success '8e-check: Both sides rename, one side adds to original directory' ' +test_expect_success '8e: Both sides rename, one side adds to original directory' ' + test_setup_8e && ( cd 8e && @@ -2333,7 +2363,7 @@ test_expect_success '8e-check: Both sides rename, one side adds to original dire # of which one had the most paths going to it. A naive implementation # of that could take the new file in commit B at z/i to x/w/i or x/i. -test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' ' +test_setup_9a () { test_create_repo 9a && ( cd 9a && @@ -2366,9 +2396,10 @@ test_expect_success '9a-setup: Inner renamed directory within outer renamed dire test_tick && git commit -m "B" ) -' +} -test_expect_success '9a-check: Inner renamed directory within outer renamed directory' ' +test_expect_success '9a: Inner renamed directory within outer renamed directory' ' + test_setup_9a && ( cd 9a && @@ -2404,7 +2435,7 @@ test_expect_success '9a-check: Inner renamed directory within outer renamed dire # Commit B: z/{b,c,d_3} # Expected: y/{b,c,d_merged} -test_expect_success '9b-setup: Transitive rename with content merge' ' +test_setup_9b () { test_create_repo 9b && ( cd 9b && @@ -2436,9 +2467,10 @@ test_expect_success '9b-setup: Transitive rename with content merge' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '9b-check: Transitive rename with content merge' ' +test_expect_success '9b: Transitive rename with content merge' ' + test_setup_9b && ( cd 9b && @@ -2491,7 +2523,7 @@ test_expect_success '9b-check: Transitive rename with content merge' ' # away, then ignore that particular rename from the other side of # history for any implicit directory renames. -test_expect_success '9c-setup: Doubly transitive rename?' ' +test_setup_9c () { test_create_repo 9c && ( cd 9c && @@ -2526,9 +2558,10 @@ test_expect_success '9c-setup: Doubly transitive rename?' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '9c-check: Doubly transitive rename?' ' +test_expect_success '9c: Doubly transitive rename?' ' + test_setup_9c && ( cd 9c && @@ -2579,7 +2612,7 @@ test_expect_success '9c-check: Doubly transitive rename?' ' # simple rules that are consistent with what we need for all the other # testcases and simplifies things for the user. -test_expect_success '9d-setup: N-way transitive rename?' ' +test_setup_9d () { test_create_repo 9d && ( cd 9d && @@ -2614,9 +2647,10 @@ test_expect_success '9d-setup: N-way transitive rename?' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '9d-check: N-way transitive rename?' ' +test_expect_success '9d: N-way transitive rename?' ' + test_setup_9d && ( cd 9d && @@ -2653,7 +2687,7 @@ test_expect_success '9d-check: N-way transitive rename?' ' # Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings, # dir1/yo, dir2/yo, dir3/yo, dirN/yo -test_expect_success '9e-setup: N-to-1 whammo' ' +test_setup_9e () { test_create_repo 9e && ( cd 9e && @@ -2696,9 +2730,10 @@ test_expect_success '9e-setup: N-to-1 whammo' ' test_tick && git commit -m "B" ) -' +} -test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' ' +test_expect_success C_LOCALE_OUTPUT '9e: N-to-1 whammo' ' + test_setup_9e && ( cd 9e && @@ -2745,7 +2780,7 @@ test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' ' # Commit B: goal/{a,b}/$more_files, goal/c # Expected: priority/{a,b}/$more_files, priority/c -test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' ' +test_setup_9f () { test_create_repo 9f && ( cd 9f && @@ -2774,9 +2809,10 @@ test_expect_success '9f-setup: Renamed directory that only contained immediate s test_tick && git commit -m "B" ) -' +} -test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' ' +test_expect_success '9f: Renamed directory that only contained immediate subdirs' ' + test_setup_9f && ( cd 9f && @@ -2809,7 +2845,7 @@ test_expect_success '9f-check: Renamed directory that only contained immediate s # Commit B: goal/{a,b}/$more_files, goal/c # Expected: priority/{alpha,bravo}/$more_files, priority/c -test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' +test_setup_9g () { test_create_repo 9g && ( cd 9g && @@ -2841,9 +2877,9 @@ test_expect_success '9g-setup: Renamed directory that only contained immediate s test_tick && git commit -m "B" ) -' +} -test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' +test_expect_failure '9g: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' ( cd 9g && @@ -2877,7 +2913,7 @@ test_expect_failure '9g-check: Renamed directory that only contained immediate s # Expected: y/{b,c}, x/d_2 # NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with # a rename/rename(1to2) conflict (z/d -> y/d vs. x/d) -test_expect_success '9h-setup: Avoid dir rename on merely modified path' ' +test_setup_9h () { test_create_repo 9h && ( cd 9h && @@ -2910,9 +2946,10 @@ test_expect_success '9h-setup: Avoid dir rename on merely modified path' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '9h-check: Avoid dir rename on merely modified path' ' +test_expect_success '9h: Avoid dir rename on merely modified path' ' + test_setup_9h && ( cd 9h && @@ -2957,7 +2994,7 @@ test_expect_success '9h-check: Avoid dir rename on merely modified path' ' # Expected: Aborted Merge + # ERROR_MSG(untracked working tree files would be overwritten by merge) -test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' ' +test_setup_10a () { test_create_repo 10a && ( cd 10a && @@ -2983,9 +3020,10 @@ test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '10a-check: Overwrite untracked with normal rename/delete' ' +test_expect_success '10a: Overwrite untracked with normal rename/delete' ' + test_setup_10a && ( cd 10a && @@ -3021,7 +3059,7 @@ test_expect_success '10a-check: Overwrite untracked with normal rename/delete' ' # z/c_1 -> z/d_1 rename recorded at stage 3 for y/d + # ERROR_MSG(refusing to lose untracked file at 'y/d') -test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' ' +test_setup_10b () { test_create_repo 10b && ( cd 10b && @@ -3050,9 +3088,10 @@ test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '10b-check: Overwrite untracked with dir rename + delete' ' +test_expect_success '10b: Overwrite untracked with dir rename + delete' ' + test_setup_10b && ( cd 10b && @@ -3098,10 +3137,10 @@ test_expect_success '10b-check: Overwrite untracked with dir rename + delete' ' # y/c~B^0 + # ERROR_MSG(Refusing to lose untracked file at y/c) -test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' ' - test_create_repo 10c && +test_setup_10c () { + test_create_repo 10c_$1 && ( - cd 10c && + cd 10c_$1 && mkdir z x && echo a >z/a && @@ -3128,11 +3167,12 @@ test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2) test_tick && git commit -m "B" ) -' +} -test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' ' +test_expect_success '10c1: Overwrite untracked with dir rename/rename(1to2)' ' + test_setup_10c 1 && ( - cd 10c && + cd 10c_1 && git checkout A^0 && echo important >y/c && @@ -3163,9 +3203,10 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2) ) ' -test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' ' +test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), other direction' ' + test_setup_10c 2 && ( - cd 10c && + cd 10c_2 && git reset --hard && git clean -fdqx && @@ -3208,7 +3249,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2) # CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham # ERROR_MSG(Refusing to lose untracked file at y/wham) -test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' ' +test_setup_10d () { test_create_repo 10d && ( cd 10d && @@ -3240,9 +3281,10 @@ test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' +test_expect_success '10d: Delete untracked with dir rename/rename(2to1)' ' + test_setup_10d && ( cd 10d && @@ -3290,7 +3332,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' # Commit B: z/{a,b,c} # Expected: y/{a,b,c} + untracked z/c -test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' ' +test_setup_10e () { test_create_repo 10e && ( cd 10e && @@ -3317,9 +3359,9 @@ test_expect_success '10e-setup: Does git complain about untracked file that is n test_tick && git commit -m "B" ) -' +} -test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' ' +test_expect_failure '10e: Does git complain about untracked file that is not really in the way?' ' ( cd 10e && @@ -3371,7 +3413,7 @@ test_expect_failure '10e-check: Does git complain about untracked file that is n # z/c~HEAD with contents of B:z/b_v2, # z/c with uncommitted mods on top of A:z/c_v1 -test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' ' +test_setup_11a () { test_create_repo 11a && ( cd 11a && @@ -3398,9 +3440,10 @@ test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' test_tick && git commit -m "B" ) -' +} -test_expect_success '11a-check: Avoid losing dirty contents with simple rename' ' +test_expect_success '11a: Avoid losing dirty contents with simple rename' ' + test_setup_11a && ( cd 11a && @@ -3441,7 +3484,7 @@ test_expect_success '11a-check: Avoid losing dirty contents with simple rename' # ERROR_MSG(Refusing to lose dirty file at z/c) -test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' ' +test_setup_11b () { test_create_repo 11b && ( cd 11b && @@ -3470,9 +3513,10 @@ test_expect_success '11b-setup: Avoid losing dirty file involved in directory re test_tick && git commit -m "B" ) -' +} -test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' ' +test_expect_success '11b: Avoid losing dirty file involved in directory rename' ' + test_setup_11b && ( cd 11b && @@ -3515,7 +3559,7 @@ test_expect_success '11b-check: Avoid losing dirty file involved in directory re # Expected: Abort_msg("following files would be overwritten by merge") + # y/c left untouched (still has uncommitted mods) -test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' ' +test_setup_11c () { test_create_repo 11c && ( cd 11c && @@ -3545,9 +3589,10 @@ test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conf test_tick && git commit -m "B" ) -' +} -test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' ' +test_expect_success '11c: Avoid losing not-uptodate with rename + D/F conflict' ' + test_setup_11c && ( cd 11c && @@ -3581,7 +3626,7 @@ test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conf # Warning_Msg("Refusing to lose dirty file at z/c) + # y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods -test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' ' +test_setup_11d () { test_create_repo 11d && ( cd 11d && @@ -3612,9 +3657,10 @@ test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conf test_tick && git commit -m "B" ) -' +} -test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' ' +test_expect_success '11d: Avoid losing not-uptodate with rename + D/F conflict' ' + test_setup_11d && ( cd 11d && @@ -3659,7 +3705,7 @@ test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conf # y/c~HEAD has A:y/c_2 contents # y/c has dirty file from before merge -test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' +test_setup_11e () { test_create_repo 11e && ( cd 11e && @@ -3691,9 +3737,10 @@ test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rena test_tick && git commit -m "B" ) -' +} -test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' +test_expect_success '11e: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + test_setup_11e && ( cd 11e && @@ -3744,7 +3791,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena # CONFLICT(rename/rename) x/c vs x/d -> y/wham # ERROR_MSG(Refusing to lose dirty file at y/wham) -test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' +test_setup_11f () { test_create_repo 11f && ( cd 11f && @@ -3773,9 +3820,10 @@ test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rena test_tick && git commit -m "B" ) -' +} -test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' +test_expect_success '11f: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + test_setup_11f && ( cd 11f && @@ -3832,7 +3880,7 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena # Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6} # Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}} -test_expect_success '12a-setup: Moving one directory hierarchy into another' ' +test_setup_12a () { test_create_repo 12a && ( cd 12a && @@ -3862,9 +3910,10 @@ test_expect_success '12a-setup: Moving one directory hierarchy into another' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '12a-check: Moving one directory hierarchy into another' ' +test_expect_success '12a: Moving one directory hierarchy into another' ' + test_setup_12a && ( cd 12a && @@ -3910,7 +3959,7 @@ test_expect_success '12a-check: Moving one directory hierarchy into another' ' # To which, I can do no more than shrug my shoulders and say that # even simple rules give weird results when given weird inputs. -test_expect_success '12b-setup: Moving two directory hierarchies into each other' ' +test_setup_12b () { test_create_repo 12b && ( cd 12b && @@ -3938,9 +3987,10 @@ test_expect_success '12b-setup: Moving two directory hierarchies into each other test_tick && git commit -m "B" ) -' +} -test_expect_success '12b-check: Moving two directory hierarchies into each other' ' +test_expect_success '12b: Moving two directory hierarchies into each other' ' + test_setup_12b && ( cd 12b && @@ -3976,7 +4026,7 @@ test_expect_success '12b-check: Moving two directory hierarchies into each other # NOTE: This is *exactly* like 12c, except that every path is modified on # each side of the merge. -test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' ' +test_setup_12c () { test_create_repo 12c && ( cd 12c && @@ -4008,9 +4058,10 @@ test_expect_success '12c-setup: Moving one directory hierarchy into another w/ c test_tick && git commit -m "B" ) -' +} -test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' ' +test_expect_success '12c: Moving one directory hierarchy into another w/ content merge' ' + test_setup_12c && ( cd 12c && @@ -4051,6 +4102,122 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c ) ' +# Testcase 12d, Rename/merge of subdirectory into the root +# Commit O: a/b/subdir/foo +# Commit A: subdir/foo +# Commit B: a/b/subdir/foo, a/b/bar +# Expected: subdir/foo, bar + +test_setup_12d () { + test_create_repo 12d && + ( + cd 12d && + + mkdir -p a/b/subdir && + test_commit a/b/subdir/foo && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir subdir && + git mv a/b/subdir/foo.t subdir/foo.t && + test_tick && + git commit -m "A" && + + git checkout B && + test_commit a/b/bar + ) +} + +test_expect_success '12d: Rename/merge subdir into the root, variant 1' ' + test_setup_12d && + ( + cd 12d && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + + git rev-parse >actual \ + HEAD:subdir/foo.t HEAD:bar.t && + git rev-parse >expect \ + O:a/b/subdir/foo.t B:a/b/bar.t && + test_cmp expect actual && + + git hash-object bar.t >actual && + git rev-parse B:a/b/bar.t >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:a/b/subdir/foo.t && + test_must_fail git rev-parse HEAD:a/b/bar.t && + test_path_is_missing a/ && + test_path_is_file bar.t + ) +' + +# Testcase 12e, Rename/merge of subdirectory into the root +# Commit O: a/b/foo +# Commit A: foo +# Commit B: a/b/foo, a/b/bar +# Expected: foo, bar + +test_setup_12e () { + test_create_repo 12e && + ( + cd 12e && + + mkdir -p a/b && + test_commit a/b/foo && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir subdir && + git mv a/b/foo.t foo.t && + test_tick && + git commit -m "A" && + + git checkout B && + test_commit a/b/bar + ) +} + +test_expect_success '12e: Rename/merge subdir into the root, variant 2' ' + test_setup_12e && + ( + cd 12e && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + + git rev-parse >actual \ + HEAD:foo.t HEAD:bar.t && + git rev-parse >expect \ + O:a/b/foo.t B:a/b/bar.t && + test_cmp expect actual && + + git hash-object bar.t >actual && + git rev-parse B:a/b/bar.t >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:a/b/foo.t && + test_must_fail git rev-parse HEAD:a/b/bar.t && + test_path_is_missing a/ && + test_path_is_file bar.t + ) +' + ########################################################################### # SECTION 13: Checking informational and conflict messages # @@ -4068,10 +4235,10 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c # Commit B: z/{b,c,d,e/f} # Expected: y/{b,c,d,e/f}, with notices/conflicts for both y/d and y/e/f -test_expect_success '13a-setup: messages for newly added files' ' - test_create_repo 13a && +test_setup_13a () { + test_create_repo 13a_$1 && ( - cd 13a && + cd 13a_$1 && mkdir z && echo b >z/b && @@ -4097,11 +4264,12 @@ test_expect_success '13a-setup: messages for newly added files' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '13a-check(conflict): messages for newly added files' ' +test_expect_success '13a(conflict): messages for newly added files' ' + test_setup_13a conflict && ( - cd 13a && + cd 13a_conflict && git checkout A^0 && @@ -4121,9 +4289,10 @@ test_expect_success '13a-check(conflict): messages for newly added files' ' ) ' -test_expect_success '13a-check(info): messages for newly added files' ' +test_expect_success '13a(info): messages for newly added files' ' + test_setup_13a info && ( - cd 13a && + cd 13a_info && git reset --hard && git checkout A^0 && @@ -4153,10 +4322,10 @@ test_expect_success '13a-check(info): messages for newly added files' ' # Expected: y/{b,c,d_merged}, with two conflict messages for y/d, # one about content, and one about file location -test_expect_success '13b-setup: messages for transitive rename with conflicted content' ' - test_create_repo 13b && +test_setup_13b () { + test_create_repo 13b_$1 && ( - cd 13b && + cd 13b_$1 && mkdir x && mkdir z && @@ -4185,11 +4354,12 @@ test_expect_success '13b-setup: messages for transitive rename with conflicted c test_tick && git commit -m "B" ) -' +} -test_expect_success '13b-check(conflict): messages for transitive rename with conflicted content' ' +test_expect_success '13b(conflict): messages for transitive rename with conflicted content' ' + test_setup_13b conflict && ( - cd 13b && + cd 13b_conflict && git checkout A^0 && @@ -4207,9 +4377,10 @@ test_expect_success '13b-check(conflict): messages for transitive rename with co ) ' -test_expect_success '13b-check(info): messages for transitive rename with conflicted content' ' +test_expect_success '13b(info): messages for transitive rename with conflicted content' ' + test_setup_13b info && ( - cd 13b && + cd 13b_info && git reset --hard && git checkout A^0 && @@ -4238,10 +4409,10 @@ test_expect_success '13b-check(info): messages for transitive rename with confli # d and B had full knowledge, but that's a slippery slope as # shown in testcase 13d. -test_expect_success '13c-setup: messages for rename/rename(1to1) via transitive rename' ' - test_create_repo 13c && +test_setup_13c () { + test_create_repo 13c_$1 && ( - cd 13c && + cd 13c_$1 && mkdir x && mkdir z && @@ -4269,11 +4440,12 @@ test_expect_success '13c-setup: messages for rename/rename(1to1) via transitive test_tick && git commit -m "B" ) -' +} -test_expect_success '13c-check(conflict): messages for rename/rename(1to1) via transitive rename' ' +test_expect_success '13c(conflict): messages for rename/rename(1to1) via transitive rename' ' + test_setup_13c conflict && ( - cd 13c && + cd 13c_conflict && git checkout A^0 && @@ -4290,9 +4462,10 @@ test_expect_success '13c-check(conflict): messages for rename/rename(1to1) via t ) ' -test_expect_success '13c-check(info): messages for rename/rename(1to1) via transitive rename' ' +test_expect_success '13c(info): messages for rename/rename(1to1) via transitive rename' ' + test_setup_13c info && ( - cd 13c && + cd 13c_info && git reset --hard && git checkout A^0 && @@ -4324,10 +4497,10 @@ test_expect_success '13c-check(info): messages for rename/rename(1to1) via trans # * B renames a/y to c/y, and A renames c/->d/ => a/y -> d/y # No conflict in where a/y ends up, so put it in d/y. -test_expect_success '13d-setup: messages for rename/rename(1to1) via dual transitive rename' ' - test_create_repo 13d && +test_setup_13d () { + test_create_repo 13d_$1 && ( - cd 13d && + cd 13d_$1 && mkdir a && mkdir b && @@ -4356,11 +4529,12 @@ test_expect_success '13d-setup: messages for rename/rename(1to1) via dual transi test_tick && git commit -m "B" ) -' +} -test_expect_success '13d-check(conflict): messages for rename/rename(1to1) via dual transitive rename' ' +test_expect_success '13d(conflict): messages for rename/rename(1to1) via dual transitive rename' ' + test_setup_13d conflict && ( - cd 13d && + cd 13d_conflict && git checkout A^0 && @@ -4380,9 +4554,10 @@ test_expect_success '13d-check(conflict): messages for rename/rename(1to1) via d ) ' -test_expect_success '13d-check(info): messages for rename/rename(1to1) via dual transitive rename' ' +test_expect_success '13d(info): messages for rename/rename(1to1) via dual transitive rename' ' + test_setup_13d info && ( - cd 13d && + cd 13d_info && git reset --hard && git checkout A^0 && @@ -4448,7 +4623,7 @@ test_expect_success '13d-check(info): messages for rename/rename(1to1) via dual # in the outer merge for this special kind of setup, but it at # least avoids hitting a BUG(). # -test_expect_success '13e-setup: directory rename detection in recursive case' ' +test_setup_13e () { test_create_repo 13e && ( cd 13e && @@ -4493,9 +4668,10 @@ test_expect_success '13e-setup: directory rename detection in recursive case' ' test_tick && git commit -m "D" ) -' +} -test_expect_success '13e-check: directory rename detection in recursive case' ' +test_expect_success '13e: directory rename detection in recursive case' ' + test_setup_13e && ( cd 13e && diff --git a/t/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh index 3a47623ed3..b7e4669832 100755 --- a/t/t6046-merge-skip-unneeded-updates.sh +++ b/t/t6046-merge-skip-unneeded-updates.sh @@ -36,10 +36,10 @@ test_description="merge cases" # Commit B: b_3 # Expected: b_2 -test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' ' - test_create_repo 1a && +test_setup_1a () { + test_create_repo 1a_$1 && ( - cd 1a && + cd 1a_$1 && test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && git add b && @@ -62,13 +62,12 @@ test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' ' - test_when_finished "git -C 1a reset --hard" && - test_when_finished "git -C 1a clean -fd" && +test_expect_success '1a-L: Modify(A)/Modify(B), change on B subset of A' ' + test_setup_1a L && ( - cd 1a && + cd 1a_L && git checkout A^0 && @@ -96,11 +95,10 @@ test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' ' ) ' -test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' ' - test_when_finished "git -C 1a reset --hard" && - test_when_finished "git -C 1a clean -fd" && +test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' ' + test_setup_1a R && ( - cd 1a && + cd 1a_R && git checkout B^0 && @@ -133,10 +131,10 @@ test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' ' # Commit B: c_1 # Expected: c_2 -test_expect_success '2a-setup: Modify(A)/rename(B)' ' - test_create_repo 2a && +test_setup_2a () { + test_create_repo 2a_$1 && ( - cd 2a && + cd 2a_$1 && test_seq 1 10 >b && git add b && @@ -158,13 +156,12 @@ test_expect_success '2a-setup: Modify(A)/rename(B)' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '2a-check-L: Modify/rename, merge into modify side' ' - test_when_finished "git -C 2a reset --hard" && - test_when_finished "git -C 2a clean -fd" && +test_expect_success '2a-L: Modify/rename, merge into modify side' ' + test_setup_2a L && ( - cd 2a && + cd 2a_L && git checkout A^0 && @@ -189,11 +186,10 @@ test_expect_success '2a-check-L: Modify/rename, merge into modify side' ' ) ' -test_expect_success '2a-check-R: Modify/rename, merge into rename side' ' - test_when_finished "git -C 2a reset --hard" && - test_when_finished "git -C 2a clean -fd" && +test_expect_success '2a-R: Modify/rename, merge into rename side' ' + test_setup_2a R && ( - cd 2a && + cd 2a_R && git checkout B^0 && @@ -224,10 +220,10 @@ test_expect_success '2a-check-R: Modify/rename, merge into rename side' ' # Commit B: b_3 # Expected: c_2 -test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' ' - test_create_repo 2b && +test_setup_2b () { + test_create_repo 2b_$1 && ( - cd 2b && + cd 2b_$1 && test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && git add b && @@ -251,13 +247,12 @@ test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' - test_when_finished "git -C 2b reset --hard" && - test_when_finished "git -C 2b clean -fd" && +test_expect_success '2b-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_setup_2b L && ( - cd 2b && + cd 2b_L && git checkout A^0 && @@ -288,11 +283,10 @@ test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' ) ' -test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' - test_when_finished "git -C 2b reset --hard" && - test_when_finished "git -C 2b clean -fd" && +test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_setup_2b R && ( - cd 2b && + cd 2b_R && git checkout B^0 && @@ -332,7 +326,7 @@ test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' # skip the update, then we're in trouble. This test verifies we do # not make that particular mistake. -test_expect_success '2c-setup: Modify b & add c VS rename b->c' ' +test_setup_2c () { test_create_repo 2c && ( cd 2c && @@ -358,9 +352,10 @@ test_expect_success '2c-setup: Modify b & add c VS rename b->c' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '2c-check: Modify b & add c VS rename b->c' ' +test_expect_success '2c: Modify b & add c VS rename b->c' ' + test_setup_2c && ( cd 2c && @@ -428,10 +423,10 @@ test_expect_success '2c-check: Modify b & add c VS rename b->c' ' # Commit B: bq_1, bar/whatever # Expected: bar/{bq_2, whatever} -test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_create_repo 3a && +test_setup_3a () { + test_create_repo 3a_$1 && ( - cd 3a && + cd 3a_$1 && mkdir foo && test_seq 1 10 >bq && @@ -456,13 +451,12 @@ test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_when_finished "git -C 3a reset --hard" && - test_when_finished "git -C 3a clean -fd" && +test_expect_success '3a-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3a L && ( - cd 3a && + cd 3a_L && git checkout A^0 && @@ -487,11 +481,10 @@ test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' ) ' -test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_when_finished "git -C 3a reset --hard" && - test_when_finished "git -C 3a clean -fd" && +test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3a R && ( - cd 3a && + cd 3a_R && git checkout B^0 && @@ -522,10 +515,10 @@ test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' # Commit B: bq_2, bar/whatever # Expected: bar/{bq_2, whatever} -test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_create_repo 3b && +test_setup_3b () { + test_create_repo 3b_$1 && ( - cd 3b && + cd 3b_$1 && mkdir foo && test_seq 1 10 >bq && @@ -550,13 +543,12 @@ test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' test_tick && git commit -m "B" ) -' +} -test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_when_finished "git -C 3b reset --hard" && - test_when_finished "git -C 3b clean -fd" && +test_expect_success '3b-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3b L && ( - cd 3b && + cd 3b_L && git checkout A^0 && @@ -581,11 +573,10 @@ test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' ) ' -test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_when_finished "git -C 3b reset --hard" && - test_when_finished "git -C 3b clean -fd" && +test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3b R && ( - cd 3b && + cd 3b_R && git checkout B^0 && @@ -621,7 +612,7 @@ test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' # Working copy: b_4 # Expected: b_2 for merge, b_4 in working copy -test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' ' +test_setup_4a () { test_create_repo 4a && ( cd 4a && @@ -647,7 +638,7 @@ test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods test_tick && git commit -m "B" ) -' +} # NOTE: For as long as we continue using unpack_trees() without index_only # set to true, it will error out on a case like this claiming the the locally @@ -655,9 +646,8 @@ test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods # correct requires doing the merge in-memory first, then realizing that no # updates to the file are necessary, and thus that we can just leave the path # alone. -test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' ' - test_when_finished "git -C 4a reset --hard" && - test_when_finished "git -C 4a clean -fd" && +test_expect_failure '4a: Change on A, change on B subset of A, dirty mods present' ' + test_setup_4a && ( cd 4a && @@ -695,7 +685,7 @@ test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods # Working copy: c_4 # Expected: c_2 -test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' +test_setup_4b () { test_create_repo 4b && ( cd 4b && @@ -722,11 +712,10 @@ test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, di test_tick && git commit -m "B" ) -' +} -test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' - test_when_finished "git -C 4b reset --hard" && - test_when_finished "git -C 4b clean -fd" && +test_expect_success '4b: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_setup_4b && ( cd 4b && diff --git a/t/t6102-rev-list-unexpected-objects.sh b/t/t6102-rev-list-unexpected-objects.sh index 28611c978e..52cde097dd 100755 --- a/t/t6102-rev-list-unexpected-objects.sh +++ b/t/t6102-rev-list-unexpected-objects.sh @@ -52,7 +52,7 @@ test_expect_success 'traverse unexpected non-commit parent (lone)' ' ' test_expect_success 'traverse unexpected non-commit parent (seen)' ' - test_must_fail git rev-list --objects $commit $broken_commit \ + test_must_fail git rev-list --objects $blob $broken_commit \ >output 2>&1 && test_i18ngrep "not a commit" output ' diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 9d1abe50ef..7476781979 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -134,6 +134,21 @@ test_expect_success 'git-clean, dirty case' ' test_i18ncmp expected result ' +test_expect_success '--ignore-skip-worktree-entries leaves worktree alone' ' + test_commit keep-me && + git update-index --skip-worktree keep-me.t && + rm keep-me.t && + + : ignoring the worktree && + git update-index --remove --ignore-skip-worktree-entries keep-me.t && + git diff-index --cached --exit-code HEAD && + + : not ignoring the worktree, a deletion is staged && + git update-index --remove keep-me.t && + test_must_fail git diff-index --cached --exit-code HEAD \ + --diff-filter=D -- keep-me.t +' + #TODO test_expect_failure 'git-apply adds file' false #TODO test_expect_failure 'git-apply updates file' false #TODO test_expect_failure 'git-apply removes file' false diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman index 5514edcf68..d8e7a1e5ba 100755 --- a/t/t7519/fsmonitor-watchman +++ b/t/t7519/fsmonitor-watchman @@ -23,7 +23,8 @@ my ($version, $time) = @ARGV; if ($version == 1) { # convert nanoseconds to seconds - $time = int $time / 1000000000; + # subtract one second to make sure watchman will return all changes + $time = int ($time / 1000000000) - 1; } else { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; @@ -54,18 +55,12 @@ sub launch_watchman { # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - # - # The category of transient files that we want to ignore will have a - # creation clock (cclock) newer than $time_t value and will also not - # currently exist. + # output to file names only. my $query = <<" END"; ["query", "$git_work_tree", { "since": $time, - "fields": ["name"], - "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + "fields": ["name"] }] END diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 54f8ce18cb..e90ac565e1 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1548,7 +1548,10 @@ test_expect_success 'complete tree filename with metacharacters' ' ' test_expect_success PERL 'send-email' ' - test_completion "git send-email --cov" "--cover-letter " && + test_completion "git send-email --cov" <<-\EOF && + --cover-from-description=Z + --cover-letter Z + EOF test_completion "git send-email ma" "master " ' diff --git a/t/test-lib.sh b/t/test-lib.sh index e06fa02a0e..46c4440843 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -404,9 +404,13 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' unset XDG_CACHE_HOME unset XDG_CONFIG_HOME unset GITPERLLIB -GIT_AUTHOR_EMAIL=author@example.com +TEST_AUTHOR_LOCALNAME=author +TEST_AUTHOR_DOMAIN=example.com +GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN} GIT_AUTHOR_NAME='A U Thor' -GIT_COMMITTER_EMAIL=committer@example.com +TEST_COMMITTER_LOCALNAME=committer +TEST_COMMITTER_DOMAIN=example.com +GIT_COMMITTER_EMAIL=${TEST_COMMITTER_LOCALNAME}@${TEST_COMMITTER_DOMAIN} GIT_COMMITTER_NAME='C O Mitter' GIT_MERGE_VERBOSITY=5 GIT_MERGE_AUTOEDIT=no @@ -141,7 +141,16 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u if (item->object.parsed) return 0; - item->object.parsed = 1; + + if (item->tag) { + /* + * Presumably left over from a previous failed parse; + * clear it out in preparation for re-parsing (we'll probably + * hit the same error, which lets us tell our current caller + * about the problem). + */ + FREE_AND_NULL(item->tag); + } if (size < the_hash_algo->hexsz + 24) return -1; @@ -167,10 +176,15 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u } else if (!strcmp(type, tag_type)) { item->tagged = (struct object *)lookup_tag(r, &oid); } else { - error("Unknown type %s", type); - item->tagged = NULL; + return error("unknown tag type '%s' in %s", + type, oid_to_hex(&item->object.oid)); } + if (!item->tagged) + return error("bad tag pointer to %s in %s", + oid_to_hex(&oid), + oid_to_hex(&item->object.oid)); + if (bufptr + 4 < tail && starts_with(bufptr, "tag ")) ; /* good */ else @@ -187,6 +201,7 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u else item->date = 0; + item->object.parsed = 1; return 0; } diff --git a/templates/hooks--fsmonitor-watchman.sample b/templates/hooks--fsmonitor-watchman.sample index e673bb3980..ef94fa2938 100755 --- a/templates/hooks--fsmonitor-watchman.sample +++ b/templates/hooks--fsmonitor-watchman.sample @@ -22,7 +22,8 @@ my ($version, $time) = @ARGV; if ($version == 1) { # convert nanoseconds to seconds - $time = int $time / 1000000000; + # subtract one second to make sure watchman will return all changes + $time = int ($time / 1000000000) - 1; } else { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; @@ -53,18 +54,12 @@ sub launch_watchman { # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - # - # The category of transient files that we want to ignore will have a - # creation clock (cclock) newer than $time_t value and will also not - # currently exist. + # output to file names only. my $query = <<" END"; ["query", "$git_work_tree", { "since": $time, - "fields": ["name"], - "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + "fields": ["name"] }] END @@ -9,14 +9,26 @@ void vreportf(const char *prefix, const char *err, va_list params) { char msg[4096]; - char *p; + char *p, *pend = msg + sizeof(msg); + size_t prefix_len = strlen(prefix); - vsnprintf(msg, sizeof(msg), err, params); - for (p = msg; *p; p++) { + if (sizeof(msg) <= prefix_len) { + fprintf(stderr, "BUG!!! too long a prefix '%s'\n", prefix); + abort(); + } + memcpy(msg, prefix, prefix_len); + p = msg + prefix_len; + if (vsnprintf(p, pend - p, err, params) < 0) + *p = '\0'; /* vsnprintf() failed, clip at prefix */ + + for (; p != pend - 1 && *p; p++) { if (iscntrl(*p) && *p != '\t' && *p != '\n') *p = '?'; } - fprintf(stderr, "%s%s\n", prefix, msg); + + *(p++) = '\n'; /* we no longer need a NUL */ + fflush(stderr); + write_in_full(2, msg, p - msg); } static NORETURN void usage_builtin(const char *err, va_list params) |