diff options
147 files changed, 2565 insertions, 636 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile index 9c67c3a1c5..a6ba5bd460 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -191,14 +191,6 @@ MAN_BASE_URL = file://$(htmldir)/ endif XMLTO_EXTRA += -m manpage-base-url.xsl -# If your target system uses GNU groff, it may try to render -# apostrophes as a "pretty" apostrophe using unicode. This breaks -# cut&paste, so you should set GNU_ROFF to force them to be ASCII -# apostrophes. Unfortunately does not work with non-GNU roff. -ifdef GNU_ROFF -XMLTO_EXTRA += -m manpage-quote-apos.xsl -endif - ifdef USE_ASCIIDOCTOR ASCIIDOC = asciidoctor ASCIIDOC_CONF = diff --git a/Documentation/RelNotes/2.41.0.txt b/Documentation/RelNotes/2.41.0.txt index 71c9342aa6..1e9b6a66e8 100644 --- a/Documentation/RelNotes/2.41.0.txt +++ b/Documentation/RelNotes/2.41.0.txt @@ -11,6 +11,40 @@ UI, Workflows & Features checking object connectivity, most useful when there are many unrelated histories in a single repository. + * "git push" has been taught to allow deletion of refs with one-level + names to help repairing a repository who acquired such a ref by + mistake. In general, we don't encourage use of such a ref, and + creation or update to such a ref is rejected as before. + + * Allow "git bisect reset" to check out the original branch when the + branch is already checked out in a different worktree linked to the + same repository. + + * A few subcommands have been taught to stop users from working on a + branch that is being used in another worktree linked to the same + repository. + + * "git format-patch" learned to write a log-message only output file + for empty commits. + + * "git format-patch" honors the src/dst prefixes set to nonstandard + values with configuration variables like "diff.noprefix", causing + receiving end of the patch that expects the standard -p1 format to + break. "format-patch" has been taught to ignore end-user configuration + and always use the standard prefixes. + + This is a backward compatibility breaking change. + + * Lift the limitation that colored prompts can only be used with + PROMPT_COMMAND mode. + + * "git blame --contents=<file> <rev> -- <path>" used to be forbidden, + but now it finds the origins of lines starting at <file> contents + through the history that leads to <rev>. + + * "git pack-redundant" gave a warning when run, as the command has + outlived its usefulness long ago and is nominated for future + removal. Now we escalate to give an error. Performance, Internal Implementation, Development Support etc. @@ -25,6 +59,13 @@ Performance, Internal Implementation, Development Support etc. configuration variables, make the subsystem lazily initialize itself. + * Remove workaround for ancient versions of DocBook to make it work + correctly with groff, which has not been necessary since docbook + 1.76 from 2010. + + * Code clean-up to include and/or uninclude parse-options.h file as + needed. + Fixes since v2.40 ----------------- @@ -33,4 +74,96 @@ Fixes since v2.40 just like "git gc" honors them as anchoring points. (merge 8d3e7eac52 jk/fsck-indices-in-worktrees later to maint). + * Fix a segfaulting loop. The function and its caller may need + further clean-up. + (merge c5773dc078 ew/commit-reach-clean-up-flags-fix later to maint). + + * "git restore" supports options like "--ours" that are only + meaningful during a conflicted merge, but these options are only + meaningful when updating the working tree files. These options are + marked to be incompatible when both "--staged" and "--worktree" are + in effect. + (merge ee8a88826a ak/restore-both-incompatible-with-conflicts later to maint). + + * Simplify UI to control progress meter given by "git bundle" command. + (merge 8b95521edb jk/bundle-progress later to maint). + + * "git bundle" learned that "-" is a common way to say that the input + comes from the standard input and/or the output goes to the + standard output. It used to work only for output and only from the + root level of the working tree. + (merge 0bbe10313e jk/bundle-use-dash-for-stdfiles later to maint). + + * Once we start running, we assumed that the list of alternate object + databases would never change. Hook into the machinery used to + update the list of packfiles during runtime to update this list as + well. + (merge e2d003dbed ds/reprepare-alternates-when-repreparing-packfiles later to maint). + + * The code to parse "git rebase -X<opt>" was not prepared to see an + unparsable option string, which has been corrected. + (merge 15a4cc912e ab/fix-strategy-opts-parsing later to maint). + + * "git add -p" while the index is unmerged sometimes failed to parse + the diff output it internally produces and died, which has been + corrected. + (merge 28d1122f9c jk/add-p-unmerged-fix later to maint). + + * Fix for a "ls-files --format="%(path)" that produced nonsense + output, which was a bug in 2.38. + (merge cfb62dd006 aj/ls-files-format-fix later to maint). + + * "git receive-pack" that responds to "git push" requests failed to + clean a stale lockfile when killed in the middle, which has been + corrected. + (merge c55c30669c ps/receive-pack-unlock-before-die later to maint). + + * "git rev-parse --quiet foo@{u}", or anything that asks @{u} to be + parsed with GET_OID_QUIETLY option, did not quietly fail, which has + been corrected. + (merge dfbfdc521d fc/oid-quietly-parse-upstream later to maint). + + * Transports that do not support protocol v2 did not correctly fall + back to protocol v0 under certain conditions, which has been + corrected. + (merge eaa0fd6584 jk/fix-proto-downgrade-to-v0 later to maint). + + * time(2) on glib 2.31+, especially on Linux, goes out of sync with + higher resolution timers used for gettimeofday(2) and by the + filesystem. Replace all calls to it with a git_time() wrapper and + (merge 370ddcbc89 pe/time-use-gettimeofday later to maint). + + * Code clean-up to use designated initializers in parse-options API. + (merge 353e6d4554 sg/parse-options-h-initializers later to maint). + + * A recent-ish change to allow unicode character classes to be used + with "grep -P" triggered a JIT bug in older pcre2 libraries. + The problematic change in Git built with these older libraries has + been disabled to work around the bug. + (merge 14b9a04479 mk/workaround-pcre-jit-ucp-bug later to maint). + + * The wildmatch library code unlearns exponential behaviour it + acquired some time ago since it was borrowed from rsync. + (merge 3dc0b7f0dc pw/wildmatch-fixes later to maint). + + * The index files can become corrupt under certain conditions when + the split-index feature is in use, especially together with + fsmonitor, which have been corrected. + (merge 061dd722dc js/split-index-fixes later to maint). + + * Document what the pathname-looking strings in "rev-list --object" + output are for and what they mean. + (merge 15364d2a3c jk/document-rev-list-object-name later to maint). + * Other code cleanup, docfix, build fix, etc. + (merge f7111175df as/doc-markup-fix later to maint). + (merge 90ff7c9898 fc/test-aggregation-clean-up later to maint). + (merge 9b0c7f308a jc/am-doc-refer-to-format-patch later to maint). + (merge b10cbdac4c bb/unicode-width-table-15 later to maint). + (merge 3457b50e8c ab/retire-scripted-add-p later to maint). + (merge d52fcf493b ds/p2000-fix-grep-sparse later to maint). + (merge ec063d2591 ss/hashmap-typofix later to maint). + (merge 1aaed69d11 rs/archive-mtime later to maint). + (merge 2da2cc9b28 ob/rollback-after-commit-lock-failure later to maint). + (merge 54dbd0933b ob/sequencer-save-head-simplify later to maint). + (merge a93cbe8d78 ar/test-cleanup-unused-file-creation later to maint). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 9a663535f4..95599bd6e5 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -64,11 +64,11 @@ include::line-range-format.txt[] manual page. --contents <file>:: - When <rev> is not specified, the command annotates the - changes starting backwards from the working tree copy. - This flag makes the command pretend as if the working - tree copy has the contents of the named file (specify - `-` to make the command read from the standard input). + Pretend the file being annotated has a commit with the + contents from the named file and a parent of <rev>, + defaulting to HEAD when no <rev> is specified. You may + specify '-' to make the command read from the standard + input for the file contents. --date <format>:: Specifies the format used to output dates. If --date is not diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index a00d0100a8..c96b5b2e5d 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -136,4 +136,6 @@ advice.*:: Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1] is asked to update index entries outside the current sparse checkout. + diverging:: + Advice shown when a fast-forward is not possible. -- diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt index 73678d88a1..8cf6f00d93 100644 --- a/Documentation/config/format.txt +++ b/Documentation/config/format.txt @@ -144,3 +144,10 @@ will only show notes from `refs/notes/bar`. format.mboxrd:: A boolean value which enables the robust "mboxrd" format when `--stdout` is in use to escape "^>+From " lines. + +format.noprefix:: + If set, do not show any source or destination prefix in patches. + This is equivalent to the `diff.noprefix` option used by `git + diff` (but which is not respected by `format-patch`). Note that + by setting this, the receiver of any patches you generate will + have to apply them using the `-p0` option. diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index f19bd0e040..afaf6dad99 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -67,3 +67,13 @@ rebase.rescheduleFailedExec:: rebase.forkPoint:: If set to false set `--no-fork-point` option by default. + +rebase.rebaseMerges:: + Whether and how to set the `--rebase-merges` option by default. Can + be `rebase-cousins`, `no-rebase-cousins`, or a boolean. Setting to + true or to `no-rebase-cousins` is equivalent to + `--rebase-merges=no-rebase-cousins`, setting to `rebase-cousins` is + equivalent to `--rebase-merges=rebase-cousins`, and setting to false is + equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the + command line, with or without an argument, overrides any + `rebase.rebaseMerges` configuration. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 7d73e976d9..08ab86189a 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -852,6 +852,11 @@ endif::git-format-patch[] --no-prefix:: Do not show any source or destination prefix. +--default-prefix:: + Use the default source and destination prefixes ("a/" and "b/"). + This is usually the default already, but may be used to override + config such as `diff.noprefix`. + --line-prefix=<prefix>:: Prepend an additional prefix to every line of output. diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 0c1dfb3c98..900be198b1 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -24,7 +24,9 @@ DESCRIPTION ----------- Splits mail messages in a mailbox into commit log message, authorship information and patches, and applies them to the -current branch. +current branch. You could think of it as a reverse operation +of linkgit:git-format-patch[1] run on a branch with a straight +history without merges. OPTIONS ------- @@ -273,7 +275,8 @@ include::config/am.txt[] SEE ALSO -------- -linkgit:git-apply[1]. +linkgit:git-apply[1], +linkgit:git-format-patch[1]. GIT --- diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 4400a17330..f69a871a96 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -12,7 +12,7 @@ SYNOPSIS [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--ignore-rev <rev>] [--ignore-revs-file <file>] [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>] - [<rev> | --contents <file> | --reverse <rev>..<rev>] [--] <file> + [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file> DESCRIPTION ----------- diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 18a022b4b4..3ab42a19ca 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -9,7 +9,7 @@ git-bundle - Move objects and refs by archive SYNOPSIS -------- [verse] -'git bundle' create [-q | --quiet | --progress | --all-progress] [--all-progress-implied] +'git bundle' create [-q | --quiet | --progress] [--version=<version>] <file> <git-rev-list-args> 'git bundle' verify [-q | --quiet] <file> 'git bundle' list-heads <file> [<refname>...] @@ -66,7 +66,7 @@ create [options] <file> <git-rev-list-args>:: Used to create a bundle named 'file'. This requires the '<git-rev-list-args>' arguments to define the bundle contents. 'options' contains the options specific to the 'git bundle create' - subcommand. + subcommand. If 'file' is `-`, the bundle is written to stdout. verify <file>:: Used to check that a bundle file is valid and will apply @@ -77,12 +77,13 @@ verify <file>:: Finally, information about additional capabilities, such as "object filter", is printed. See "Capabilities" in linkgit:gitformat-bundle[5] for more information. The exit code is zero for success, but will - be nonzero if the bundle file is invalid. + be nonzero if the bundle file is invalid. If 'file' is `-`, the + bundle is read from stdin. list-heads <file>:: Lists the references defined in the bundle. If followed by a list of references, only references matching those given are - printed out. + printed out. If 'file' is `-`, the bundle is read from stdin. unbundle <file>:: Passes the objects in the bundle to 'git index-pack' @@ -90,6 +91,7 @@ unbundle <file>:: defined references. If a list of references is given, only references matching those in the list are printed. This command is really plumbing, intended to be called only by 'git fetch'. + If 'file' is `-`, the bundle is read from stdin. <git-rev-list-args>:: A list of arguments, acceptable to 'git rev-parse' and @@ -115,22 +117,6 @@ unbundle <file>:: is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. ---all-progress:: - When --stdout is specified then progress report is - displayed during the object count and compression phases - but inhibited during the write-out phase. The reason is - that in some cases the output stream is directly linked - to another command which may wish to display progress - status of its own as it processes incoming pack data. - This flag is like --progress except that it forces progress - report for the write-out phase as well even if --stdout is - used. - ---all-progress-implied:: - This is used to imply --all-progress whenever progress display - is activated. Unlike --all-progress this flag doesn't actually - force any progress display by itself. - --version=<version>:: Specify the bundle version. Version 2 is the older format and can only be used with SHA-1 repositories; the newer version 3 contains capabilities that diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 6da899c629..0713e49b49 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -9,7 +9,8 @@ SYNOPSIS -------- [verse] 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] - [(--sort=<key>)...] [--format=<format>] [<pattern>...] + [(--sort=<key>)...] [--format=<format>] + [ --stdin | <pattern>... ] [--points-at=<object>] [--merged[=<object>]] [--no-merged[=<object>]] [--contains[=<object>]] [--no-contains[=<object>]] @@ -32,6 +33,10 @@ OPTIONS literally, in the latter case matching completely or from the beginning up to a slash. +--stdin:: + If `--stdin` is supplied, then the list of patterns is read from + standard input instead of from the argument list. + --count=<count>:: By default the command shows all refs that match `<pattern>`. This option makes it stop after showing @@ -217,6 +222,11 @@ worktreepath:: out, if it is checked out in any linked worktree. Empty string otherwise. +ahead-behind:<committish>:: + Two integers, separated by a space, demonstrating the number of + commits ahead and behind, respectively, when comparing the output + ref to the `<committish>` specified in the format. + In addition to the above, for commit and tag objects, the header field names (`tree`, `parent`, `object`, `type`, and `tag`) can be used to specify the value in the header field. diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt index 88ee942101..ffc4fbf7e8 100644 --- a/Documentation/git-merge-tree.txt +++ b/Documentation/git-merge-tree.txt @@ -108,7 +108,7 @@ This is an integer status followed by a NUL character. The integer status is: 0: merge had conflicts 1: merge was clean - <0: something prevented the merge from running (e.g. access to repository + <0: something prevented the merge from running (e.g. access to repository objects denied by filesystem) [[OIDTLT]] diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt index 99ef13839d..13c3eb5ec9 100644 --- a/Documentation/git-pack-redundant.txt +++ b/Documentation/git-pack-redundant.txt @@ -11,6 +11,20 @@ SYNOPSIS [verse] 'git pack-redundant' [--verbose] [--alt-odb] (--all | <pack-filename>...) +WARNING +------- +`git pack-redundant` has been deprecated and is scheduled for removal in +a future version of Git. Because it can only remove entire duplicate +packs and not individual duplicate objects, it is generally not a useful +tool for reducing repository size. You are better off using `git gc` to +do so, which will put objects into a new pack, removing duplicates. + +Running `pack-redundant` without the `--i-still-use-this` flag will fail +in this release. If you believe you have a use case for which +`pack-redundant` is better suited and oppose this removal, please +contact the Git mailing list at git@vger.kernel.org. More information +about the list is available at https://git-scm.com/community. + DESCRIPTION ----------- This program computes which packs in your repository diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 9a295bcee4..e7b39ad244 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -529,20 +529,25 @@ See also INCOMPATIBLE OPTIONS below. -r:: --rebase-merges[=(rebase-cousins|no-rebase-cousins)]:: +--no-rebase-merges:: By default, a rebase will simply drop merge commits from the todo list, and put the rebased commits into a single, linear branch. With `--rebase-merges`, the rebase will instead try to preserve the branching structure within the commits that are to be rebased, by recreating the merge commits. Any resolved merge conflicts or manual amendments in these merge commits will have to be - resolved/re-applied manually. + resolved/re-applied manually. `--no-rebase-merges` can be used to + countermand both the `rebase.rebaseMerges` config option and a previous + `--rebase-merges`. + -By default, or when `no-rebase-cousins` was specified, commits which do not -have `<upstream>` as direct ancestor will keep their original branch point, -i.e. commits that would be excluded by linkgit:git-log[1]'s -`--ancestry-path` option will keep their original ancestry by default. If -the `rebase-cousins` mode is turned on, such commits are instead rebased -onto `<upstream>` (or `<onto>`, if specified). +When rebasing merges, there are two modes: `rebase-cousins` and +`no-rebase-cousins`. If the mode is not specified, it defaults to +`no-rebase-cousins`. In `no-rebase-cousins` mode, commits which do not have +`<upstream>` as direct ancestor will keep their original branch point, i.e. +commits that would be excluded by linkgit:git-log[1]'s `--ancestry-path` +option will keep their original ancestry by default. In `rebase-cousins` mode, +such commits are instead rebased onto `<upstream>` (or `<onto>`, if +specified). + It is currently only possible to recreate the merge commits using the `ort` merge strategy; different merge strategies can be used only via diff --git a/Documentation/manpage-quote-apos.xsl b/Documentation/manpage-quote-apos.xsl deleted file mode 100644 index aeb8839f33..0000000000 --- a/Documentation/manpage-quote-apos.xsl +++ /dev/null @@ -1,16 +0,0 @@ -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" - version="1.0"> - -<!-- work around newer groff/man setups using a prettier apostrophe - that unfortunately does not quote anything when cut&pasting - examples to the shell --> -<xsl:template name="escape.apostrophe"> - <xsl:param name="content"/> - <xsl:call-template name="string.subst"> - <xsl:with-param name="string" select="$content"/> - <xsl:with-param name="target">'</xsl:with-param> - <xsl:with-param name="replacement">\(aq</xsl:with-param> - </xsl:call-template> -</xsl:template> - -</xsl:stylesheet> diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 90c73d6708..3000888a90 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -890,7 +890,7 @@ ifdef::git-rev-list[] Print the object IDs of any object referenced by the listed commits. `--objects foo ^bar` thus means ``send me all object IDs which I need to download if I have the commit - object _bar_ but not _foo_''. + object _bar_ but not _foo_''. See also `--object-names` below. --in-commit-order:: Print tree and blob ids in order of the commits. The tree @@ -920,7 +920,12 @@ ifdef::git-rev-list[] --object-names:: Only useful with `--objects`; print the names of the object IDs - that are found. This is the default behavior. + that are found. This is the default behavior. Note that the + "name" of each object is ambiguous, and mostly intended as a + hint for packing objects. In particular: no distinction is made between + the names of tags, trees, and blobs; path names may be modified + to remove newlines; and if an object would appear multiple times + with different names, only one name is shown. --no-object-names:: Only useful with `--objects`; does not print the names of the object @@ -207,10 +207,6 @@ include shared.mak # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks # field that counts the on-disk footprint in 512-byte blocks. # -# Define GNU_ROFF if your target system uses GNU groff. This forces -# apostrophes to be ASCII so that cut&pasting examples to the shell -# will work. -# # Define USE_ASCIIDOCTOR to use Asciidoctor instead of AsciiDoc to build the # documentation. # @@ -280,3 +280,10 @@ char *prefix_filename(const char *pfx, const char *arg) #endif return strbuf_detach(&path, NULL); } + +char *prefix_filename_except_for_dash(const char *pfx, const char *arg) +{ + if (!strcmp(arg, "-")) + return xstrdup(arg); + return prefix_filename(pfx, arg); +} diff --git a/add-patch.c b/add-patch.c index c6e451c136..e6c34b9c38 100644 --- a/add-patch.c +++ b/add-patch.c @@ -484,7 +484,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) if (!eol) eol = pend; - if (starts_with(p, "diff ")) { + if (starts_with(p, "diff ") || + starts_with(p, "* Unmerged path ")) { complete_file(marker, hunk); ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1, file_diff_alloc); @@ -46,6 +46,7 @@ static struct { [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 }, [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 }, [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead", 1 }, + [ADVICE_DIVERGING] = { "diverging", 1 }, [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 }, [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 }, [ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 }, @@ -219,6 +220,14 @@ void NORETURN die_conclude_merge(void) void NORETURN die_ff_impossible(void) { + advise_if_enabled(ADVICE_DIVERGING, + _("Diverging branches can't be fast-forwarded, you need to either:\n" + "\n" + "\tgit merge --no-ff\n" + "\n" + "or:\n" + "\n" + "\tgit rebase\n")); die(_("Not possible to fast-forward, aborting.")); } @@ -19,6 +19,7 @@ struct string_list; ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME, ADVICE_COMMIT_BEFORE_MERGE, ADVICE_DETACHED_HEAD, + ADVICE_DIVERGING, ADVICE_SUGGEST_DETACHING_HEAD, ADVICE_FETCH_SHOW_FORCED_UPDATES, ADVICE_GRAFT_FILE_DEPRECATED, @@ -177,12 +177,12 @@ static void set_commit_buffer_from_strbuf(struct repository *r, static struct commit *fake_working_tree_commit(struct repository *r, struct diff_options *opt, const char *path, - const char *contents_from) + const char *contents_from, + struct object_id *oid) { struct commit *commit; struct blame_origin *origin; struct commit_list **parent_tail, *parent; - struct object_id head_oid; struct strbuf buf = STRBUF_INIT; const char *ident; time_t now; @@ -198,10 +198,7 @@ static struct commit *fake_working_tree_commit(struct repository *r, commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) - die("no such ref: HEAD"); - - parent_tail = append_parent(r, parent_tail, &head_oid); + parent_tail = append_parent(r, parent_tail, oid); append_merge_parents(r, parent_tail); verify_working_tree_path(r, commit, path); @@ -2772,22 +2769,37 @@ void setup_scoreboard(struct blame_scoreboard *sb, sb->commits.compare = compare_commits_by_reverse_commit_date; } - if (sb->final && sb->contents_from) - die(_("cannot use --contents with final commit object name")); - if (sb->reverse && sb->revs->first_parent_only) sb->revs->children.name = NULL; - if (!sb->final) { + if (sb->contents_from || !sb->final) { + struct object_id head_oid, *parent_oid; + /* - * "--not A B -- path" without anything positive; - * do not default to HEAD, but use the working tree - * or "--contents". + * Build a fake commit at the top of the history, when + * (1) "git blame [^A] --path", i.e. with no positive end + * of the history range, in which case we build such + * a fake commit on top of the HEAD to blame in-tree + * modifications. + * (2) "git blame --contents=file [A] -- path", with or + * without positive end of the history range but with + * --contents, in which case we pretend that there is + * a fake commit on top of the positive end (defaulting to + * HEAD) that has the given contents in the path. */ + if (sb->final) { + parent_oid = &sb->final->object.oid; + } else { + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) + die("no such ref: HEAD"); + parent_oid = &head_oid; + } + setup_work_tree(); sb->final = fake_working_tree_commit(sb->repo, &sb->revs->diffopt, - sb->path, sb->contents_from); + sb->path, sb->contents_from, + parent_oid); add_pending_object(sb->revs, &(sb->final->object), ":"); } @@ -821,12 +821,16 @@ void remove_branch_state(struct repository *r, int verbose) void die_if_checked_out(const char *branch, int ignore_current_worktree) { struct worktree **worktrees = get_worktrees(); - const struct worktree *wt; - wt = find_shared_symref(worktrees, "HEAD", branch); - if (wt && (!ignore_current_worktree || !wt->is_current)) { - skip_prefix(branch, "refs/heads/", &branch); - die(_("'%s' is already checked out at '%s'"), branch, wt->path); + for (int i = 0; worktrees[i]; i++) { + if (worktrees[i]->is_current && ignore_current_worktree) + continue; + + if (is_shared_symref(worktrees[i], "HEAD", branch)) { + skip_prefix(branch, "refs/heads/", &branch); + die(_("'%s' is already checked out at '%s'"), + branch, worktrees[i]->path); + } } free_worktrees(worktrees); diff --git a/builtin/archive.c b/builtin/archive.c index f094390ee0..d0a583ea95 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -81,7 +81,7 @@ static int run_remote_archiver(int argc, const char **argv, int cmd_archive(int argc, const char **argv, const char *prefix) { const char *exec = "git-upload-archive"; - const char *output = NULL; + char *output = NULL; const char *remote = NULL; struct option local_opts[] = { OPT_FILENAME('o', "output", &output, @@ -106,5 +106,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix) setvbuf(stderr, NULL, _IOLBF, BUFSIZ); + UNLEAK(output); return write_archive(argc, argv, prefix, the_repository, output, 0); } diff --git a/builtin/bisect.c b/builtin/bisect.c index e8ee4a4dc8..c64c8d715a 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -245,7 +245,8 @@ static int bisect_reset(const char *commit) struct child_process cmd = CHILD_PROCESS_INIT; cmd.git_cmd = 1; - strvec_pushl(&cmd.args, "checkout", branch.buf, "--", NULL); + strvec_pushl(&cmd.args, "checkout", "--ignore-other-worktrees", + branch.buf, "--", NULL); if (run_command(&cmd)) { error(_("could not check out original" " HEAD '%s'. Try 'git bisect" diff --git a/builtin/branch.c b/builtin/branch.c index f63fd45edb..0554d7cebb 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -448,6 +448,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin if (verify_ref_format(format)) die(_("unable to parse format string")); + filter_ahead_behind(the_repository, format, &array); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { diff --git a/builtin/bundle.c b/builtin/bundle.c index acceef6200..666f01bccd 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -12,7 +12,7 @@ */ #define BUILTIN_BUNDLE_CREATE_USAGE \ - N_("git bundle create [-q | --quiet | --progress | --all-progress] [--all-progress-implied]\n" \ + N_("git bundle create [-q | --quiet | --progress]\n" \ " [--version=<version>] <file> <git-rev-list-args>") #define BUILTIN_BUNDLE_VERIFY_USAGE \ N_("git bundle verify [-q | --quiet] <file>") @@ -59,12 +59,12 @@ static int parse_options_cmd_bundle(int argc, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc) usage_msg_opt(_("need a <file> argument"), usagestr, options); - *bundle_file = prefix_filename(prefix, argv[0]); + *bundle_file = prefix_filename_except_for_dash(prefix, argv[0]); return argc; } static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { - int all_progress_implied = 0; + int all_progress_implied = 1; int progress = isatty(STDERR_FILENO); struct strvec pack_opts; int version = -1; @@ -74,11 +74,12 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { N_("do not show progress meter"), 0), OPT_SET_INT(0, "progress", &progress, N_("show progress meter"), 1), - OPT_SET_INT(0, "all-progress", &progress, - N_("show progress meter during object writing phase"), 2), - OPT_BOOL(0, "all-progress-implied", - &all_progress_implied, - N_("similar to --all-progress when progress meter is shown")), + OPT_SET_INT_F(0, "all-progress", &progress, + N_("historical; same as --progress"), 2, + PARSE_OPT_HIDDEN), + OPT_HIDDEN_BOOL(0, "all-progress-implied", + &all_progress_implied, + N_("historical; does nothing")), OPT_INTEGER(0, "version", &version, N_("specify bundle format version")), OPT_END() @@ -107,6 +108,23 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { return ret; } +/* + * Similar to read_bundle_header(), but handle "-" as stdin. + */ +static int open_bundle(const char *path, struct bundle_header *header, + const char **name) +{ + if (!strcmp(path, "-")) { + if (name) + *name = "<stdin>"; + return read_bundle_header_fd(0, header, "<stdin>"); + } + + if (name) + *name = path; + return read_bundle_header(path, header); +} + static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; @@ -118,12 +136,13 @@ static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { OPT_END() }; char *bundle_file; + const char *name; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_verify_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + if ((bundle_fd = open_bundle(bundle_file, &header, &name)) < 0) { ret = 1; goto cleanup; } @@ -134,7 +153,7 @@ static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { goto cleanup; } - fprintf(stderr, _("%s is okay\n"), bundle_file); + fprintf(stderr, _("%s is okay\n"), name); ret = 0; cleanup: free(bundle_file); @@ -155,7 +174,7 @@ static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix builtin_bundle_list_heads_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + if ((bundle_fd = open_bundle(bundle_file, &header, NULL)) < 0) { ret = 1; goto cleanup; } @@ -185,7 +204,7 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) builtin_bundle_unbundle_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + if ((bundle_fd = open_bundle(bundle_file, &header, NULL)) < 0) { ret = 1; goto cleanup; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 21a4335abb..734d730980 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -76,7 +76,7 @@ struct checkout_opts { const char *ignore_unmerged_opt; int ignore_unmerged; int pathspec_file_nul; - const char *pathspec_from_file; + char *pathspec_from_file; const char *new_branch; const char *new_branch_force; @@ -490,15 +490,28 @@ static int checkout_paths(const struct checkout_opts *opts, die(_("'%s' must be used when '%s' is not specified"), "--worktree", "--source"); - if (opts->checkout_index && !opts->checkout_worktree && - opts->writeout_stage) - die(_("'%s' or '%s' cannot be used with %s"), - "--ours", "--theirs", "--staged"); - - if (opts->checkout_index && !opts->checkout_worktree && - opts->merge) - die(_("'%s' or '%s' cannot be used with %s"), - "--merge", "--conflict", "--staged"); + /* + * Reject --staged option to the restore command when combined with + * merge-related options. Use the accept_ref flag to distinguish it + * from the checkout command, which does not accept --staged anyway. + * + * `restore --ours|--theirs --worktree --staged` could mean resolving + * conflicted paths to one side in both the worktree and the index, + * but does not currently. + * + * `restore --merge|--conflict=<style>` already recreates conflicts + * in both the worktree and the index, so adding --staged would be + * meaningless. + */ + if (!opts->accept_ref && opts->checkout_index) { + if (opts->writeout_stage) + die(_("'%s' or '%s' cannot be used with %s"), + "--ours", "--theirs", "--staged"); + + if (opts->merge) + die(_("'%s' or '%s' cannot be used with %s"), + "--merge", "--conflict", "--staged"); + } if (opts->patch_mode) { enum add_p_mode patch_mode; @@ -1877,6 +1890,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) options, checkout_usage, &new_branch_info); branch_info_release(&new_branch_info); clear_pathspec(&opts.pathspec); + free(opts.pathspec_from_file); FREE_AND_NULL(options); return ret; } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 78493c6d2b..f3cc548686 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -110,7 +110,7 @@ static struct decoration idnums; static uint32_t last_idnum; struct anonymized_entry { struct hashmap_entry hash; - const char *anon; + char *anon; const char orig[FLEX_ARRAY]; }; @@ -139,43 +139,56 @@ static int anonymized_entry_cmp(const void *cmp_data UNUSED, return strcmp(a->orig, b->orig); } +static struct anonymized_entry *add_anonymized_entry(struct hashmap *map, + unsigned hash, + const char *orig, size_t len, + char *anon) +{ + struct anonymized_entry *ret, *old; + + if (!map->cmpfn) + hashmap_init(map, anonymized_entry_cmp, NULL, 0); + + FLEX_ALLOC_MEM(ret, orig, orig, len); + hashmap_entry_init(&ret->hash, hash); + ret->anon = anon; + old = hashmap_put_entry(map, ret, hash); + + if (old) { + free(old->anon); + free(old); + } + + return ret; +} + /* * Basically keep a cache of X->Y so that we can repeatedly replace * the same anonymized string with another. The actual generation * is farmed out to the generate function. */ static const char *anonymize_str(struct hashmap *map, - char *(*generate)(void *), - const char *orig, size_t len, - void *data) + char *(*generate)(void), + const char *orig, size_t len) { struct anonymized_entry_key key; struct anonymized_entry *ret; - if (!map->cmpfn) - hashmap_init(map, anonymized_entry_cmp, NULL, 0); - hashmap_entry_init(&key.hash, memhash(orig, len)); key.orig = orig; key.orig_len = len; /* First check if it's a token the user configured manually... */ - if (anonymized_seeds.cmpfn) - ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key); - else - ret = NULL; + ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key); /* ...otherwise check if we've already seen it in this context... */ if (!ret) ret = hashmap_get_entry(map, &key, hash, &key); /* ...and finally generate a new mapping if necessary */ - if (!ret) { - FLEX_ALLOC_MEM(ret, orig, orig, len); - hashmap_entry_init(&ret->hash, key.hash.hash); - ret->anon = generate(data); - hashmap_put(map, &ret->hash); - } + if (!ret) + ret = add_anonymized_entry(map, key.hash.hash, + orig, len, generate()); return ret->anon; } @@ -188,12 +201,12 @@ static const char *anonymize_str(struct hashmap *map, */ static void anonymize_path(struct strbuf *out, const char *path, struct hashmap *map, - char *(*generate)(void *)) + char *(*generate)(void)) { while (*path) { const char *end_of_component = strchrnul(path, '/'); size_t len = end_of_component - path; - const char *c = anonymize_str(map, generate, path, len, NULL); + const char *c = anonymize_str(map, generate, path, len); strbuf_addstr(out, c); path = end_of_component; if (*path) @@ -368,7 +381,7 @@ static void print_path_1(const char *path) printf("%s", path); } -static char *anonymize_path_component(void *data) +static char *anonymize_path_component(void) { static int counter; struct strbuf out = STRBUF_INIT; @@ -390,7 +403,7 @@ static void print_path(const char *path) } } -static char *generate_fake_oid(void *data) +static char *generate_fake_oid(void) { static uint32_t counter = 1; /* avoid null oid */ const unsigned hashsz = the_hash_algo->rawsz; @@ -406,7 +419,7 @@ static const char *anonymize_oid(const char *oid_hex) { static struct hashmap objs; size_t len = strlen(oid_hex); - return anonymize_str(&objs, generate_fake_oid, oid_hex, len, NULL); + return anonymize_str(&objs, generate_fake_oid, oid_hex, len); } static void show_filemodify(struct diff_queue_struct *q, @@ -503,7 +516,7 @@ static const char *find_encoding(const char *begin, const char *end) return bol; } -static char *anonymize_ref_component(void *data) +static char *anonymize_ref_component(void) { static int counter; struct strbuf out = STRBUF_INIT; @@ -543,13 +556,13 @@ static const char *anonymize_refname(const char *refname) * We do not even bother to cache commit messages, as they are unlikely * to be repeated verbatim, and it is not that interesting when they are. */ -static char *anonymize_commit_message(const char *old) +static char *anonymize_commit_message(void) { static int counter; return xstrfmt("subject %d\n\nbody\n", counter++); } -static char *anonymize_ident(void *data) +static char *anonymize_ident(void) { static int counter; struct strbuf out = STRBUF_INIT; @@ -592,7 +605,7 @@ static void anonymize_ident_line(const char **beg, const char **end) len = split.mail_end - split.name_begin; ident = anonymize_str(&idents, anonymize_ident, - split.name_begin, len, NULL); + split.name_begin, len); strbuf_addstr(out, ident); strbuf_addch(out, ' '); strbuf_add(out, split.date_begin, split.tz_end - split.date_begin); @@ -670,7 +683,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, mark_next_object(&commit->object); if (anonymize) { - reencoded = anonymize_commit_message(message); + reencoded = anonymize_commit_message(); } else if (encoding) { switch(reencode_mode) { case REENCODE_YES: @@ -733,7 +746,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, show_progress(); } -static char *anonymize_tag(void *data) +static char *anonymize_tag(void) { static int counter; struct strbuf out = STRBUF_INIT; @@ -795,7 +808,7 @@ static void handle_tag(const char *name, struct tag *tag) if (message) { static struct hashmap tags; message = anonymize_str(&tags, anonymize_tag, - message, message_size, NULL); + message, message_size); message_size = strlen(message); } } @@ -1126,11 +1139,6 @@ static void handle_deletes(void) } } -static char *anonymize_seed(void *data) -{ - return xstrdup(data); -} - static int parse_opt_anonymize_map(const struct option *opt, const char *arg, int unset) { @@ -1152,7 +1160,8 @@ static int parse_opt_anonymize_map(const struct option *opt, if (!keylen || !*value) return error(_("--anonymize-map token cannot be empty")); - anonymize_str(map, anonymize_seed, arg, keylen, (void *)value); + add_anonymized_entry(map, memhash(arg, keylen), arg, keylen, + xstrdup(value)); return 0; } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 702c9a3397..d1a4306da3 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -213,8 +213,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) int flags = args.verbose ? CONNECT_VERBOSE : 0; if (args.diag_url) flags |= CONNECT_DIAG_URL; - conn = git_connect(fd, dest, args.uploadpack, - flags); + conn = git_connect(fd, dest, "git-upload-pack", + args.uploadpack, flags); if (!conn) return args.diag_url ? 0 : 1; } diff --git a/builtin/fetch.c b/builtin/fetch.c index bf627e0412..7221e57f35 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1883,6 +1883,8 @@ static void add_options_to_argv(struct strvec *argv) strvec_push(argv, "--ipv4"); else if (family == TRANSPORT_FAMILY_IPV6) strvec_push(argv, "--ipv6"); + if (!write_fetch_head) + strvec_push(argv, "--no-write-fetch-head"); } /* Fetch multiple remotes in parallel */ diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 6f62f40d12..6b3d07ef40 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -5,6 +5,8 @@ #include "object.h" #include "parse-options.h" #include "ref-filter.h" +#include "strvec.h" +#include "commit-reach.h" static char const * const for_each_ref_usage[] = { N_("git for-each-ref [<options>] [<pattern>]"), @@ -25,6 +27,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) struct ref_format format = REF_FORMAT_INIT; struct strbuf output = STRBUF_INIT; struct strbuf err = STRBUF_INIT; + int from_stdin = 0; + struct strvec vec = STRVEC_INIT; struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style, @@ -49,6 +53,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")), OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")), OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")), + OPT_BOOL(0, "stdin", &from_stdin, N_("read reference patterns from stdin")), OPT_END(), }; @@ -75,9 +80,27 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; - filter.name_patterns = argv; + if (from_stdin) { + struct strbuf line = STRBUF_INIT; + + if (argv[0]) + die(_("unknown arguments supplied with --stdin")); + + while (strbuf_getline(&line, stdin) != EOF) + strvec_push(&vec, line.buf); + + strbuf_release(&line); + + /* vec.v is NULL-terminated, just like 'argv'. */ + filter.name_patterns = vec.v; + } else { + filter.name_patterns = argv; + } + filter.match_as_path = 1; filter_refs(&array, &filter, FILTER_REFS_ALL); + filter_ahead_behind(the_repository, &format, &array); + ref_array_sort(sorting, &array); if (!maxcount || array.nr < maxcount) @@ -97,5 +120,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) free_commit_list(filter.with_commit); free_commit_list(filter.no_commit); ref_sorting_release(sorting); + strvec_clear(&vec); return 0; } diff --git a/builtin/log.c b/builtin/log.c index b62e629ba8..4693385e8e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -58,6 +58,7 @@ static int stdout_mboxrd; static const char *fmt_patch_subject_prefix = "PATCH"; static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; static const char *fmt_pretty; +static int format_no_prefix; static const char * const builtin_log_usage[] = { N_("git log [<options>] [<revision-range>] [[--] <path>...]"), @@ -1084,6 +1085,19 @@ static int git_format_config(const char *var, const char *value, void *cb) stdout_mboxrd = git_config_bool(var, value); return 0; } + if (!strcmp(var, "format.noprefix")) { + format_no_prefix = 1; + return 0; + } + + /* + * ignore some porcelain config which would otherwise be parsed by + * git_diff_ui_config(), via git_log_config(); we can't just avoid + * diff_ui_config completely, because we do care about some ui options + * like color. + */ + if (!strcmp(var, "diff.noprefix")) + return 0; return git_log_config(var, value, cb); } @@ -1993,6 +2007,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; + if (format_no_prefix) + diff_set_noprefix(&rev.diffopt); + if (default_attach) { rev.mime_boundary = default_attach; rev.no_inline = 1; @@ -2097,6 +2114,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* Always generate a patch */ rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + rev.always_show_header = 1; rev.zero_commit = zero_commit; rev.patch_name_max = fmt_patch_name_max; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index a03b559eca..26e309f533 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -89,12 +89,15 @@ static void write_name(const char *name) static void write_name_to_buf(struct strbuf *sb, const char *name) { - const char *rel = relative_path(name, prefix_len ? prefix : NULL, sb); + struct strbuf buf = STRBUF_INIT; + const char *rel = relative_path(name, prefix_len ? prefix : NULL, &buf); if (line_terminator) quote_c_style(rel, sb, NULL, 0); else strbuf_addstr(sb, rel); + + strbuf_release(&buf); } static const char *get_tag(const struct cache_entry *ce, const char *tag) diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 2dfbd8ed9b..a9de0575ce 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,6 +5,7 @@ #include "ref-filter.h" #include "remote.h" #include "refs.h" +#include "parse-options.h" static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 545b8bddc8..7e9e20172a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -39,6 +39,7 @@ #include "shallow.h" #include "promisor-remote.h" #include "pack-mtimes.h" +#include "parse-options.h" /* * Objects we are going to pack are collected in the `to_pack` structure. diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 82115c5808..5e93d87320 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -604,6 +604,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) "option, '--i-still-use-this', on the command line\n" "and let us know you still use it by sending an e-mail\n" "to <git@vger.kernel.org>. Thanks.\n"), stderr); + die(_("refusing to run without --i-still-use-this")); } if (load_all_packs) diff --git a/builtin/rebase.c b/builtin/rebase.c index dd31d5ab91..beebeb8f52 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -124,6 +124,7 @@ struct rebase_options { int fork_point; int update_refs; int config_autosquash; + int config_rebase_merges; int config_update_refs; }; @@ -141,6 +142,8 @@ struct rebase_options { .allow_empty_message = 1, \ .autosquash = -1, \ .config_autosquash = -1, \ + .rebase_merges = -1, \ + .config_rebase_merges = -1, \ .update_refs = -1, \ .config_update_refs = -1, \ } @@ -661,7 +664,7 @@ static int run_am(struct rebase_options *opts) format_patch.git_cmd = 1; strvec_pushl(&format_patch.args, "format-patch", "-k", "--stdout", "--full-index", "--cherry-pick", "--right-only", - "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", + "--default-prefix", "--no-renames", "--no-cover-letter", "--pretty=mboxrd", "--topo-order", "--no-base", NULL); if (opts->git_format_patch_opt.len) @@ -772,6 +775,16 @@ static int run_specific_rebase(struct rebase_options *opts) return status ? -1 : 0; } +static void parse_rebase_merges_value(struct rebase_options *options, const char *value) +{ + if (!strcmp("no-rebase-cousins", value)) + options->rebase_cousins = 0; + else if (!strcmp("rebase-cousins", value)) + options->rebase_cousins = 1; + else + die(_("Unknown rebase-merges mode: %s"), value); +} + static int rebase_config(const char *var, const char *value, void *data) { struct rebase_options *opts = data; @@ -801,6 +814,17 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.rebasemerges")) { + opts->config_rebase_merges = git_parse_maybe_bool(value); + if (opts->config_rebase_merges < 0) { + opts->config_rebase_merges = 1; + parse_rebase_merges_value(opts, value); + } else { + opts->rebase_cousins = 0; + } + return 0; + } + if (!strcmp(var, "rebase.updaterefs")) { opts->config_update_refs = git_config_bool(var, value); return 0; @@ -981,6 +1005,28 @@ static int parse_opt_empty(const struct option *opt, const char *arg, int unset) return 0; } +static int parse_opt_rebase_merges(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *options = opt->value; + + options->rebase_merges = !unset; + options->rebase_cousins = 0; + + if (arg) { + if (!*arg) { + warning(_("--rebase-merges with an empty string " + "argument is deprecated and will stop " + "working in a future version of Git. Use " + "--rebase-merges without an argument " + "instead, which does the same thing.")); + return 0; + } + parse_rebase_merges_value(options, arg); + } + + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -1036,7 +1082,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct object_id branch_base; int ignore_whitespace = 0; const char *gpg_sign = NULL; - const char *rebase_merges = NULL; struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; @@ -1138,10 +1183,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) &options.allow_empty_message, N_("allow rebasing commits with empty messages"), PARSE_OPT_HIDDEN), - {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, - N_("mode"), + OPT_CALLBACK_F('r', "rebase-merges", &options, N_("mode"), N_("try to rebase merges instead of skipping them"), - PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + PARSE_OPT_OPTARG, parse_opt_rebase_merges), OPT_BOOL(0, "fork-point", &options.fork_point, N_("use 'merge-base --fork-point' to refine upstream")), OPT_STRING('s', "strategy", &options.strategy, @@ -1437,17 +1481,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.exec.nr) imply_merge(&options, "--exec"); - if (rebase_merges) { - if (!*rebase_merges) - ; /* default mode; do nothing */ - else if (!strcmp("rebase-cousins", rebase_merges)) - options.rebase_cousins = 1; - else if (strcmp("no-rebase-cousins", rebase_merges)) - die(_("Unknown mode: %s"), rebase_merges); - options.rebase_merges = 1; - imply_merge(&options, "--rebase-merges"); - } - if (options.type == REBASE_APPLY) { if (ignore_whitespace) strvec_push(&options.git_am_opts, @@ -1515,6 +1548,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "cannot be used together")); else if (options.autosquash == -1 && options.config_autosquash == 1) die(_("apply options are incompatible with rebase.autoSquash. Consider adding --no-autosquash")); + else if (options.rebase_merges == -1 && options.config_rebase_merges == 1) + die(_("apply options are incompatible with rebase.rebaseMerges. Consider adding --no-rebase-merges")); else if (options.update_refs == -1 && options.config_update_refs == 1) die(_("apply options are incompatible with rebase.updateRefs. Consider adding --no-update-refs")); else @@ -1527,6 +1562,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.update_refs = (options.update_refs >= 0) ? options.update_refs : ((options.config_update_refs >= 0) ? options.config_update_refs : 0); + if (options.rebase_merges == 1) + imply_merge(&options, "--rebase-merges"); + options.rebase_merges = (options.rebase_merges >= 0) ? options.rebase_merges : + ((options.config_rebase_merges >= 0) ? options.config_rebase_merges : 0); + if (options.autosquash == 1) imply_merge(&options, "--autosquash"); options.autosquash = (options.autosquash >= 0) ? options.autosquash : diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index fe68c79ee3..554cda269d 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -31,6 +31,7 @@ #include "commit-reach.h" #include "worktree.h" #include "shallow.h" +#include "parse-options.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -1460,8 +1461,10 @@ static const char *update(struct command *cmd, struct shallow_info *si) find_shared_symref(worktrees, "HEAD", name); /* only refs/... are allowed */ - if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { - rp_error("refusing to create funny ref '%s' remotely", name); + if (!starts_with(name, "refs/") || + check_refname_format(name + 5, is_null_oid(new_oid) ? + REFNAME_ALLOW_ONELEVEL : 0)) { + rp_error("refusing to update funny ref '%s' remotely", name); ret = "funny refname"; goto out; } @@ -2181,7 +2184,7 @@ static const char *parse_pack_header(struct pack_header *hdr) } } -static const char *pack_lockfile; +static struct tempfile *pack_lockfile; static void push_header_arg(struct strvec *args, struct pack_header *hdr) { @@ -2248,6 +2251,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) return "unpack-objects abnormal exit"; } else { char hostname[HOST_NAME_MAX + 1]; + char *lockfile; strvec_pushl(&child.args, "index-pack", "--stdin", NULL); push_header_arg(&child.args, &hdr); @@ -2277,8 +2281,14 @@ static const char *unpack(int err_fd, struct shallow_info *si) status = start_command(&child); if (status) return "index-pack fork failed"; - pack_lockfile = index_pack_lockfile(child.out, NULL); + + lockfile = index_pack_lockfile(child.out, NULL); + if (lockfile) { + pack_lockfile = register_tempfile(lockfile); + free(lockfile); + } close(child.out); + status = finish_command(&child); if (status) return "index-pack abnormal exit"; @@ -2565,8 +2575,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) use_keepalive = KEEPALIVE_ALWAYS; execute_commands(commands, unpack_status, &si, &push_options); - if (pack_lockfile) - unlink_or_warn(pack_lockfile); + delete_tempfile(&pack_lockfile); sigchain_push(SIGPIPE, SIG_IGN); if (report_status_v2) report_v2(commands, unpack_status); diff --git a/builtin/reflog.c b/builtin/reflog.c index 270681dcdf..9b000bb6bc 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -4,6 +4,7 @@ #include "reachable.h" #include "worktree.h" #include "reflog.h" +#include "parse-options.h" #define BUILTIN_REFLOG_SHOW_USAGE \ N_("git reflog [show] [<log-options>] [<ref>]") diff --git a/builtin/reset.c b/builtin/reset.c index 58f567afd3..24b04aeecb 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -318,7 +318,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int reset_type = NONE, update_ref_status = 0, quiet = 0; int no_refresh = 0; int patch_mode = 0, pathspec_file_nul = 0, unborn; - const char *rev, *pathspec_from_file = NULL; + const char *rev; + char *pathspec_from_file = NULL; struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; @@ -496,5 +497,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) cleanup: clear_pathspec(&pathspec); + free(pathspec_from_file); return update_ref_status; } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index fb5b2bad2c..640125fe95 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -16,6 +16,7 @@ #include "gpg-interface.h" #include "gettext.h" #include "protocol.h" +#include "parse-options.h" static const char * const send_pack_usage[] = { N_("git send-pack [--mirror] [--dry-run] [--force]\n" @@ -274,7 +275,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) fd[0] = 0; fd[1] = 1; } else { - conn = git_connect(fd, dest, receivepack, + conn = git_connect(fd, dest, "git-receive-pack", receivepack, args.verbose ? CONNECT_VERBOSE : 0); } diff --git a/builtin/tag.c b/builtin/tag.c index 7cdcd4293d..7e2f686600 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -67,6 +67,7 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, die(_("unable to parse format string")); filter->with_commit_tag_algo = 1; filter_refs(&array, filter, FILTER_REFS_TAGS); + filter_ahead_behind(the_repository, format, &array); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { @@ -429,7 +430,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) int create_reflog = 0; int annotate = 0, force = 0; int cmdmode = 0, create_tag_object = 0; - const char *msgfile = NULL, *keyid = NULL; + char *msgfile = NULL; + const char *keyid = NULL; struct msg_arg msg = { .buf = STRBUF_INIT }; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; @@ -639,5 +641,6 @@ cleanup: strbuf_release(&reflog_msg); strbuf_release(&msg.buf); strbuf_release(&err); + free(msgfile); return ret; } @@ -610,6 +610,9 @@ char *prefix_path_gently(const char *prefix, int len, int *remaining, const char */ char *prefix_filename(const char *prefix, const char *path); +/* Likewise, but path=="-" always yields "-" */ +char *prefix_filename_except_for_dash(const char *prefix, const char *path); + int check_filename(const char *prefix, const char *name); void verify_filename(const char *prefix, const char *name, diff --git a/commit-graph.c b/commit-graph.c index 5e6098ff35..8fce79b39b 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -117,12 +117,10 @@ timestamp_t commit_graph_generation(const struct commit *c) struct commit_graph_data *data = commit_graph_data_slab_peek(&commit_graph_data_slab, c); - if (!data) - return GENERATION_NUMBER_INFINITY; - else if (data->graph_pos == COMMIT_NOT_FROM_GRAPH) - return GENERATION_NUMBER_INFINITY; + if (data && data->generation) + return data->generation; - return data->generation; + return GENERATION_NUMBER_INFINITY; } static struct commit_graph_data *commit_graph_data_at(const struct commit *c) @@ -1447,24 +1445,52 @@ static void close_reachable(struct write_commit_graph_context *ctx) stop_progress(&ctx->progress); } -static void compute_topological_levels(struct write_commit_graph_context *ctx) +struct compute_generation_info { + struct repository *r; + struct packed_commit_list *commits; + struct progress *progress; + int progress_cnt; + + timestamp_t (*get_generation)(struct commit *c, void *data); + void (*set_generation)(struct commit *c, timestamp_t gen, void *data); + void *data; +}; + +static timestamp_t compute_generation_from_max(struct commit *c, + timestamp_t max_gen, + int generation_version) +{ + switch (generation_version) { + case 1: /* topological levels */ + if (max_gen > GENERATION_NUMBER_V1_MAX - 1) + max_gen = GENERATION_NUMBER_V1_MAX - 1; + return max_gen + 1; + + case 2: /* corrected commit date */ + if (c->date && c->date > max_gen) + max_gen = c->date - 1; + return max_gen + 1; + + default: + BUG("attempting unimplemented version"); + } +} + +static void compute_reachable_generation_numbers( + struct compute_generation_info *info, + int generation_version) { int i; struct commit_list *list = NULL; - if (ctx->report_progress) - ctx->progress = start_delayed_progress( - _("Computing commit graph topological levels"), - ctx->commits.nr); - for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; - uint32_t level; - - repo_parse_commit(ctx->r, c); - level = *topo_level_slab_at(ctx->topo_levels, c); + for (i = 0; i < info->commits->nr; i++) { + struct commit *c = info->commits->list[i]; + timestamp_t gen; + repo_parse_commit(info->r, c); + gen = info->get_generation(c, info->data); + display_progress(info->progress, info->progress_cnt + 1); - display_progress(ctx->progress, i + 1); - if (level != GENERATION_NUMBER_ZERO) + if (gen != GENERATION_NUMBER_ZERO && gen != GENERATION_NUMBER_INFINITY) continue; commit_list_insert(c, &list); @@ -1472,41 +1498,91 @@ static void compute_topological_levels(struct write_commit_graph_context *ctx) struct commit *current = list->item; struct commit_list *parent; int all_parents_computed = 1; - uint32_t max_level = 0; + uint32_t max_gen = 0; for (parent = current->parents; parent; parent = parent->next) { - repo_parse_commit(ctx->r, parent->item); - level = *topo_level_slab_at(ctx->topo_levels, parent->item); + repo_parse_commit(info->r, parent->item); + gen = info->get_generation(parent->item, info->data); - if (level == GENERATION_NUMBER_ZERO) { + if (gen == GENERATION_NUMBER_ZERO) { all_parents_computed = 0; commit_list_insert(parent->item, &list); break; } - if (level > max_level) - max_level = level; + if (gen > max_gen) + max_gen = gen; } if (all_parents_computed) { pop_commit(&list); - - if (max_level > GENERATION_NUMBER_V1_MAX - 1) - max_level = GENERATION_NUMBER_V1_MAX - 1; - *topo_level_slab_at(ctx->topo_levels, current) = max_level + 1; + gen = compute_generation_from_max( + current, max_gen, + generation_version); + info->set_generation(current, gen, info->data); } } } +} + +static timestamp_t get_topo_level(struct commit *c, void *data) +{ + struct write_commit_graph_context *ctx = data; + return *topo_level_slab_at(ctx->topo_levels, c); +} + +static void set_topo_level(struct commit *c, timestamp_t t, void *data) +{ + struct write_commit_graph_context *ctx = data; + *topo_level_slab_at(ctx->topo_levels, c) = (uint32_t)t; +} + +static void compute_topological_levels(struct write_commit_graph_context *ctx) +{ + struct compute_generation_info info = { + .r = ctx->r, + .commits = &ctx->commits, + .get_generation = get_topo_level, + .set_generation = set_topo_level, + .data = ctx, + }; + + if (ctx->report_progress) + info.progress = ctx->progress + = start_delayed_progress( + _("Computing commit graph topological levels"), + ctx->commits.nr); + + compute_reachable_generation_numbers(&info, 1); + stop_progress(&ctx->progress); } +static timestamp_t get_generation_from_graph_data(struct commit *c, void *data) +{ + return commit_graph_data_at(c)->generation; +} + +static void set_generation_v2(struct commit *c, timestamp_t t, void *data) +{ + struct commit_graph_data *g = commit_graph_data_at(c); + g->generation = t; +} + static void compute_generation_numbers(struct write_commit_graph_context *ctx) { int i; - struct commit_list *list = NULL; + struct compute_generation_info info = { + .r = ctx->r, + .commits = &ctx->commits, + .get_generation = get_generation_from_graph_data, + .set_generation = set_generation_v2, + .data = ctx, + }; if (ctx->report_progress) - ctx->progress = start_delayed_progress( + info.progress = ctx->progress + = start_delayed_progress( _("Computing commit graph generation numbers"), ctx->commits.nr); @@ -1518,47 +1594,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) } } - for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; - timestamp_t corrected_commit_date; - - repo_parse_commit(ctx->r, c); - corrected_commit_date = commit_graph_data_at(c)->generation; - - display_progress(ctx->progress, i + 1); - if (corrected_commit_date != GENERATION_NUMBER_ZERO) - continue; - - commit_list_insert(c, &list); - while (list) { - struct commit *current = list->item; - struct commit_list *parent; - int all_parents_computed = 1; - timestamp_t max_corrected_commit_date = 0; - - for (parent = current->parents; parent; parent = parent->next) { - repo_parse_commit(ctx->r, parent->item); - corrected_commit_date = commit_graph_data_at(parent->item)->generation; - - if (corrected_commit_date == GENERATION_NUMBER_ZERO) { - all_parents_computed = 0; - commit_list_insert(parent->item, &list); - break; - } - - if (corrected_commit_date > max_corrected_commit_date) - max_corrected_commit_date = corrected_commit_date; - } - - if (all_parents_computed) { - pop_commit(&list); - - if (current->date && current->date > max_corrected_commit_date) - max_corrected_commit_date = current->date - 1; - commit_graph_data_at(current)->generation = max_corrected_commit_date + 1; - } - } - } + compute_reachable_generation_numbers(&info, 2); for (i = 0; i < ctx->commits.nr; i++) { struct commit *c = ctx->commits.list[i]; @@ -1569,6 +1605,35 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) stop_progress(&ctx->progress); } +static void set_generation_in_graph_data(struct commit *c, timestamp_t t, + void *data) +{ + commit_graph_data_at(c)->generation = t; +} + +/* + * After this method, all commits reachable from those in the given + * list will have non-zero, non-infinite generation numbers. + */ +void ensure_generations_valid(struct repository *r, + struct commit **commits, size_t nr) +{ + int generation_version = get_configured_generation_version(r); + struct packed_commit_list list = { + .list = commits, + .alloc = nr, + .nr = nr, + }; + struct compute_generation_info info = { + .r = r, + .commits = &list, + .get_generation = get_generation_from_graph_data, + .set_generation = set_generation_in_graph_data, + }; + + compute_reachable_generation_numbers(&info, generation_version); +} + static void trace2_bloom_filter_write_statistics(struct write_commit_graph_context *ctx) { trace2_data_intmax("commit-graph", ctx->r, "filter-computed", diff --git a/commit-graph.h b/commit-graph.h index bb88bec7aa..83aaa1dbb9 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -189,4 +189,12 @@ struct commit_graph_data { */ timestamp_t commit_graph_generation(const struct commit *); uint32_t commit_graph_position(const struct commit *); + +/* + * After this method, all commits reachable from those in the given + * list will have non-zero, non-infinite generation numbers. + */ +void ensure_generations_valid(struct repository *r, + struct commit **commits, size_t nr); + #endif diff --git a/commit-reach.c b/commit-reach.c index 5ba822a720..d28cc96eb0 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -10,6 +10,7 @@ #include "revision.h" #include "tag.h" #include "commit-reach.h" +#include "ewah/ewok.h" /* Remember to update object flag allocation in object.h */ #define PARENT1 (1u<<16) @@ -809,8 +810,12 @@ cleanup: clear_commit_marks_many(nr_commits, list, RESULT | assign_flag); free(list); - for (i = 0; i < from->nr; i++) - from->objects[i].item->flags &= ~assign_flag; + for (i = 0; i < from->nr; i++) { + struct object *from_one = from->objects[i].item; + + if (from_one) + from_one->flags &= ~assign_flag; + } return result; } @@ -943,3 +948,218 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from, return found_commits; } + +define_commit_slab(bit_arrays, struct bitmap *); +static struct bit_arrays bit_arrays; + +static void insert_no_dup(struct prio_queue *queue, struct commit *c) +{ + if (c->object.flags & PARENT2) + return; + prio_queue_put(queue, c); + c->object.flags |= PARENT2; +} + +static struct bitmap *get_bit_array(struct commit *c, int width) +{ + struct bitmap **bitmap = bit_arrays_at(&bit_arrays, c); + if (!*bitmap) + *bitmap = bitmap_word_alloc(width); + return *bitmap; +} + +static void free_bit_array(struct commit *c) +{ + struct bitmap **bitmap = bit_arrays_at(&bit_arrays, c); + if (!*bitmap) + return; + bitmap_free(*bitmap); + *bitmap = NULL; +} + +void ahead_behind(struct repository *r, + struct commit **commits, size_t commits_nr, + struct ahead_behind_count *counts, size_t counts_nr) +{ + struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date }; + size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD); + + if (!commits_nr || !counts_nr) + return; + + for (size_t i = 0; i < counts_nr; i++) { + counts[i].ahead = 0; + counts[i].behind = 0; + } + + ensure_generations_valid(r, commits, commits_nr); + + init_bit_arrays(&bit_arrays); + + for (size_t i = 0; i < commits_nr; i++) { + struct commit *c = commits[i]; + struct bitmap *bitmap = get_bit_array(c, width); + + bitmap_set(bitmap, i); + insert_no_dup(&queue, c); + } + + while (queue_has_nonstale(&queue)) { + struct commit *c = prio_queue_get(&queue); + struct commit_list *p; + struct bitmap *bitmap_c = get_bit_array(c, width); + + for (size_t i = 0; i < counts_nr; i++) { + int reach_from_tip = !!bitmap_get(bitmap_c, counts[i].tip_index); + int reach_from_base = !!bitmap_get(bitmap_c, counts[i].base_index); + + if (reach_from_tip ^ reach_from_base) { + if (reach_from_base) + counts[i].behind++; + else + counts[i].ahead++; + } + } + + for (p = c->parents; p; p = p->next) { + struct bitmap *bitmap_p; + + repo_parse_commit(r, p->item); + + bitmap_p = get_bit_array(p->item, width); + bitmap_or(bitmap_p, bitmap_c); + + /* + * If this parent is reachable from every starting + * commit, then none of its ancestors can contribute + * to the ahead/behind count. Mark it as STALE, so + * we can stop the walk when every commit in the + * queue is STALE. + */ + if (bitmap_popcount(bitmap_p) == commits_nr) + p->item->object.flags |= STALE; + + insert_no_dup(&queue, p->item); + } + + free_bit_array(c); + } + + /* STALE is used here, PARENT2 is used by insert_no_dup(). */ + repo_clear_commit_marks(r, PARENT2 | STALE); + clear_bit_arrays(&bit_arrays); + clear_prio_queue(&queue); +} + +struct commit_and_index { + struct commit *commit; + unsigned int index; + timestamp_t generation; +}; + +static int compare_commit_and_index_by_generation(const void *va, const void *vb) +{ + const struct commit_and_index *a = (const struct commit_and_index *)va; + const struct commit_and_index *b = (const struct commit_and_index *)vb; + + if (a->generation > b->generation) + return 1; + if (a->generation < b->generation) + return -1; + return 0; +} + +void tips_reachable_from_bases(struct repository *r, + struct commit_list *bases, + struct commit **tips, size_t tips_nr, + int mark) +{ + struct commit_and_index *commits; + size_t min_generation_index = 0; + timestamp_t min_generation; + struct commit_list *stack = NULL; + + if (!bases || !tips || !tips_nr) + return; + + /* + * Do a depth-first search starting at 'bases' to search for the + * tips. Stop at the lowest (un-found) generation number. When + * finding the lowest commit, increase the minimum generation + * number to the next lowest (un-found) generation number. + */ + + CALLOC_ARRAY(commits, tips_nr); + + for (size_t i = 0; i < tips_nr; i++) { + commits[i].commit = tips[i]; + commits[i].index = i; + commits[i].generation = commit_graph_generation(tips[i]); + } + + /* Sort with generation number ascending. */ + QSORT(commits, tips_nr, compare_commit_and_index_by_generation); + min_generation = commits[0].generation; + + while (bases) { + repo_parse_commit(r, bases->item); + commit_list_insert(bases->item, &stack); + bases = bases->next; + } + + while (stack) { + int explored_all_parents = 1; + struct commit_list *p; + struct commit *c = stack->item; + timestamp_t c_gen = commit_graph_generation(c); + + /* Does it match any of our tips? */ + for (size_t j = min_generation_index; j < tips_nr; j++) { + if (c_gen < commits[j].generation) + break; + + if (commits[j].commit == c) { + tips[commits[j].index]->object.flags |= mark; + + if (j == min_generation_index) { + unsigned int k = j + 1; + while (k < tips_nr && + (tips[commits[k].index]->object.flags & mark)) + k++; + + /* Terminate early if all found. */ + if (k >= tips_nr) + goto done; + + min_generation_index = k; + min_generation = commits[k].generation; + } + } + } + + for (p = c->parents; p; p = p->next) { + repo_parse_commit(r, p->item); + + /* Have we already explored this parent? */ + if (p->item->object.flags & SEEN) + continue; + + /* Is it below the current minimum generation? */ + if (commit_graph_generation(p->item) < min_generation) + continue; + + /* Ok, we will explore from here on. */ + p->item->object.flags |= SEEN; + explored_all_parents = 0; + commit_list_insert(p->item, &stack); + break; + } + + if (explored_all_parents) + pop_commit(&stack); + } + +done: + free(commits); + repo_clear_commit_marks(r, SEEN); +} diff --git a/commit-reach.h b/commit-reach.h index 148b56fea5..d6321ae700 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -104,4 +104,44 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from, struct commit **to, int nr_to, unsigned int reachable_flag); +struct ahead_behind_count { + /** + * As input, the *_index members indicate which positions in + * the 'tips' array correspond to the tip and base of this + * comparison. + */ + size_t tip_index; + size_t base_index; + + /** + * These values store the computed counts for each side of the + * symmetric difference: + * + * 'ahead' stores the number of commits reachable from the tip + * and not reachable from the base. + * + * 'behind' stores the number of commits reachable from the base + * and not reachable from the tip. + */ + unsigned int ahead; + unsigned int behind; +}; + +/* + * Given an array of commits and an array of ahead_behind_count pairs, + * compute the ahead/behind counts for each pair. + */ +void ahead_behind(struct repository *r, + struct commit **commits, size_t commits_nr, + struct ahead_behind_count *counts, size_t counts_nr); + +/* + * For all tip commits, add 'mark' to their flags if and only if they + * are reachable from one of the commits in 'bases'. + */ +void tips_reachable_from_bases(struct repository *r, + struct commit_list *bases, + struct commit **tips, size_t tips_nr, + int mark); + #endif @@ -1686,7 +1686,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb) comment_line_char = value[0]; auto_comment_line_char = 0; } else - return error(_("core.commentChar should only be one character")); + return error(_("core.commentChar should only be one ASCII character")); return 0; } @@ -31,7 +31,8 @@ static int check_ref(const char *name, unsigned int flags) return 0; /* REF_NORMAL means that we don't want the magic fake tag refs */ - if ((flags & REF_NORMAL) && check_refname_format(name, 0)) + if ((flags & REF_NORMAL) && check_refname_format(name, + REFNAME_ALLOW_ONELEVEL)) return 0; /* REF_HEADS means that we want regular branch heads */ @@ -1407,6 +1408,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, * the connection failed). */ struct child_process *git_connect(int fd[2], const char *url, + const char *name, const char *prog, int flags) { char *hostandport, *path; @@ -1416,10 +1418,11 @@ struct child_process *git_connect(int fd[2], const char *url, /* * NEEDSWORK: If we are trying to use protocol v2 and we are planning - * to perform a push, then fallback to v0 since the client doesn't know - * how to push yet using v2. + * to perform any operation that doesn't involve upload-pack (i.e., a + * fetch, ls-remote, etc), then fallback to v0 since we don't know how + * to do anything else (like push or remote archive) via v2. */ - if (version == protocol_v2 && !strcmp("git-receive-pack", prog)) + if (version == protocol_v2 && strcmp("git-upload-pack", name)) version = protocol_v0; /* Without this we cannot rely on waitpid() to tell @@ -7,7 +7,7 @@ #define CONNECT_DIAG_URL (1u << 1) #define CONNECT_IPV4 (1u << 2) #define CONNECT_IPV6 (1u << 3) -struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); +struct child_process *git_connect(int fd[2], const char *url, const char *name, const char *prog, int flags); int finish_connect(struct child_process *conn); int git_connection_is_socket(struct child_process *conn); int server_supports(const char *feature); diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 57972c2845..76ee4ab1e5 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -100,9 +100,7 @@ # # If you would like a colored hint about the current dirty state, set # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on -# the colored output of "git status -sb" and are available only when -# using __git_ps1 for PROMPT_COMMAND or precmd in Bash, -# but always available in Zsh. +# the colored output of "git status -sb". # # If you would like __git_ps1 to do nothing in the case when the current # directory is set up to be ignored by git, then set @@ -259,12 +257,12 @@ __git_ps1_colorize_gitstring () local c_lblue='%F{blue}' local c_clear='%f' else - # Using \[ and \] around colors is necessary to prevent + # Using \001 and \002 around colors is necessary to prevent # issues with command line editing/browsing/completion! - local c_red='\[\e[31m\]' - local c_green='\[\e[32m\]' - local c_lblue='\[\e[1;34m\]' - local c_clear='\[\e[0m\]' + local c_red=$'\001\e[31m\002' + local c_green=$'\001\e[32m\002' + local c_lblue=$'\001\e[1;34m\002' + local c_clear=$'\001\e[0m\002' fi local bad_color=$c_red local ok_color=$c_green @@ -574,11 +572,8 @@ __git_ps1 () b="\${__git_ps1_branch_name}" fi - # NO color option unless in PROMPT_COMMAND mode or it's Zsh if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then - if [ $pcmode = yes ] || [ -n "${ZSH_VERSION-}" ]; then - __git_ps1_colorize_gitstring - fi + __git_ps1_colorize_gitstring fi local f="$h$w$i$s$u$p" diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile index 4655e0987b..093399c788 100644 --- a/contrib/subtree/t/Makefile +++ b/contrib/subtree/t/Makefile @@ -74,9 +74,7 @@ aggregate-results-and-cleanup: $(T) $(MAKE) clean aggregate-results: - for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ - echo "$$f"; \ - done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh + @'$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh '$(TEST_RESULTS_DIRECTORY_SQ)' valgrind: $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" diff --git a/diagnose.c b/diagnose.c index 5b398f0cff..998f517c48 100644 --- a/diagnose.c +++ b/diagnose.c @@ -8,6 +8,7 @@ #include "strvec.h" #include "object-store.h" #include "packfile.h" +#include "parse-options.h" struct archive_dir { const char *path; diff --git a/diagnose.h b/diagnose.h index 7a4951a786..f525219ab0 100644 --- a/diagnose.h +++ b/diagnose.h @@ -2,7 +2,8 @@ #define DIAGNOSE_H #include "strbuf.h" -#include "parse-options.h" + +struct option; enum diagnose_mode { DIAGNOSE_NONE, @@ -3376,6 +3376,17 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } +void diff_set_noprefix(struct diff_options *options) +{ + options->a_prefix = options->b_prefix = ""; +} + +void diff_set_default_prefix(struct diff_options *options) +{ + options->a_prefix = "a/"; + options->b_prefix = "b/"; +} + struct userdiff_driver *get_textconv(struct repository *r, struct diff_filespec *one) { @@ -4676,10 +4687,9 @@ void repo_diff_setup(struct repository *r, struct diff_options *options) options->flags.ignore_untracked_in_submodules = 1; if (diff_no_prefix) { - options->a_prefix = options->b_prefix = ""; + diff_set_noprefix(options); } else if (!diff_mnemonic_prefix) { - options->a_prefix = "a/"; - options->b_prefix = "b/"; + diff_set_default_prefix(options); } options->color_moved = diff_color_moved_default; @@ -5263,8 +5273,18 @@ static int diff_opt_no_prefix(const struct option *opt, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(optarg); - options->a_prefix = ""; - options->b_prefix = ""; + diff_set_noprefix(options); + return 0; +} + +static int diff_opt_default_prefix(const struct option *opt, + const char *optarg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(optarg); + diff_set_default_prefix(options); return 0; } @@ -5557,6 +5577,9 @@ struct option *add_diff_options(const struct option *opts, OPT_CALLBACK_F(0, "no-prefix", options, NULL, N_("do not show any source or destination prefix"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_no_prefix), + OPT_CALLBACK_F(0, "default-prefix", options, NULL, + N_("use default prefixes a/ and b/"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_default_prefix), OPT_INTEGER_F(0, "inter-hunk-context", &options->interhunkcontext, N_("show context between diff hunks up to the specified number of lines"), PARSE_OPT_NONEG), @@ -496,6 +496,8 @@ void diff_tree_combined(const struct object_id *oid, const struct oid_array *par void diff_tree_combined_merge(const struct commit *commit, struct rev_info *rev); void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b); +void diff_set_noprefix(struct diff_options *options); +void diff_set_default_prefix(struct diff_options *options); int diff_can_quit_early(struct diff_options *); diff --git a/fsmonitor.h b/fsmonitor.h index edf7ce5203..778707b131 100644 --- a/fsmonitor.h +++ b/fsmonitor.h @@ -86,7 +86,7 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cache !(ce->ce_flags & CE_FSMONITOR_VALID)) { if (S_ISGITLINK(ce->ce_mode)) return; - istate->cache_changed = 1; + istate->cache_changed |= FSMONITOR_CHANGED; ce->ce_flags |= CE_FSMONITOR_VALID; trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_clean '%s'", ce->name); } diff --git a/git-compat-util.h b/git-compat-util.h index 1e6592624d..4a200a9fb4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -339,6 +339,25 @@ static inline const char *precompose_string_if_needed(const char *in) int compat_mkdir_wo_trailing_slash(const char*, mode_t); #endif +#ifdef time +#undef time +#endif +static inline time_t git_time(time_t *tloc) +{ + struct timeval tv; + + /* + * Avoid time(NULL), which can disagree with gettimeofday(2) + * and filesystem timestamps. + */ + gettimeofday(&tv, NULL); + + if (tloc) + *tloc = tv.tv_sec; + return tv.tv_sec; +} +#define time git_time + #ifdef NO_STRUCT_ITIMERVAL struct itimerval { struct timeval it_interval; @@ -321,6 +321,15 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt if (!opt->ignore_locale && is_utf8_locale() && !literal) options |= (PCRE2_UTF | PCRE2_UCP | PCRE2_MATCH_INVALID_UTF); +#ifndef GIT_PCRE2_VERSION_10_35_OR_HIGHER + /* + * Work around a JIT bug related to invalid Unicode character handling + * fixed in 10.35: + * https://github.com/PCRE2Project/pcre2/commit/c21bd977547d + */ + options &= ~PCRE2_UCP; +#endif + #ifndef GIT_PCRE2_VERSION_10_36_OR_HIGHER /* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */ if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS)) @@ -7,6 +7,9 @@ #if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 36) || PCRE2_MAJOR >= 11 #define GIT_PCRE2_VERSION_10_36_OR_HIGHER #endif +#if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 35) || PCRE2_MAJOR >= 11 +#define GIT_PCRE2_VERSION_10_35_OR_HIGHER +#endif #if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 34) || PCRE2_MAJOR >= 11 #define GIT_PCRE2_VERSION_10_34_OR_HIGHER #endif @@ -270,7 +270,7 @@ void hashmap_clear_(struct hashmap *map, ssize_t offset); #define hashmap_clear(map) hashmap_clear_(map, -1) /* - * Similar to hashmap_clear(), except that the table is no deallocated; it + * Similar to hashmap_clear(), except that the table is not deallocated; it * is merely zeroed out but left the same size as before. If the hashmap * will be reused, this avoids the overhead of deallocating and * reallocating map->table. As with hashmap_clear(), you may need to free diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 1d25a5737d..72aeb089cc 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -11,6 +11,7 @@ #include "promisor-remote.h" #include "trace.h" #include "url.h" +#include "parse-options.h" static int parse_combine_filter( struct list_objects_filter_options *filter_options, diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h index ef03b45132..727c986122 100644 --- a/list-objects-filter-options.h +++ b/list-objects-filter-options.h @@ -2,10 +2,11 @@ #define LIST_OBJECTS_FILTER_OPTIONS_H #include "object.h" -#include "parse-options.h" #include "string-list.h" #include "strbuf.h" +struct option; + /* * The list of defined filters for list-objects. */ diff --git a/object-name.c b/object-name.c index 69db1ec498..61e8540f2e 100644 --- a/object-name.c +++ b/object-name.c @@ -899,6 +899,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len, char *real_ref = NULL; int refs_found = 0; int at, reflog_len, nth_prior = 0; + int fatal = !(flags & GET_OID_QUIETLY); if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) { if (warn_ambiguous_refs && warn_on_object_refname_ambiguity) { @@ -953,11 +954,11 @@ static int get_oid_basic(struct repository *r, const char *str, int len, if (!len && reflog_len) /* allow "@{...}" to mean the current branch reflog */ - refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref, 0); + refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref, !fatal); else if (reflog_len) refs_found = repo_dwim_log(r, str, len, oid, &real_ref); else - refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, 0); + refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, !fatal); if (!refs_found) return -1; diff --git a/packfile.c b/packfile.c index fc2d7c2190..2023df1b75 100644 --- a/packfile.c +++ b/packfile.c @@ -1010,6 +1010,16 @@ void reprepare_packed_git(struct repository *r) struct object_directory *odb; obj_read_lock(); + + /* + * Reprepare alt odbs, in case the alternates file was modified + * during the course of this process. This only _adds_ odbs to + * the linked list, so existing odbs will continue to exist for + * the lifetime of the process. + */ + r->objects->loaded_alternates = 0; + prepare_alt_odb(r); + for (odb = r->objects->odb; odb; odb = odb->next) odb_clear_loose_cache(odb); diff --git a/parse-options.c b/parse-options.c index fd4743293f..6dd4c090e0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -59,12 +59,12 @@ static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p, return 0; } -static void fix_filename(const char *prefix, const char **file) +static void fix_filename(const char *prefix, char **file) { - if (!file || !*file || !prefix || is_absolute_path(*file) - || !strcmp("-", *file)) - return; - *file = prefix_filename(prefix, *file); + if (!file || !*file) + ; /* leave as NULL */ + else + *file = prefix_filename_except_for_dash(prefix, *file); } static enum parse_opt_result opt_command_mode_error( @@ -177,7 +177,7 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, err = get_arg(p, opt, flags, (const char **)opt->value); if (!err) - fix_filename(p->prefix, (const char **)opt->value); + fix_filename(p->prefix, (char **)opt->value); return err; case OPTION_CALLBACK: diff --git a/parse-options.h b/parse-options.h index 50d852f299..26f19384e5 100644 --- a/parse-options.h +++ b/parse-options.h @@ -158,71 +158,202 @@ struct option { parse_opt_subcommand_fn *subcommand_fn; }; -#define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \ - PARSE_OPT_NOARG|(f), NULL, (b) } -#define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), (v), NULL, \ - (h), PARSE_OPT_NOARG|(f) } -#define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ - (h), PARSE_OPT_NOARG | (f), NULL, (i) } +#define OPT_BIT_F(s, l, v, h, b, f) { \ + .type = OPTION_BIT, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG|(f), \ + .callback = NULL, \ + .defval = (b), \ +} +#define OPT_COUNTUP_F(s, l, v, h, f) { \ + .type = OPTION_COUNTUP, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG|(f), \ +} +#define OPT_SET_INT_F(s, l, v, h, i, f) { \ + .type = OPTION_SET_INT, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG | (f), \ + .defval = (i), \ +} #define OPT_BOOL_F(s, l, v, h, f) OPT_SET_INT_F(s, l, v, h, 1, f) -#define OPT_CALLBACK_F(s, l, v, a, h, f, cb) \ - { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), (cb) } -#define OPT_STRING_F(s, l, v, a, h, f) { OPTION_STRING, (s), (l), (v), (a), (h), (f) } -#define OPT_INTEGER_F(s, l, v, h, f) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) } +#define OPT_CALLBACK_F(s, l, v, a, h, f, cb) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = (a), \ + .help = (h), \ + .flags = (f), \ + .callback = (cb), \ +} +#define OPT_STRING_F(s, l, v, a, h, f) { \ + .type = OPTION_STRING, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = (a), \ + .help = (h), \ + .flags = (f), \ +} +#define OPT_INTEGER_F(s, l, v, h, f) { \ + .type = OPTION_INTEGER, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("n"), \ + .help = (h), \ + .flags = (f), \ +} -#define OPT_END() { OPTION_END } -#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } +#define OPT_END() { \ + .type = OPTION_END, \ +} +#define OPT_GROUP(h) { \ + .type = OPTION_GROUP, \ + .help = (h), \ +} #define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0) -#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \ - PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \ - (set), NULL, (clear) } -#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ - (h), PARSE_OPT_NOARG, NULL, (b) } +#define OPT_BITOP(s, l, v, h, set, clear) { \ + .type = OPTION_BITOP, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG|PARSE_OPT_NONEG, \ + .defval = (set), \ + .extra = (clear), \ +} +#define OPT_NEGBIT(s, l, v, h, b) { \ + .type = OPTION_NEGBIT, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG, \ + .defval = (b), \ +} #define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0) #define OPT_SET_INT(s, l, v, h, i) OPT_SET_INT_F(s, l, v, h, i, 0) #define OPT_BOOL(s, l, v, h) OPT_BOOL_F(s, l, v, h, 0) -#define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \ - (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1} -#define OPT_CMDMODE_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ - (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), NULL, (i) } +#define OPT_HIDDEN_BOOL(s, l, v, h) { \ + .type = OPTION_SET_INT, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, \ + .defval = 1, \ +} +#define OPT_CMDMODE_F(s, l, v, h, i, f) { \ + .type = OPTION_SET_INT, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), \ + .defval = (i), \ +} #define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0) #define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0) -#define OPT_MAGNITUDE(s, l, v, h) { OPTION_MAGNITUDE, (s), (l), (v), \ - N_("n"), (h), PARSE_OPT_NONEG } +#define OPT_MAGNITUDE(s, l, v, h) { \ + .type = OPTION_MAGNITUDE, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("n"), \ + .help = (h), \ + .flags = PARSE_OPT_NONEG, \ +} #define OPT_STRING(s, l, v, a, h) OPT_STRING_F(s, l, v, a, h, 0) -#define OPT_STRING_LIST(s, l, v, a, h) \ - { OPTION_CALLBACK, (s), (l), (v), (a), \ - (h), 0, &parse_opt_string_list } -#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ - (h), PARSE_OPT_NOARG, &parse_opt_tertiary } -#define OPT_EXPIRY_DATE(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0, \ - parse_opt_expiry_date_cb } -#define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f) -#define OPT_NUMBER_CALLBACK(v, h, f) \ - { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ - PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } -#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ - N_("file"), (h) } -#define OPT_COLOR_FLAG(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), N_("when"), (h), PARSE_OPT_OPTARG, \ - parse_opt_color_flag_cb, (intptr_t)"always" } - -#define OPT_NOOP_NOARG(s, l) \ - { OPTION_CALLBACK, (s), (l), NULL, NULL, \ - N_("no-op (backward compatibility)"), \ - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb } - -#define OPT_ALIAS(s, l, source_long_name) \ - { OPTION_ALIAS, (s), (l), (source_long_name) } +#define OPT_STRING_LIST(s, l, v, a, h) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = (a), \ + .help = (h), \ + .callback = &parse_opt_string_list, \ +} +#define OPT_UYN(s, l, v, h) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG, \ + .callback = &parse_opt_tertiary, \ +} +#define OPT_EXPIRY_DATE(s, l, v, h) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("expiry-date"), \ + .help = (h), \ + .callback = parse_opt_expiry_date_cb, \ +} +#define OPT_CALLBACK(s, l, v, a, h, cb) OPT_CALLBACK_F(s, l, v, a, h, 0, cb) +#define OPT_NUMBER_CALLBACK(v, h, cb) { \ + .type = OPTION_NUMBER, \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, \ + .callback = (cb), \ +} +#define OPT_FILENAME(s, l, v, h) { \ + .type = OPTION_FILENAME, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("file"), \ + .help = (h), \ +} +#define OPT_COLOR_FLAG(s, l, v, h) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("when"), \ + .help = (h), \ + .flags = PARSE_OPT_OPTARG, \ + .callback = parse_opt_color_flag_cb, \ + .defval = (intptr_t)"always", \ +} + +#define OPT_NOOP_NOARG(s, l) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .help = N_("no-op (backward compatibility)"), \ + .flags = PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, \ + .callback = parse_opt_noop_cb, \ +} + +#define OPT_ALIAS(s, l, source_long_name) { \ + .type = OPTION_ALIAS, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (source_long_name), \ +} #define OPT_SUBCOMMAND_F(l, v, fn, f) { \ .type = OPTION_SUBCOMMAND, \ .long_name = (l), \ .value = (v), \ .flags = (f), \ - .subcommand_fn = (fn) } + .subcommand_fn = (fn), \ +} #define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0) /* @@ -358,34 +489,80 @@ int parse_opt_tracking_mode(const struct option *, const char *, int); #define OPT__VERBOSE(var, h) OPT_COUNTUP('v', "verbose", (var), (h)) #define OPT__QUIET(var, h) OPT_COUNTUP('q', "quiet", (var), (h)) -#define OPT__VERBOSITY(var) \ - { OPTION_CALLBACK, 'v', "verbose", (var), NULL, N_("be more verbose"), \ - PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \ - { OPTION_CALLBACK, 'q', "quiet", (var), NULL, N_("be more quiet"), \ - PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 } +#define OPT__VERBOSITY(var) { \ + .type = OPTION_CALLBACK, \ + .short_name = 'v', \ + .long_name = "verbose", \ + .value = (var), \ + .help = N_("be more verbose"), \ + .flags = PARSE_OPT_NOARG, \ + .callback = &parse_opt_verbosity_cb, \ +}, { \ + .type = OPTION_CALLBACK, \ + .short_name = 'q', \ + .long_name = "quiet", \ + .value = (var), \ + .help = N_("be more quiet"), \ + .flags = PARSE_OPT_NOARG, \ + .callback = &parse_opt_verbosity_cb, \ +} #define OPT__DRY_RUN(var, h) OPT_BOOL('n', "dry-run", (var), (h)) #define OPT__FORCE(var, h, f) OPT_COUNTUP_F('f', "force", (var), (h), (f)) -#define OPT__ABBREV(var) \ - { OPTION_CALLBACK, 0, "abbrev", (var), N_("n"), \ - N_("use <n> digits to display object names"), \ - PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } +#define OPT__ABBREV(var) { \ + .type = OPTION_CALLBACK, \ + .long_name = "abbrev", \ + .value = (var), \ + .argh = N_("n"), \ + .help = N_("use <n> digits to display object names"), \ + .flags = PARSE_OPT_OPTARG, \ + .callback = &parse_opt_abbrev_cb, \ +} #define OPT__SUPER_PREFIX(var) \ OPT_STRING_F(0, "super-prefix", (var), N_("prefix"), \ N_("prefixed path to initial superproject"), PARSE_OPT_HIDDEN) #define OPT__COLOR(var, h) \ OPT_COLOR_FLAG(0, "color", (var), (h)) -#define OPT_COLUMN(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), N_("style"), (h), PARSE_OPT_OPTARG, parseopt_column_callback } -#define OPT_PASSTHRU(s, l, v, a, h, f) \ - { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru } -#define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \ - { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv } -#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \ - { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \ - PARSE_OPT_LASTARG_DEFAULT | flag, \ - parse_opt_commits, (intptr_t) "HEAD" \ - } +#define OPT_COLUMN(s, l, v, h) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("style"), \ + .help = (h), \ + .flags = PARSE_OPT_OPTARG, \ + .callback = parseopt_column_callback, \ +} +#define OPT_PASSTHRU(s, l, v, a, h, f) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = (a), \ + .help = (h), \ + .flags = (f), \ + .callback = parse_opt_passthru, \ +} +#define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .argh = (a), \ + .help = (h), \ + .flags = (f), \ + .callback = parse_opt_passthru_argv, \ +} +#define _OPT_CONTAINS_OR_WITH(l, v, h, f) { \ + .type = OPTION_CALLBACK, \ + .long_name = (l), \ + .value = (v), \ + .argh = N_("commit"), \ + .help = (h), \ + .flags = PARSE_OPT_LASTARG_DEFAULT | (f), \ + .callback = parse_opt_commits, \ + .defval = (intptr_t) "HEAD", \ +} #define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, PARSE_OPT_NONEG) #define OPT_NO_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("no-contains", v, h, PARSE_OPT_NONEG) #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG) diff --git a/read-cache.c b/read-cache.c index 1bcf673271..12564bbd90 100644 --- a/read-cache.c +++ b/read-cache.c @@ -2903,6 +2903,16 @@ static int record_ieot(void) return !git_config_get_index_threads(&val) && val != 1; } +enum write_extensions { + WRITE_NO_EXTENSION = 0, + WRITE_SPLIT_INDEX_EXTENSION = 1<<0, + WRITE_CACHE_TREE_EXTENSION = 1<<1, + WRITE_RESOLVE_UNDO_EXTENSION = 1<<2, + WRITE_UNTRACKED_CACHE_EXTENSION = 1<<3, + WRITE_FSMONITOR_EXTENSION = 1<<4, +}; +#define WRITE_ALL_EXTENSIONS ((enum write_extensions)-1) + /* * On success, `tempfile` is closed. If it is the temporary file * of a `struct lock_file`, we will therefore effectively perform @@ -2911,7 +2921,7 @@ static int record_ieot(void) * rely on it. */ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, - int strip_extensions, unsigned flags) + enum write_extensions write_extensions, unsigned flags) { uint64_t start = getnanotime(); struct hashfile *f; @@ -3084,8 +3094,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, return -1; } - if (!strip_extensions && istate->split_index && - !is_null_oid(&istate->split_index->base_oid)) { + if (write_extensions & WRITE_SPLIT_INDEX_EXTENSION && + istate->split_index) { struct strbuf sb = STRBUF_INIT; if (istate->sparse_index) @@ -3099,7 +3109,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (err) return -1; } - if (!strip_extensions && !drop_cache_tree && istate->cache_tree) { + if (write_extensions & WRITE_CACHE_TREE_EXTENSION && + !drop_cache_tree && istate->cache_tree) { struct strbuf sb = STRBUF_INIT; cache_tree_write(&sb, istate->cache_tree); @@ -3109,7 +3120,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (err) return -1; } - if (!strip_extensions && istate->resolve_undo) { + if (write_extensions & WRITE_RESOLVE_UNDO_EXTENSION && + istate->resolve_undo) { struct strbuf sb = STRBUF_INIT; resolve_undo_write(&sb, istate->resolve_undo); @@ -3120,7 +3132,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (err) return -1; } - if (!strip_extensions && istate->untracked) { + if (write_extensions & WRITE_UNTRACKED_CACHE_EXTENSION && + istate->untracked) { struct strbuf sb = STRBUF_INIT; write_untracked_extension(&sb, istate->untracked); @@ -3131,7 +3144,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (err) return -1; } - if (!strip_extensions && istate->fsmonitor_last_update) { + if (write_extensions & WRITE_FSMONITOR_EXTENSION && + istate->fsmonitor_last_update) { struct strbuf sb = STRBUF_INIT; write_fsmonitor_extension(&sb, istate); @@ -3205,8 +3219,10 @@ static int commit_locked_index(struct lock_file *lk) return commit_lock_file(lk); } -static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, - unsigned flags) +static int do_write_locked_index(struct index_state *istate, + struct lock_file *lock, + unsigned flags, + enum write_extensions write_extensions) { int ret; int was_full = istate->sparse_index == INDEX_EXPANDED; @@ -3224,7 +3240,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l */ trace2_region_enter_printf("index", "do_write_index", the_repository, "%s", get_lock_file_path(lock)); - ret = do_write_index(istate, lock->tempfile, 0, flags); + ret = do_write_index(istate, lock->tempfile, write_extensions, flags); trace2_region_leave_printf("index", "do_write_index", the_repository, "%s", get_lock_file_path(lock)); @@ -3253,7 +3269,7 @@ static int write_split_index(struct index_state *istate, { int ret; prepare_to_write_split_index(istate); - ret = do_write_locked_index(istate, lock, flags); + ret = do_write_locked_index(istate, lock, flags, WRITE_ALL_EXTENSIONS); finish_writing_split_index(istate); return ret; } @@ -3328,7 +3344,7 @@ static int write_shared_index(struct index_state *istate, trace2_region_enter_printf("index", "shared/do_write_index", the_repository, "%s", get_tempfile_path(*temp)); - ret = do_write_index(si->base, *temp, 1, flags); + ret = do_write_index(si->base, *temp, WRITE_NO_EXTENSION, flags); trace2_region_leave_printf("index", "shared/do_write_index", the_repository, "%s", get_tempfile_path(*temp)); @@ -3405,9 +3421,8 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, if ((!si && !test_split_index_env) || alternate_index_output || (istate->cache_changed & ~EXTMASK)) { - if (si) - oidclr(&si->base_oid); - ret = do_write_locked_index(istate, lock, flags); + ret = do_write_locked_index(istate, lock, flags, + ~WRITE_SPLIT_INDEX_EXTENSION); goto out; } @@ -3433,8 +3448,8 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, /* Same initial permissions as the main .git/index file */ temp = mks_tempfile_sm(git_path("sharedindex_XXXXXX"), 0, 0666); if (!temp) { - oidclr(&si->base_oid); - ret = do_write_locked_index(istate, lock, flags); + ret = do_write_locked_index(istate, lock, flags, + ~WRITE_SPLIT_INDEX_EXTENSION); goto out; } ret = write_shared_index(istate, &temp, flags); diff --git a/ref-filter.c b/ref-filter.c index ed802778da..25831e3336 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -158,6 +158,7 @@ enum atom_type { ATOM_THEN, ATOM_ELSE, ATOM_REST, + ATOM_AHEADBEHIND, }; /* @@ -600,6 +601,22 @@ static int rest_atom_parser(struct ref_format *format, return 0; } +static int ahead_behind_atom_parser(struct ref_format *format, struct used_atom *atom, + const char *arg, struct strbuf *err) +{ + struct string_list_item *item; + + if (!arg) + return strbuf_addf_ret(err, -1, _("expected format: %%(ahead-behind:<committish>)")); + + item = string_list_append(&format->bases, arg); + item->util = lookup_commit_reference_by_name(arg); + if (!item->util) + die("failed to find '%s'", arg); + + return 0; +} + static int head_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, const char *arg, struct strbuf *err) @@ -660,6 +677,7 @@ static struct { [ATOM_THEN] = { "then", SOURCE_NONE }, [ATOM_ELSE] = { "else", SOURCE_NONE }, [ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser }, + [ATOM_AHEADBEHIND] = { "ahead-behind", SOURCE_OTHER, FIELD_STR, ahead_behind_atom_parser }, /* * Please update $__git_ref_fieldlist in git-completion.bash * when you add new atoms @@ -1866,6 +1884,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err) struct object *obj; int i; struct object_info empty = OBJECT_INFO_INIT; + int ahead_behind_atoms = 0; CALLOC_ARRAY(ref->value, used_atom_cnt); @@ -1996,6 +2015,16 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err) else v->s = xstrdup(""); continue; + } else if (atom_type == ATOM_AHEADBEHIND) { + if (ref->counts) { + const struct ahead_behind_count *count; + count = ref->counts[ahead_behind_atoms++]; + v->s = xstrfmt("%d %d", count->ahead, count->behind); + } else { + /* Not a commit. */ + v->s = xstrdup(""); + } + continue; } else continue; @@ -2346,6 +2375,7 @@ static void free_array_item(struct ref_array_item *item) free((char *)item->value[i].s); free(item->value); } + free(item->counts); free(item); } @@ -2374,6 +2404,8 @@ void ref_array_clear(struct ref_array *array) free_worktrees(ref_to_worktree_map.worktrees); ref_to_worktree_map.worktrees = NULL; } + + FREE_AND_NULL(array->counts); } #define EXCLUDE_REACHED 0 @@ -2382,33 +2414,22 @@ static void reach_filter(struct ref_array *array, struct commit_list *check_reachable, int include_reached) { - struct rev_info revs; int i, old_nr; struct commit **to_clear; - struct commit_list *cr; if (!check_reachable) return; CALLOC_ARRAY(to_clear, array->nr); - - repo_init_revisions(the_repository, &revs, NULL); - for (i = 0; i < array->nr; i++) { struct ref_array_item *item = array->items[i]; - add_pending_object(&revs, &item->commit->object, item->refname); to_clear[i] = item->commit; } - for (cr = check_reachable; cr; cr = cr->next) { - struct commit *merge_commit = cr->item; - merge_commit->object.flags |= UNINTERESTING; - add_pending_object(&revs, &merge_commit->object, ""); - } - - revs.limited = 1; - if (prepare_revision_walk(&revs)) - die(_("revision walk setup failed")); + tips_reachable_from_bases(the_repository, + check_reachable, + to_clear, array->nr, + UNINTERESTING); old_nr = array->nr; array->nr = 0; @@ -2432,10 +2453,50 @@ static void reach_filter(struct ref_array *array, clear_commit_marks(merge_commit, ALL_REV_FLAGS); } - release_revisions(&revs); free(to_clear); } +void filter_ahead_behind(struct repository *r, + struct ref_format *format, + struct ref_array *array) +{ + struct commit **commits; + size_t commits_nr = format->bases.nr + array->nr; + + if (!format->bases.nr || !array->nr) + return; + + ALLOC_ARRAY(commits, commits_nr); + for (size_t i = 0; i < format->bases.nr; i++) + commits[i] = format->bases.items[i].util; + + ALLOC_ARRAY(array->counts, st_mult(format->bases.nr, array->nr)); + + commits_nr = format->bases.nr; + array->counts_nr = 0; + for (size_t i = 0; i < array->nr; i++) { + const char *name = array->items[i]->refname; + commits[commits_nr] = lookup_commit_reference_by_name(name); + + if (!commits[commits_nr]) + continue; + + CALLOC_ARRAY(array->items[i]->counts, format->bases.nr); + for (size_t j = 0; j < format->bases.nr; j++) { + struct ahead_behind_count *count; + count = &array->counts[array->counts_nr++]; + count->tip_index = commits_nr; + count->base_index = j; + + array->items[i]->counts[j] = count; + } + commits_nr++; + } + + ahead_behind(r, commits, commits_nr, array->counts, array->counts_nr); + free(commits); +} + /* * API for filtering a set of refs. Based on the type of refs the user * has requested, we iterate through those refs and apply filters diff --git a/ref-filter.h b/ref-filter.h index aa0eea4ecf..ae51ace3a2 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -4,7 +4,7 @@ #include "oid-array.h" #include "refs.h" #include "commit.h" -#include "parse-options.h" +#include "string-list.h" /* Quoting styles */ #define QUOTE_NONE 0 @@ -24,6 +24,8 @@ struct atom_value; struct ref_sorting; +struct ahead_behind_count; +struct option; enum ref_sorting_order { REF_SORTING_REVERSE = 1<<0, @@ -40,6 +42,8 @@ struct ref_array_item { const char *symref; struct commit *commit; struct atom_value *value; + struct ahead_behind_count **counts; + char refname[FLEX_ARRAY]; }; @@ -47,6 +51,9 @@ struct ref_array { int nr, alloc; struct ref_array_item **items; struct rev_info *revs; + + struct ahead_behind_count *counts; + size_t counts_nr; }; struct ref_filter { @@ -80,9 +87,15 @@ struct ref_format { /* Internal state to ref-filter */ int need_color_reset_at_eol; + + /* List of bases for ahead-behind counts. */ + struct string_list bases; }; -#define REF_FORMAT_INIT { .use_color = -1 } +#define REF_FORMAT_INIT { \ + .use_color = -1, \ + .bases = STRING_LIST_INIT_DUP, \ +} /* Macros for checking --merged and --no-merged options */ #define _OPT_MERGED_NO_MERGED(option, filter, h) \ @@ -143,4 +156,15 @@ struct ref_array_item *ref_array_push(struct ref_array *array, const char *refname, const struct object_id *oid); +/* + * If the provided format includes ahead-behind atoms, then compute the + * ahead-behind values for the array of filtered references. Must be + * called after filter_refs() but before outputting the formatted refs. + * + * If this is not called, then any ahead-behind atoms will be blank. + */ +void filter_ahead_behind(struct repository *r, + struct ref_format *format, + struct ref_array *array); + #endif /* REF_FILTER_H */ diff --git a/remote-curl.c b/remote-curl.c index ed7e3a043a..bf5a0b186f 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -474,10 +474,11 @@ static struct discovery *discover_refs(const char *service, int for_push) /* * NEEDSWORK: If we are trying to use protocol v2 and we are planning - * to perform a push, then fallback to v0 since the client doesn't know - * how to push yet using v2. + * to perform any operation that doesn't involve upload-pack (i.e., a + * fetch, ls-remote, etc), then fallback to v0 since we don't know how + * to do anything else (like push or remote archive) via v2. */ - if (version == protocol_v2 && !strcmp("git-receive-pack", service)) + if (version == protocol_v2 && strcmp("git-upload-pack", service)) version = protocol_v0; /* Add the extra Git-Protocol header */ @@ -17,6 +17,7 @@ #include "commit-reach.h" #include "advice.h" #include "connect.h" +#include "parse-options.h" enum map_direction { FROM_SRC, FROM_DST }; @@ -1,10 +1,10 @@ #ifndef REMOTE_H #define REMOTE_H -#include "parse-options.h" #include "hashmap.h" #include "refspec.h" +struct option; struct transport_ls_refs_options; /** diff --git a/revision.c b/revision.c index e4c066e90b..8b1ecf07fc 100644 --- a/revision.c +++ b/revision.c @@ -37,6 +37,7 @@ #include "json-writer.h" #include "list-objects-filter-options.h" #include "resolve-undo.h" +#include "parse-options.h" volatile show_early_output_fn_t show_early_output; diff --git a/revision.h b/revision.h index ab71443696..649f817f39 100644 --- a/revision.h +++ b/revision.h @@ -2,7 +2,6 @@ #define REVISION_H #include "commit.h" -#include "parse-options.h" #include "grep.h" #include "notes.h" #include "pretty.h" @@ -62,6 +61,8 @@ struct string_list; struct saved_parents; struct bloom_key; struct bloom_filter_settings; +struct option; +struct parse_opt_ctx_t; define_shared_commit_slab(revision_sources, char *); struct rev_cmdline_info { diff --git a/send-pack.c b/send-pack.c index 423a5cfe22..0d05191162 100644 --- a/send-pack.c +++ b/send-pack.c @@ -17,6 +17,7 @@ #include "gpg-interface.h" #include "cache.h" #include "shallow.h" +#include "parse-options.h" int option_parse_push_signed(const struct option *opt, const char *arg, int unset) diff --git a/sequencer.c b/sequencer.c index fb99115be5..ca7c228c9f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2919,13 +2919,18 @@ static int populate_opts_cb(const char *key, const char *value, void *data) void parse_strategy_opts(struct replay_opts *opts, char *raw_opts) { int i; + int count; char *strategy_opts_string = raw_opts; if (*strategy_opts_string == ' ') strategy_opts_string++; - opts->xopts_nr = split_cmdline(strategy_opts_string, - (const char ***)&opts->xopts); + count = split_cmdline(strategy_opts_string, + (const char ***)&opts->xopts); + if (count < 0) + die(_("could not split '%s': %s"), strategy_opts_string, + split_cmdline_strerror(count)); + opts->xopts_nr = count; for (i = 0; i < opts->xopts_nr; i++) { const char *arg = opts->xopts[i]; @@ -3182,25 +3187,7 @@ static int create_seq_dir(struct repository *r) static int save_head(const char *head) { - struct lock_file head_lock = LOCK_INIT; - struct strbuf buf = STRBUF_INIT; - int fd; - ssize_t written; - - fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0); - if (fd < 0) - return error_errno(_("could not lock HEAD")); - strbuf_addf(&buf, "%s\n", head); - written = write_in_full(fd, buf.buf, buf.len); - strbuf_release(&buf); - if (written < 0) { - error_errno(_("could not write to '%s'"), git_path_head_file()); - rollback_lock_file(&head_lock); - return -1; - } - if (commit_lock_file(&head_lock) < 0) - return error(_("failed to finalize '%s'"), git_path_head_file()); - return 0; + return write_message(head, strlen(head), git_path_head_file(), 1); } static int rollback_is_safe(void) @@ -3671,7 +3658,6 @@ static int safe_append(const char *filename, const char *fmt, ...) } if (commit_lock_file(&lock) < 0) { strbuf_release(&buf); - rollback_lock_file(&lock); return error(_("failed to finalize '%s'"), filename); } diff --git a/t/Makefile b/t/Makefile index 2c2b252240..88fa504957 100644 --- a/t/Makefile +++ b/t/Makefile @@ -140,9 +140,7 @@ aggregate-results-and-cleanup: $(T) $(MAKE) clean aggregate-results: - for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ - echo "$$f"; \ - done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh + @'$(SHELL_PATH_SQ)' ./aggregate-results.sh '$(TEST_RESULTS_DIRECTORY_SQ)' valgrind: $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" diff --git a/t/aggregate-results.sh b/t/aggregate-results.sh index 7f2b83bdc8..6e3bcc4aec 100755 --- a/t/aggregate-results.sh +++ b/t/aggregate-results.sh @@ -8,7 +8,7 @@ broken=0 total=0 missing_prereq= -while read file +for file in "$1"/t*-*.counts do while read type value do diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index f1b9a6ce4d..b35be20cf3 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -72,6 +72,16 @@ test_expect_success 'blame 1 author' ' check_count A 2 ' +test_expect_success 'blame with --contents' ' + check_count --contents=file A 2 +' + +test_expect_success 'blame with --contents changed' ' + echo "1A quick brown fox jumps over the" >contents && + echo "another lazy dog" >>contents && + check_count --contents=contents A 1 "Not Committed Yet" 1 +' + test_expect_success 'blame in a bare repo without starting commit' ' git clone --bare . bare.git && ( @@ -98,6 +108,10 @@ test_expect_success 'blame 2 authors' ' check_count A 2 B 2 ' +test_expect_success 'blame with --contents and revision' ' + check_count -h testTag --contents=file A 2 "Not Committed Yet" 2 +' + test_expect_success 'setup B1 lines (branch1)' ' git checkout -b branch1 main && echo "3A slow green fox jumps into the" >>file && diff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c index b3e08cef4b..71d2131fba 100644 --- a/t/helper/test-parse-pathspec-file.c +++ b/t/helper/test-parse-pathspec-file.c @@ -6,7 +6,7 @@ int cmd__parse_pathspec_file(int argc, const char **argv) { struct pathspec pathspec; - const char *pathspec_from_file = NULL; + char *pathspec_from_file = NULL; int pathspec_file_nul = 0, i; static const char *const usage[] = { @@ -29,5 +29,6 @@ int cmd__parse_pathspec_file(int argc, const char **argv) printf("%s\n", pathspec.items[i].original); clear_pathspec(&pathspec); + free(pathspec_from_file); return 0; } diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 3e876536eb..6805229dcb 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -228,8 +228,12 @@ test_http_push_nonff () { git commit -a -m path2 --amend && test_must_fail git push -v origin >output 2>&1 && - (cd "$REMOTE_REPO" && - test $HEAD = $(git rev-parse --verify HEAD)) + ( + cd "$REMOTE_REPO" && + echo "$HEAD" >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual + ) ' test_expect_success 'non-fast-forward push show ref status' ' diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh index cfd76bf987..89ca1f7805 100644 --- a/t/lib-patch-mode.sh +++ b/t/lib-patch-mode.sh @@ -29,8 +29,12 @@ set_and_save_state () { # verify_state <path> <expected-worktree-content> <expected-index-content> verify_state () { - test "$(cat "$1")" = "$2" && - test "$(git show :"$1")" = "$3" + echo "$2" >expect && + test_cmp expect "$1" && + + echo "$3" >expect && + git show :"$1" >actual && + test_cmp expect actual } # verify_saved_state <path> @@ -46,5 +50,6 @@ save_head () { } verify_saved_head () { - test "$(cat _head)" = "$(git rev-parse HEAD)" + git rev-parse HEAD >actual && + test_cmp _head actual } diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 2d31fcfda1..dee14992c5 100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -168,20 +168,16 @@ replace_gitfile_with_git_dir () { # Note that this only supports submodules at the root level of the # superproject, with the default name, i.e. same as its path. test_git_directory_is_unchanged () { - ( - cd ".git/modules/$1" && - # does core.worktree point at the right place? - test "$(git config core.worktree)" = "../../../$1" && - # remove it temporarily before comparing, as - # "$1/.git/config" lacks it... - git config --unset core.worktree - ) && + # does core.worktree point at the right place? + echo "../../../$1" >expect && + git -C ".git/modules/$1" config core.worktree >actual && + test_cmp expect actual && + # remove it temporarily before comparing, as + # "$1/.git/config" lacks it... + git -C ".git/modules/$1" config --unset core.worktree && diff -r ".git/modules/$1" "$1/.git" && - ( - # ... and then restore. - cd ".git/modules/$1" && - git config core.worktree "../../../$1" - ) + # ... and then restore. + git -C ".git/modules/$1" config core.worktree "../../../$1" } test_git_directory_exists () { @@ -189,7 +185,9 @@ test_git_directory_exists () { if test -f sub1/.git then # does core.worktree point at the right place? - test "$(git -C .git/modules/$1 config core.worktree)" = "../../../$1" + echo "../../../$1" >expect && + git -C ".git/modules/$1" config core.worktree >actual && + test_cmp expect actual fi } diff --git a/t/perf/p1500-graph-walks.sh b/t/perf/p1500-graph-walks.sh new file mode 100755 index 0000000000..e14e7620cc --- /dev/null +++ b/t/perf/p1500-graph-walks.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='Commit walk performance tests' +. ./perf-lib.sh + +test_perf_large_repo + +test_expect_success 'setup' ' + git for-each-ref --format="%(refname)" "refs/heads/*" "refs/tags/*" >allrefs && + sort -r allrefs | head -n 50 >refs && + for ref in $(cat refs) + do + git branch -f ref-$ref $ref && + echo ref-$ref || + return 1 + done >branches && + for ref in $(cat refs) + do + git tag -f tag-$ref $ref && + echo tag-$ref || + return 1 + done >tags && + git commit-graph write --reachable +' + +test_perf 'ahead-behind counts: git for-each-ref' ' + git for-each-ref --format="%(ahead-behind:HEAD)" --stdin <refs +' + +test_perf 'ahead-behind counts: git branch' ' + xargs git branch -l --format="%(ahead-behind:HEAD)" <branches +' + +test_perf 'ahead-behind counts: git tag' ' + xargs git tag -l --format="%(ahead-behind:HEAD)" <tags +' + +test_perf 'contains: git for-each-ref --merged' ' + git for-each-ref --merged=HEAD --stdin <refs +' + +test_perf 'contains: git branch --merged' ' + xargs git branch --merged=HEAD <branches +' + +test_perf 'contains: git tag --merged' ' + xargs git tag --merged=HEAD <tags +' + +test_done diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index 3242cfe91a..e4a132f593 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -124,6 +124,6 @@ test_perf_on_all git read-tree -mu HEAD test_perf_on_all git checkout-index -f --all test_perf_on_all git update-index --add --remove $SPARSE_CONE/a test_perf_on_all "git rm -f $SPARSE_CONE/a && git checkout HEAD -- $SPARSE_CONE/a" -test_perf_on_all git grep --cached --sparse bogus -- "f2/f1/f1/*" +test_perf_on_all git grep --cached bogus -- "f2/f1/f1/*" test_done diff --git a/t/t0001-init.sh b/t/t0001-init.sh index d479303efa..30a6edca1d 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -598,9 +598,14 @@ test_expect_success 'invalid default branch name' ' test_expect_success 'branch -m with the initial branch' ' git init rename-initial && git -C rename-initial branch -m renamed && - test renamed = $(git -C rename-initial symbolic-ref --short HEAD) && + echo renamed >expect && + git -C rename-initial symbolic-ref --short HEAD >actual && + test_cmp expect actual && + git -C rename-initial branch -m renamed again && - test again = $(git -C rename-initial symbolic-ref --short HEAD) + echo again >expect && + git -C rename-initial symbolic-ref --short HEAD >actual && + test_cmp expect actual ' test_done diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 26eaca095a..e013d38f48 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -33,7 +33,9 @@ test_expect_success 'bad setup: invalid .git file path' ' test_expect_success 'final setup + check rev-parse --git-dir' ' echo "gitdir: $REAL" >.git && - test "$REAL" = "$(git rev-parse --git-dir)" + echo "$REAL" >expect && + git rev-parse --git-dir >actual && + test_cmp expect actual ' test_expect_success 'check hash-object' ' diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index a94ac1eae3..2f57c8669c 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -70,7 +70,8 @@ create_NNO_MIX_files () { cp CRLF ${pfx}_CRLF.txt && cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt && cp LF_mix_CR ${pfx}_LF_mix_CR.txt && - cp CRLF_nul ${pfx}_CRLF_nul.txt + cp CRLF_nul ${pfx}_CRLF_nul.txt || + return 1 done done done @@ -101,7 +102,8 @@ commit_check_warn () { do fname=${pfx}_$f.txt && cp $f $fname && - git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" || + return 1 done && git commit -m "core.autocrlf $crlf" && check_warning "$lfname" ${pfx}_LF.err && @@ -121,15 +123,19 @@ commit_chk_wrnNNO () { lfmixcr=$1 ; shift crlfnul=$1 ; shift pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} - #Commit files on top of existing file - create_gitattributes "$attr" $aeol && - for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul - do - fname=${pfx}_$f.txt && - cp $f $fname && - printf Z >>"$fname" && - git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" - done + + test_expect_success 'setup commit NNO files' ' + #Commit files on top of existing file + create_gitattributes "$attr" $aeol && + for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + do + fname=${pfx}_$f.txt && + cp $f $fname && + printf Z >>"$fname" && + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" || + return 1 + done + ' test_expect_success "commit NNO files crlf=$crlf attr=$attr LF" ' check_warning "$lfwarn" ${pfx}_LF.err @@ -163,15 +169,19 @@ commit_MIX_chkwrn () { lfmixcr=$1 ; shift crlfnul=$1 ; shift pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} - #Commit file with CLRF_mix_LF on top of existing file - create_gitattributes "$attr" $aeol && - for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul - do - fname=${pfx}_$f.txt && - cp CRLF_mix_LF $fname && - printf Z >>"$fname" && - git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" - done + + test_expect_success 'setup commit file with mixed EOL' ' + #Commit file with CLRF_mix_LF on top of existing file + create_gitattributes "$attr" $aeol && + for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + do + fname=${pfx}_$f.txt && + cp CRLF_mix_LF $fname && + printf Z >>"$fname" && + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" || + return 1 + done + ' test_expect_success "commit file with mixed EOL onto LF crlf=$crlf attr=$attr" ' check_warning "$lfwarn" ${pfx}_LF.err @@ -289,17 +299,17 @@ checkout_files () { lfmixcrlf=$1 ; shift lfmixcr=$1 ; shift crlfnul=$1 ; shift - create_gitattributes "$attr" $ident $aeol && - git config core.autocrlf $crlf && + test_expect_success "setup config for checkout attr=$attr ident=$ident aeol=$aeol core.autocrlf=$crlf" ' + create_gitattributes "$attr" $ident $aeol && + git config core.autocrlf $crlf + ' pfx=eol_${ceol}_crlf_${crlf}_attr_${attr}_ && for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul do - rm crlf_false_attr__$f.txt && - if test -z "$ceol"; then - git checkout -- crlf_false_attr__$f.txt - else - git -c core.eol=$ceol checkout -- crlf_false_attr__$f.txt - fi + test_expect_success "setup $f checkout ${ceol:+ with -c core.eol=$ceol}" ' + rm -f crlf_false_attr__$f.txt && + git ${ceol:+-c core.eol=$ceol} checkout -- crlf_false_attr__$f.txt + ' done test_expect_success "ls-files --eol attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol" ' diff --git a/t/t0055-beyond-symlinks.sh b/t/t0055-beyond-symlinks.sh index 6bada37022..c3eb1158ef 100755 --- a/t/t0055-beyond-symlinks.sh +++ b/t/t0055-beyond-symlinks.sh @@ -15,12 +15,22 @@ test_expect_success SYMLINKS setup ' test_expect_success SYMLINKS 'update-index --add beyond symlinks' ' test_must_fail git update-index --add c/d && - ! ( git ls-files | grep c/d ) + cat >expect <<-\EOF && + a + b/d + EOF + git ls-files >actual && + test_cmp expect actual ' test_expect_success SYMLINKS 'add beyond symlinks' ' test_must_fail git add c/d && - ! ( git ls-files | grep c/d ) + cat >expect <<-\EOF && + a + b/d + EOF + git ls-files >actual && + test_cmp expect actual ' test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 68e29c904a..0afa3d0d31 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -10,20 +10,27 @@ TEST_PASSES_SANITIZE_LEAK=true norm_path() { expected=$(test-tool path-utils print_path "$2") - test_expect_success $3 "normalize path: $1 => $2" \ - "test \"\$(test-tool path-utils normalize_path_copy '$1')\" = '$expected'" + test_expect_success $3 "normalize path: $1 => $2" " + echo '$expected' >expect && + test-tool path-utils normalize_path_copy '$1' >actual && + test_cmp expect actual + " } relative_path() { expected=$(test-tool path-utils print_path "$3") - test_expect_success $4 "relative path: $1 $2 => $3" \ - "test \"\$(test-tool path-utils relative_path '$1' '$2')\" = '$expected'" + test_expect_success $4 "relative path: $1 $2 => $3" " + echo '$expected' >expect && + test-tool path-utils relative_path '$1' '$2' >actual && + test_cmp expect actual + " } test_submodule_relative_url() { test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" " - actual=\$(test-tool submodule resolve-relative-url '$1' '$2' '$3') && - test \"\$actual\" = '$4' + echo '$4' >expect && + test-tool submodule resolve-relative-url '$1' '$2' '$3' >actual && + test_cmp expect actual " } @@ -64,9 +71,11 @@ ancestor() { expected=$(($expected-$rootslash+$rootoff)) ;; esac - test_expect_success $4 "longest ancestor: $1 $2 => $expected" \ - "actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') && - test \"\$actual\" = '$expected'" + test_expect_success $4 "longest ancestor: $1 $2 => $expected" " + echo '$expected' >expect && + test-tool path-utils longest_ancestor_length '$1' '$2' >actual && + test_cmp expect actual + " } # Some absolute path tests should be skipped on Windows due to path mangling @@ -166,8 +175,10 @@ ancestor D:/Users/me C:/ -1 MINGW ancestor //server/share/my-directory //server/share/ 14 MINGW test_expect_success 'strip_path_suffix' ' - test c:/msysgit = $(test-tool path-utils strip_path_suffix \ - c:/msysgit/libexec//git-core libexec/git-core) + echo c:/msysgit >expect && + test-tool path-utils strip_path_suffix \ + c:/msysgit/libexec//git-core libexec/git-core >actual && + test_cmp expect actual ' test_expect_success 'absolute path rejects the empty string' ' @@ -188,35 +199,61 @@ test_expect_success 'real path rejects the empty string' ' ' test_expect_success POSIX 'real path works on absolute paths 1' ' + echo / >expect && + test-tool path-utils real_path "/" >actual && + test_cmp expect actual && + nopath="hopefully-absent-path" && - test "/" = "$(test-tool path-utils real_path "/")" && - test "/$nopath" = "$(test-tool path-utils real_path "/$nopath")" + echo "/$nopath" >expect && + test-tool path-utils real_path "/$nopath" >actual && + test_cmp expect actual ' test_expect_success 'real path works on absolute paths 2' ' - nopath="hopefully-absent-path" && # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-tool path-utils real_path "$d")" && - test "$d/$nopath" = "$(test-tool path-utils real_path "$d/$nopath")" + echo "$d" >expect && + test-tool path-utils real_path "$d" >actual && + test_cmp expect actual && + + nopath="hopefully-absent-path" && + echo "$d/$nopath" >expect && + test-tool path-utils real_path "$d/$nopath" >actual && + test_cmp expect actual ' test_expect_success POSIX 'real path removes extra leading slashes' ' + echo "/" >expect && + test-tool path-utils real_path "///" >actual && + test_cmp expect actual && + nopath="hopefully-absent-path" && - test "/" = "$(test-tool path-utils real_path "///")" && - test "/$nopath" = "$(test-tool path-utils real_path "///$nopath")" && + echo "/$nopath" >expect && + test-tool path-utils real_path "///$nopath" >actual && + test_cmp expect actual && + # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-tool path-utils real_path "//$d")" && - test "$d/$nopath" = "$(test-tool path-utils real_path "//$d/$nopath")" + echo "$d" >expect && + test-tool path-utils real_path "//$d" >actual && + test_cmp expect actual && + + echo "$d/$nopath" >expect && + test-tool path-utils real_path "//$d/$nopath" >actual && + test_cmp expect actual ' test_expect_success 'real path removes other extra slashes' ' - nopath="hopefully-absent-path" && # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-tool path-utils real_path "$d///")" && - test "$d/$nopath" = "$(test-tool path-utils real_path "$d///$nopath")" + echo "$d" >expect && + test-tool path-utils real_path "$d///" >actual && + test_cmp expect actual && + + nopath="hopefully-absent-path" && + echo "$d/$nopath" >expect && + test-tool path-utils real_path "$d///$nopath" >actual && + test_cmp expect actual ' test_expect_success SYMLINKS 'real path works on symlinks' ' @@ -227,19 +264,29 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' mkdir third && dir="$(cd .git && pwd -P)" && dir2=third/../second/other/.git && - test "$dir" = "$(test-tool path-utils real_path $dir2)" && + echo "$dir" >expect && + test-tool path-utils real_path $dir2 >actual && + test_cmp expect actual && file="$dir"/index && - test "$file" = "$(test-tool path-utils real_path $dir2/index)" && + echo "$file" >expect && + test-tool path-utils real_path $dir2/index >actual && + test_cmp expect actual && basename=blub && - test "$dir/$basename" = "$(cd .git && test-tool path-utils real_path "$basename")" && + echo "$dir/$basename" >expect && + test-tool -C .git path-utils real_path "$basename" >actual && + test_cmp expect actual && ln -s ../first/file .git/syml && sym="$(cd first && pwd -P)"/file && - test "$sym" = "$(test-tool path-utils real_path "$dir2/syml")" + echo "$sym" >expect && + test-tool path-utils real_path "$dir2/syml" >actual && + test_cmp expect actual ' test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' ' ln -s target symlink && - test "$(test-tool path-utils prefix_path prefix "$(pwd)/symlink")" = "symlink" + echo "symlink" >expect && + test-tool path-utils prefix_path prefix "$(pwd)/symlink" >actual && + test_cmp expect actual ' test_expect_success 'prefix_path works with only absolute path to work tree' ' @@ -255,7 +302,10 @@ test_expect_success 'prefix_path rejects absolute path to dir with same beginnin test_expect_success SYMLINKS 'prefix_path works with absolute path to a symlink to work tree having same beginning as work tree' ' git init repo && ln -s repo repolink && - test "a" = "$(cd repo && test-tool path-utils prefix_path prefix "$(pwd)/../repolink/a")" + echo "a" >expect && + repo_path="$(cd repo && pwd)" && + test-tool -C repo path-utils prefix_path prefix "$repo_path/../repolink/a" >actual && + test_cmp expect actual ' relative_path /foo/a/b/c/ /foo/a/b/ c/ diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh index a16cc3d298..70a3223f21 100755 --- a/t/t0100-previous.sh +++ b/t/t0100-previous.sh @@ -12,7 +12,9 @@ test_expect_success 'branch -d @{-1}' ' test_commit A && git checkout -b junk && git checkout - && - test "$(git symbolic-ref HEAD)" = refs/heads/main && + echo refs/heads/main >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && git branch -d @{-1} && test_must_fail git rev-parse --verify refs/heads/junk ' @@ -21,7 +23,9 @@ test_expect_success 'branch -d @{-12} when there is not enough switches yet' ' git reflog expire --expire=now && git checkout -b junk2 && git checkout - && - test "$(git symbolic-ref HEAD)" = refs/heads/main && + echo refs/heads/main >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && test_must_fail git branch -d @{-12} && git rev-parse --verify refs/heads/main ' diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index 12e30d77d0..26be4a2b5a 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh @@ -41,7 +41,8 @@ test_expect_success 'reset should remove remnants from a failed merge' ' git ls-files -s && read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >actual && - ! test -f old + ! test -f old && + test_cmp expect actual ' test_expect_success 'two-way reset should remove remnants too' ' @@ -56,7 +57,8 @@ test_expect_success 'two-way reset should remove remnants too' ' git ls-files -s && read_tree_u_must_succeed --reset -u HEAD HEAD && git ls-files -s >actual && - ! test -f old + ! test -f old && + test_cmp expect actual ' test_expect_success 'Porcelain reset should remove remnants too' ' @@ -71,7 +73,8 @@ test_expect_success 'Porcelain reset should remove remnants too' ' git ls-files -s && git reset --hard && git ls-files -s >actual && - ! test -f old + ! test -f old && + test_cmp expect actual ' test_expect_success 'Porcelain checkout -f should remove remnants too' ' @@ -86,7 +89,8 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' ' git ls-files -s && git checkout -f && git ls-files -s >actual && - ! test -f old + ! test -f old && + test_cmp expect actual ' test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' ' @@ -101,7 +105,8 @@ test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' ' git ls-files -s && git checkout -f HEAD && git ls-files -s >actual && - ! test -f old + ! test -f old && + test_cmp expect actual ' test_done diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 2d875b17d8..8eac74b59c 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -603,7 +603,8 @@ do fatal: Not a valid object name $(test_oid deadbeef_short) EOF test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef_short) >out 2>err.actual && - test_must_be_empty out + test_must_be_empty out && + test_cmp expect.err err.actual ' test_expect_success "cat-file $arg1 $arg2 error on missing full OID" ' diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh index 3c08194526..22875ba598 100755 --- a/t/t1010-mktree.sh +++ b/t/t1010-mktree.sh @@ -60,11 +60,11 @@ test_expect_success 'allow missing object with --missing' ' ' test_expect_success 'mktree refuses to read ls-tree -r output (1)' ' - test_must_fail git mktree <all >actual + test_must_fail git mktree <all ' test_expect_success 'mktree refuses to read ls-tree -r output (2)' ' - test_must_fail git mktree <all.withsub >actual + test_must_fail git mktree <all.withsub ' test_done diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 70389fa2eb..179474fa65 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -37,7 +37,7 @@ test_expect_success 'gitdir selection on normal repos' ' test_expect_success 'gitdir selection on unsupported repo' ' # Make sure it would stop at test2, not trash - test_expect_code 1 git -C test2 config core.repositoryformatversion >actual + test_expect_code 1 git -C test2 config core.repositoryformatversion ' test_expect_success 'gitdir not required mode' ' diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index cf58cf025c..4d66cd7f4a 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -1568,6 +1568,7 @@ test_expect_success 'transaction can create and delete' ' EOF git update-ref --stdin <stdin >actual && printf "%s: ok\n" start commit start commit >expect && + test_cmp expect actual && test_must_fail git show-ref --verify refs/heads/create-and-delete ' @@ -1595,6 +1596,8 @@ test_expect_success 'transaction cannot restart ongoing transaction' ' commit EOF test_must_fail git update-ref --stdin <stdin >actual && + printf "%s: ok\n" start >expect && + test_cmp expect actual && test_must_fail git show-ref --verify refs/heads/restart ' diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index be23be30c7..c7745e1bf6 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -33,7 +33,8 @@ test_expect_success 'symbolic-ref refuses non-ref for HEAD' ' reset_to_sane test_expect_success 'symbolic-ref refuses bare sha1' ' - test_must_fail git symbolic-ref HEAD $(git rev-parse HEAD) + rev=$(git rev-parse HEAD) && + test_must_fail git symbolic-ref HEAD "$rev" ' reset_to_sane diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index b5606d93b5..937ae0d733 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -551,7 +551,6 @@ test_expect_success REFFILES 'no bogus intermediate values during delete' ' git update-ref $prefix/foo $C && git pack-refs --all && git update-ref $prefix/foo $D && - git for-each-ref $prefix >unchanged && # Now try to update the reference, but hold the `packed-refs` lock # for a while to see what happens while the process is blocked: : >.git/packed-refs.lock && diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh index 0fafcf9dde..c1679e31d8 100755 --- a/t/t1504-ceiling-dirs.sh +++ b/t/t1504-ceiling-dirs.sh @@ -6,8 +6,12 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_prefix() { - test_expect_success "$1" \ - "test '$2' = \"\$(git rev-parse --show-prefix)\"" + local expect="$2" && + test_expect_success "$1: git rev-parse --show-prefix is '$2'" ' + echo "$expect" >expect && + git rev-parse --show-prefix >actual && + test_cmp expect actual + ' } test_fail() { diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index c34714ffe3..d94c72c672 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -183,6 +183,11 @@ test_expect_success '@{u} error message when no upstream' ' test_cmp expect actual ' +test_expect_success '@{u} silent error when no upstream' ' + test_must_fail git rev-parse --verify --quiet @{u} 2>actual && + test_must_be_empty actual +' + test_expect_success 'branch@{u} error message with misspelt branch' ' cat >expect <<-EOF && fatal: no such branch: ${SQ}no-such-branch${SQ} @@ -258,7 +263,8 @@ test_expect_success '@{reflog}-parsing does not look beyond colon' ' git add @{yesterday} && git commit -m "funny reflog file" && git hash-object @{yesterday} >expect && - git rev-parse HEAD:@{yesterday} >actual + git rev-parse HEAD:@{yesterday} >actual && + test_cmp expect actual ' test_expect_success '@{upstream}-parsing does not look beyond colon' ' @@ -266,7 +272,8 @@ test_expect_success '@{upstream}-parsing does not look beyond colon' ' git add @{upstream} && git commit -m "funny upstream file" && git hash-object @{upstream} >expect && - git rev-parse HEAD:@{upstream} >actual + git rev-parse HEAD:@{upstream} >actual && + test_cmp expect actual ' test_done diff --git a/t/t2005-checkout-index-symlinks.sh b/t/t2005-checkout-index-symlinks.sh index 112682a45a..67d18cfa10 100755 --- a/t/t2005-checkout-index-symlinks.sh +++ b/t/t2005-checkout-index-symlinks.sh @@ -22,8 +22,10 @@ test_expect_success \ git checkout-index symlink && test -f symlink' -test_expect_success \ -'the file must be the blob we added during the setup' ' -test "$(git hash-object -t blob symlink)" = $l' +test_expect_success 'the file must be the blob we added during the setup' ' + echo "$l" >expect && + git hash-object -t blob symlink >actual && + test_cmp expect actual +' test_done diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh index 5a7caf958c..e247a4735b 100755 --- a/t/t2060-switch.sh +++ b/t/t2060-switch.sh @@ -146,4 +146,33 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' ' test_cmp_config "" --default "" branch.main2.merge ' +test_expect_success 'switch back when temporarily detached and checked out elsewhere ' ' + test_when_finished " + git worktree remove wt1 ||: + git worktree remove wt2 ||: + git checkout - ||: + git branch -D shared ||: + " && + git checkout -b shared && + test_commit shared-first && + HASH1=$(git rev-parse --verify HEAD) && + test_commit shared-second && + test_commit shared-third && + HASH2=$(git rev-parse --verify HEAD) && + git worktree add wt1 -f shared && + git -C wt1 bisect start && + git -C wt1 bisect good $HASH1 && + git -C wt1 bisect bad $HASH2 && + git worktree add wt2 -f shared && + git -C wt2 bisect start && + git -C wt2 bisect good $HASH1 && + git -C wt2 bisect bad $HASH2 && + # we test in both worktrees to ensure that works + # as expected with "first" and "next" worktrees + test_must_fail git -C wt1 switch shared && + git -C wt1 switch --ignore-other-worktrees shared && + test_must_fail git -C wt2 switch shared && + git -C wt2 switch --ignore-other-worktrees shared +' + test_done diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh index 7c43ddf1d9..c5d19dd973 100755 --- a/t/t2070-restore.sh +++ b/t/t2070-restore.sh @@ -137,4 +137,20 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' ' test_must_fail git rev-parse HEAD:new1 ' +test_expect_success 'restore with merge options rejects --staged' ' + for opts in \ + "--staged --ours" \ + "--staged --theirs" \ + "--staged --merge" \ + "--staged --conflict=diff3" \ + "--staged --worktree --ours" \ + "--staged --worktree --theirs" \ + "--staged --worktree --merge" \ + "--staged --worktree --conflict=zdiff3" + do + test_must_fail git restore $opts . 2>err && + grep "cannot be used with --staged" err || return + done +' + test_done diff --git a/t/t3013-ls-files-format.sh b/t/t3013-ls-files-format.sh index efb7450bf1..ef6fb53f7f 100755 --- a/t/t3013-ls-files-format.sh +++ b/t/t3013-ls-files-format.sh @@ -54,6 +54,22 @@ test_expect_success 'git ls-files --format path v.s. -s' ' test_cmp expect actual ' +test_expect_success 'git ls-files --format with relative path' ' + cat >expect <<-\EOF && + ../o1.txt + ../o2.txt + ../o3.txt + ../o4.txt + ../o5.txt + ../o6.txt + EOF + mkdir sub && + cd sub && + git ls-files --format="%(path)" ":/" >../actual && + cd .. && + test_cmp expect actual +' + test_expect_success 'git ls-files --format with -m' ' echo change >o1.txt && cat >expect <<-\EOF && diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index 5d871fde96..4dd42df38c 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -431,4 +431,15 @@ match 1 1 1 1 'a' '[B-a]' match 0 1 0 1 'z' '[Z-y]' match 1 1 1 1 'Z' '[Z-y]' +test_expect_success 'matching does not exhibit exponential behavior' ' + { + test-tool wildmatch wildmatch \ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab \ + "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a" & + pid=$! + } && + sleep 2 && + ! kill $! +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5a169b68d6..5a8a48287c 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -245,9 +245,13 @@ test_expect_success 'git branch -M baz bam should succeed within a worktree in w ( cd bazdir && git branch -M baz bam && - test $(git rev-parse --abbrev-ref HEAD) = bam + echo bam >expect && + git rev-parse --abbrev-ref HEAD >actual && + test_cmp expect actual ) && - test $(git rev-parse --abbrev-ref HEAD) = bam && + echo bam >expect && + git rev-parse --abbrev-ref HEAD >actual && + test_cmp expect actual && rm -r bazdir && git worktree prune ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index d34d77f893..1c0f7ea24e 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -337,6 +337,20 @@ test_expect_success 'git branch --format option' ' test_cmp expect actual ' +test_expect_success 'git branch --format with ahead-behind' ' + cat >expect <<-\EOF && + (HEAD detached from fromtag) 0 0 + refs/heads/ambiguous 0 0 + refs/heads/branch-one 1 0 + refs/heads/branch-two 0 0 + refs/heads/main 1 0 + refs/heads/ref-to-branch 1 0 + refs/heads/ref-to-remote 1 0 + EOF + git branch --format="%(refname) %(ahead-behind:HEAD)" >actual && + test_cmp expect actual +' + test_expect_success 'git branch with --format=%(rest) must fail' ' test_must_fail git branch --format="%(rest)" >actual ' diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index d5a8ee39fc..3ce918fdb8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -388,6 +388,20 @@ test_expect_success 'switch to branch checked out here' ' git rebase main main ' +test_expect_success 'switch to branch checked out elsewhere fails' ' + test_when_finished " + git worktree remove wt1 && + git worktree remove wt2 && + git branch -d shared + " && + git worktree add wt1 -b shared && + git worktree add wt2 -f shared && + # we test in both worktrees to ensure that works + # as expected with "first" and "next" worktrees + test_must_fail git -C wt1 rebase shared shared && + test_must_fail git -C wt2 rebase shared shared +' + test_expect_success 'switch to branch not checked out' ' git checkout main && git branch other && diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh index 4711b37a28..2eba00bdf5 100755 --- a/t/t3422-rebase-incompatible-options.sh +++ b/t/t3422-rebase-incompatible-options.sh @@ -85,6 +85,11 @@ test_rebase_am_only () { test_must_fail git rebase $opt --reapply-cherry-picks A " + test_expect_success "$opt incompatible with --rebase-merges" " + git checkout B^0 && + test_must_fail git rebase $opt --rebase-merges A + " + test_expect_success "$opt incompatible with --update-refs" " git checkout B^0 && test_must_fail git rebase $opt --update-refs A @@ -101,6 +106,12 @@ test_rebase_am_only () { grep -e --no-autosquash err " + test_expect_success "$opt incompatible with rebase.rebaseMerges" " + git checkout B^0 && + test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err && + grep -e --no-rebase-merges err + " + test_expect_success "$opt incompatible with rebase.updateRefs" " git checkout B^0 && test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err && @@ -113,6 +124,12 @@ test_rebase_am_only () { git -c rebase.autosquash=true rebase --no-autosquash $opt A " + test_expect_success "$opt okay with overridden rebase.rebaseMerges" " + test_when_finished \"git reset --hard B^0\" && + git checkout B^0 && + git -c rebase.rebaseMerges=true rebase --no-rebase-merges $opt A + " + test_expect_success "$opt okay with overridden rebase.updateRefs" " test_when_finished \"git reset --hard B^0\" && git checkout B^0 && diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index fa2a06c19f..f03599c63b 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -250,6 +250,16 @@ test_expect_success 'with a branch tip that was cherry-picked already' ' EOF ' +test_expect_success '--no-rebase-merges countermands --rebase-merges' ' + git checkout -b no-rebase-merges E && + git rebase --rebase-merges --no-rebase-merges C && + test_cmp_graph C.. <<-\EOF + * B + * D + o C + EOF +' + test_expect_success 'do not rebase cousins unless asked for' ' git checkout -b cousins main && before="$(git rev-parse --verify HEAD)" && @@ -268,6 +278,40 @@ test_expect_success 'do not rebase cousins unless asked for' ' EOF ' +test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' ' + test_config rebase.rebaseMerges rebase-cousins && + git checkout -b config-rebase-cousins main && + git rebase HEAD^ && + test_cmp_graph HEAD^.. <<-\EOF + * Merge the topic branch '\''onebranch'\'' + |\ + | * D + | * G + |/ + o H + EOF +' + +test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' ' + test_config rebase.rebaseMerges no-rebase-cousins && + git checkout -b override-config-no-rebase-cousins E && + git rebase --no-rebase-merges C && + test_cmp_graph C.. <<-\EOF + * B + * D + o C + EOF +' + +test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' ' + test_config rebase.rebaseMerges rebase-cousins && + git checkout -b override-config-rebase-cousins E && + before="$(git rev-parse --verify HEAD)" && + test_tick && + git rebase --rebase-merges C && + test_cmp_rev HEAD $before +' + test_expect_success 'refs/rewritten/* is worktree-local' ' git worktree add wt && cat >wt/script-from-scratch <<-\EOF && diff --git a/t/t3436-rebase-more-options.sh b/t/t3436-rebase-more-options.sh index 94671d3c46..c3184c9ade 100755 --- a/t/t3436-rebase-more-options.sh +++ b/t/t3436-rebase-more-options.sh @@ -40,6 +40,24 @@ test_expect_success 'setup' ' EOF ' +test_expect_success 'bad -X <strategy-option> arguments: unclosed quote' ' + cat >expect <<-\EOF && + fatal: could not split '\''--bad'\'': unclosed quote + EOF + test_expect_code 128 git rebase -X"bad argument\"" side main >out 2>actual && + test_must_be_empty out && + test_cmp expect actual +' + +test_expect_success 'bad -X <strategy-option> arguments: bad escape' ' + cat >expect <<-\EOF && + fatal: could not split '\''--bad'\'': cmdline ends with \ + EOF + test_expect_code 128 git rebase -X"bad escape \\" side main >out 2>actual && + test_must_be_empty out && + test_cmp expect actual +' + test_expect_success '--ignore-whitespace works with apply backend' ' test_must_fail git rebase --apply main side && git rebase --abort && diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 51afbd7b24..82dd768944 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -106,24 +106,32 @@ test_expect_success '.gitignore test setup' ' test_expect_success '.gitignore is honored' ' git add . && - ! (git ls-files | grep "\\.ig") + git ls-files >files && + sed -n "/\\.ig/p" <files >actual && + test_must_be_empty actual ' test_expect_success 'error out when attempting to add ignored ones without -f' ' test_must_fail git add a.?? && - ! (git ls-files | grep "\\.ig") + git ls-files >files && + sed -n "/\\.ig/p" <files >actual && + test_must_be_empty actual ' test_expect_success 'error out when attempting to add ignored ones without -f' ' test_must_fail git add d.?? && - ! (git ls-files | grep "\\.ig") + git ls-files >files && + sed -n "/\\.ig/p" <files >actual && + test_must_be_empty actual ' test_expect_success 'error out when attempting to add ignored ones but add others' ' touch a.if && test_must_fail git add a.?? && - ! (git ls-files | grep "\\.ig") && - (git ls-files | grep a.if) + git ls-files >files && + sed -n "/\\.ig/p" <files >actual && + test_must_be_empty actual && + grep a.if files ' test_expect_success 'add ignored ones with -f' ' diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 3a99837d9b..3982b6b49d 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -7,12 +7,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh -if test_have_prereq !PERL -then - skip_all='skipping add -i (scripted) tests, perl not available' - test_done -fi - diff_cmp () { for x do @@ -311,9 +305,11 @@ test_expect_success FILEMODE 'stage mode and hunk' ' echo content >>file && chmod +x file && printf "y\\ny\\n" | git add -p && - git diff --cached file | grep "new mode" && - git diff --cached file | grep "+content" && - test -z "$(git diff file)" + git diff --cached file >out && + grep "new mode" out && + grep "+content" out && + git diff file >out && + test_must_be_empty out ' # end of tests disabled when filemode is not usable @@ -1075,4 +1071,25 @@ test_expect_success 'show help from add--helper' ' test_cmp expect actual ' +test_expect_success 'reset -p with unmerged files' ' + test_when_finished "git checkout --force main" && + test_commit one conflict && + git checkout -B side HEAD^ && + test_commit two conflict && + test_must_fail git merge one && + + # this is a noop with only an unmerged entry + git reset -p && + + # add files that sort before and after unmerged entry + echo a >a && + echo z >z && + git add a z && + + # confirm that we can reset those files + printf "%s\n" y y | git reset -p && + git diff-index --cached --diff-filter=u HEAD >staged && + test_must_be_empty staged +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index dfcf3a0aaa..5de1d19075 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -616,4 +616,46 @@ test_expect_success 'diff -I<regex>: detect malformed regex' ' test_i18ngrep "invalid regex given to -I: " error ' +# check_prefix <patch> <src> <dst> +# check only lines with paths to avoid dependency on exact oid/contents +check_prefix () { + grep -E '^(diff|---|\+\+\+) ' "$1" >actual.paths && + cat >expect <<-EOF && + diff --git $2 $3 + --- $2 + +++ $3 + EOF + test_cmp expect actual.paths +} + +test_expect_success 'diff-files does not respect diff.noprefix' ' + git -c diff.noprefix diff-files -p >actual && + check_prefix actual a/file0 b/file0 +' + +test_expect_success 'diff-files respects --no-prefix' ' + git diff-files -p --no-prefix >actual && + check_prefix actual file0 file0 +' + +test_expect_success 'diff respects diff.noprefix' ' + git -c diff.noprefix diff >actual && + check_prefix actual file0 file0 +' + +test_expect_success 'diff --default-prefix overrides diff.noprefix' ' + git -c diff.noprefix diff --default-prefix >actual && + check_prefix actual a/file0 b/file0 +' + +test_expect_success 'diff respects diff.mnemonicprefix' ' + git -c diff.mnemonicprefix diff >actual && + check_prefix actual i/file0 w/file0 +' + +test_expect_success 'diff --default-prefix overrides diff.mnemonicprefix' ' + git -c diff.mnemonicprefix diff --default-prefix >actual && + check_prefix actual a/file0 b/file0 +' + test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index f3313b8c58..8c3d06622a 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -59,6 +59,10 @@ test_expect_success setup ' test_tick && git commit -m "patchid 3" && + git checkout -b empty main && + test_tick && + git commit --allow-empty -m "empty commit" && + git checkout main ' @@ -128,6 +132,12 @@ test_expect_success 'replay did not screw up the log message' ' grep "^Side .* with .* backslash-n" actual ' +test_expect_success 'format-patch empty commit' ' + git format-patch --stdout main..empty >empty && + grep "^From " empty >from && + test_line_count = 1 from +' + test_expect_success 'extra headers' ' git config format.headers "To: R E Cipient <rcipient@example.com> " && @@ -2386,4 +2396,20 @@ test_expect_success 'interdiff: solo-patch' ' test_cmp expect actual ' +test_expect_success 'format-patch does not respect diff.noprefix' ' + git -c diff.noprefix format-patch -1 --stdout >actual && + grep "^--- a/blorp" actual +' + +test_expect_success 'format-patch respects format.noprefix' ' + git -c format.noprefix format-patch -1 --stdout >actual && + grep "^--- blorp" actual +' + +test_expect_success 'format-patch --default-prefix overrides format.noprefix' ' + git -c format.noprefix \ + format-patch -1 --default-prefix --stdout >actual && + grep "^--- a/blorp" actual +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 918a2fc7c6..f0bd70dbd6 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -185,6 +185,7 @@ test_expect_success 'git archive' ' ' check_tar b +check_mtime b a/a 1117231200 test_expect_success 'git archive --mtime' ' git archive --mtime=2002-02-02T02:02:02-0200 HEAD >with_mtime.tar @@ -257,14 +258,6 @@ test_expect_success 'git archive --remote with configured remote' ' test_cmp_bin b.tar b5-nick.tar ' -test_expect_success 'validate file modification time' ' - mkdir extract && - "$TAR" xf b.tar -C extract a/a && - test-tool chmtime --get extract/a/a >b.mtime && - echo "1117231200" >expected.mtime && - test_cmp expected.mtime b.mtime -' - test_expect_success 'git get-tar-commit-id' ' git get-tar-commit-id <b.tar >actual && git rev-parse HEAD >expect && diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 049c5fc8ea..b6e1211578 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -630,7 +630,7 @@ test_expect_success 'detect incorrect generation number' ' test_expect_success 'detect incorrect generation number' ' corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\01" \ - "non-zero generation number" + "commit-graph generation for commit" ' test_expect_success 'detect incorrect commit date' ' diff --git a/t/t5328-commit-graph-64bit-time.sh b/t/t5328-commit-graph-64bit-time.sh index 093f0c067a..57e4d9c699 100755 --- a/t/t5328-commit-graph-64bit-time.sh +++ b/t/t5328-commit-graph-64bit-time.sh @@ -63,4 +63,13 @@ test_expect_success 'set up and verify repo with generation data overflow chunk' graph_git_behavior 'overflow 2' repo left right +test_expect_success 'single commit with generation data exceeding UINT32_MAX' ' + git init repo-uint32-max && + cd repo-uint32-max && + test_commit --date "@4294967297 +0000" 1 && + git commit-graph write --reachable && + graph_read_expect 1 "generation_data" && + git commit-graph verify +' + test_done diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 54f422ced3..98f034aa77 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -58,6 +58,13 @@ test_expect_success 'git fetch --all' ' test_cmp expect output) ' +test_expect_success 'git fetch --all --no-write-fetch-head' ' + (cd test && + rm -f .git/FETCH_HEAD && + git fetch --all --no-write-fetch-head && + test_path_is_missing .git/FETCH_HEAD) +' + test_expect_success 'git fetch --all should continue if a remote has errors' ' (git clone one test2 && cd test2 && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 98a27a2948..19ebefa5ac 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -401,6 +401,11 @@ test_expect_success 'push with ambiguity' ' ' +test_expect_success 'push with onelevel ref' ' + mk_test testrepo heads/main && + test_must_fail git push testrepo HEAD:refs/onelevel +' + test_expect_success 'push with colon-less refspec (1)' ' mk_test testrepo heads/frotz tags/frotz && @@ -898,6 +903,13 @@ test_expect_success 'push --delete refuses empty string' ' test_must_fail git push testrepo --delete "" ' +test_expect_success 'push --delete onelevel refspecs' ' + mk_test testrepo heads/main && + git -C testrepo update-ref refs/onelevel refs/heads/main && + git push testrepo --delete refs/onelevel && + test_must_fail git -C testrepo rev-parse --verify refs/onelevel +' + test_expect_success 'warn on push to HEAD of non-bare repository' ' mk_test testrepo heads/main && ( diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 394bc60cb8..cc5496e28f 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -79,7 +79,9 @@ test_expect_success SYMLINKS 'pushing from symlinked subdir' ' git commit -m push ./file && git push ) && - test push = $(git show HEAD:subdir/file) + echo push >expect && + git show HEAD:subdir/file >actual && + test_cmp expect actual ' test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index b9546ef8e5..dcdbe26a08 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -167,6 +167,19 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' verify_fetch_result actual.err ' +test_expect_success "fetch --recurse-submodules honors --no-write-fetch-head" ' + ( + cd downstream && + git submodule foreach --recursive \ + sh -c "cd \"\$(git rev-parse --git-dir)\" && rm -f FETCH_HEAD" && + + git fetch --recurse-submodules --no-write-fetch-head && + + git submodule foreach --recursive \ + sh -c "cd \"\$(git rev-parse --git-dir)\" && ! test -f FETCH_HEAD" + ) +' + test_expect_success "submodule.recurse option triggers recursive fetch" ' add_submodule_commits && ( diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index ccf7e54b07..f45a43b4b5 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -252,15 +252,14 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "">$CHALLENGE && - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >$CHALLENGE && - printf " \r\n" >>$CHALLENGE && - printf " param2=\"value2\"\r\n" >>$CHALLENGE && - printf "WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>$CHALLENGE && - printf " p=1\r\n" >>$CHALLENGE && - printf " \r\n" >>$CHALLENGE && - printf " q=0\r\n" >>$CHALLENGE && - printf "WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>$CHALLENGE && + printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && + printf " \r\n" >>"$CHALLENGE" && + printf " param2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" && + printf " p=1\r\n" >>"$CHALLENGE" && + printf " \r\n" >>"$CHALLENGE" && + printf " q=0\r\n" >>"$CHALLENGE" && + printf "WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && @@ -298,11 +297,10 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "">$CHALLENGE && - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >$CHALLENGE && - printf " \r\n" >>$CHALLENGE && - printf "\tparam2=\"value2\"\r\n" >>$CHALLENGE && - printf "WWW-Authenticate: Basic realm=\"example.com\"" >>$CHALLENGE && + printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && + printf " \r\n" >>"$CHALLENGE" && + printf "\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 38b850c10e..1d7b1abda1 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -15,8 +15,12 @@ test_expect_success 'preparing origin repository' ' : >file && git add . && git commit -m1 && git clone --bare . a.git && git clone --bare . x && - test "$(cd a.git && git config --bool core.bare)" = true && - test "$(cd x && git config --bool core.bare)" = true && + echo true >expect && + git -C a.git config --bool core.bare >actual && + test_cmp expect actual && + echo true >expect && + git -C x config --bool core.bare >actual && + test_cmp expect actual && git bundle create b1.bundle --all && git bundle create b2.bundle main && mkdir dir && @@ -29,7 +33,9 @@ test_expect_success 'preparing origin repository' ' test_expect_success 'local clone without .git suffix' ' git clone -l -s a b && (cd b && - test "$(git config --bool core.bare)" = false && + echo false >expect && + git config --bool core.bare >actual && + test_cmp expect actual && git fetch) ' diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index e4db7513f4..71aabe30b7 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -728,6 +728,33 @@ test_expect_success 'file:// --negotiate-only with protocol v0' ' test_i18ngrep "negotiate-only requires protocol v2" err ' +test_expect_success 'push with custom path does not request v2' ' + rm -f env.trace && + git -C client push \ + --receive-pack="env >../env.trace; git-receive-pack" \ + origin HEAD:refs/heads/custom-push-test && + test_path_is_file env.trace && + ! grep ^GIT_PROTOCOL env.trace +' + +test_expect_success 'fetch with custom path does request v2' ' + rm -f env.trace && + git -C client fetch \ + --upload-pack="env >../env.trace; git-upload-pack" \ + origin HEAD && + grep ^GIT_PROTOCOL=version=2 env.trace +' + +test_expect_success 'archive with custom path does not request v2' ' + rm -f env.trace && + git -C client archive \ + --exec="env >../env.trace; git-upload-archive" \ + --remote=origin \ + HEAD >/dev/null && + test_path_is_file env.trace && + ! grep ^GIT_PROTOCOL env.trace +' + # Test protocol v2 with 'http://' transport # . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh index 7d40994991..dface8bcfe 100755 --- a/t/t6020-bundle-misc.sh +++ b/t/t6020-bundle-misc.sh @@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-bundle.sh +. "$TEST_DIRECTORY"/lib-terminal.sh for cmd in create verify list-heads unbundle do @@ -606,4 +607,42 @@ test_expect_success 'verify catches unreachable, broken prerequisites' ' ) ' +test_expect_success 'bundle progress includes write phase' ' + GIT_PROGRESS_DELAY=0 \ + git bundle create --progress out.bundle --all 2>err && + grep 'Writing' err +' + +test_expect_success TTY 'create --quiet disables all bundle progress' ' + test_terminal env GIT_PROGRESS_DELAY=0 \ + git bundle create --quiet out.bundle --all 2>err && + test_must_be_empty err +' + +test_expect_success 'read bundle over stdin' ' + git bundle create some.bundle HEAD && + + git bundle verify - <some.bundle 2>err && + grep "<stdin> is okay" err && + + git bundle list-heads some.bundle >expect && + git bundle list-heads - <some.bundle >actual && + test_cmp expect actual && + + git bundle unbundle some.bundle >expect && + git bundle unbundle - <some.bundle >actual && + test_cmp expect actual +' + +test_expect_success 'send a bundle to standard output' ' + git bundle create - --all HEAD >bundle-one && + mkdir -p down && + git -C down bundle create - --all HEAD >bundle-two && + git bundle verify bundle-one && + git bundle verify bundle-two && + git ls-remote bundle-one >expect && + git ls-remote bundle-two >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 3ba4fdf615..fb01bd6abc 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -122,6 +122,29 @@ test_expect_success 'bisect start without -- takes unknown arg as pathspec' ' grep bar ".git/BISECT_NAMES" ' +test_expect_success 'bisect reset: back in a branch checked out also elsewhere' ' + echo "shared" > branch.expect && + test_bisect_reset() { + git -C $1 bisect start && + git -C $1 bisect good $HASH1 && + git -C $1 bisect bad $HASH3 && + git -C $1 bisect reset && + git -C $1 branch --show-current > branch.output && + cmp branch.expect branch.output + } && + test_when_finished " + git worktree remove wt1 && + git worktree remove wt2 && + git branch -d shared + " && + git worktree add wt1 -b shared && + git worktree add wt2 -f shared && + # we test in both worktrees to ensure that works + # as expected with "first" and "next" worktrees + test_bisect_reset wt1 && + test_bisect_reset wt2 +' + test_expect_success 'bisect reset: back in the main branch' ' git bisect reset && echo "* main" > branch.expect && diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index c466fd989f..6614469d2d 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -1464,4 +1464,54 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" sig_crlf=${sig_crlf%dummy} test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" +test_expect_success 'git for-each-ref --stdin: empty' ' + >in && + git for-each-ref --format="%(refname)" --stdin <in >actual && + git for-each-ref --format="%(refname)" >expect && + test_cmp expect actual +' + +test_expect_success 'git for-each-ref --stdin: fails if extra args' ' + >in && + test_must_fail git for-each-ref --format="%(refname)" \ + --stdin refs/heads/extra <in 2>err && + grep "unknown arguments supplied with --stdin" err +' + +test_expect_success 'git for-each-ref --stdin: matches' ' + cat >in <<-EOF && + refs/tags/multi* + refs/heads/amb* + EOF + + cat >expect <<-EOF && + refs/heads/ambiguous + refs/tags/multi-ref1-100000-user1 + refs/tags/multi-ref1-100000-user2 + refs/tags/multi-ref1-200000-user1 + refs/tags/multi-ref1-200000-user2 + refs/tags/multi-ref2-100000-user1 + refs/tags/multi-ref2-100000-user2 + refs/tags/multi-ref2-200000-user1 + refs/tags/multi-ref2-200000-user2 + refs/tags/multiline + EOF + + git for-each-ref --format="%(refname)" --stdin <in >actual && + test_cmp expect actual +' + +test_expect_success 'git for-each-ref with non-existing refs' ' + cat >in <<-EOF && + refs/heads/this-ref-does-not-exist + refs/tags/bogus + EOF + + git for-each-ref --format="%(refname)" --stdin <in >actual && + test_must_be_empty actual && + + xargs git for-each-ref --format="%(refname)" <in >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh index bfda1f46ad..2667dd13fe 100755 --- a/t/t6301-for-each-ref-errors.sh +++ b/t/t6301-for-each-ref-errors.sh @@ -54,4 +54,18 @@ test_expect_success 'Missing objects are reported correctly' ' test_must_be_empty brief-err ' +test_expect_success 'ahead-behind requires an argument' ' + test_must_fail git for-each-ref \ + --format="%(ahead-behind)" 2>err && + echo "fatal: expected format: %(ahead-behind:<committish>)" >expect && + test_cmp expect err +' + +test_expect_success 'missing ahead-behind base' ' + test_must_fail git for-each-ref \ + --format="%(ahead-behind:refs/heads/missing)" 2>err && + echo "fatal: failed to find '\''refs/heads/missing'\''" >expect && + test_cmp expect err +' + test_done diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index 338a9c46a2..b330945f49 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh @@ -443,4 +443,173 @@ test_expect_success 'get_reachable_subset:none' ' test_all_modes get_reachable_subset ' +test_expect_success 'for-each-ref ahead-behind:linear' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-1-3 + refs/heads/commit-1-5 + refs/heads/commit-1-8 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 0 8 + refs/heads/commit-1-3 0 6 + refs/heads/commit-1-5 0 4 + refs/heads/commit-1-8 0 1 + EOF + run_all_modes git for-each-ref \ + --format="%(refname) %(ahead-behind:commit-1-9)" --stdin +' + +test_expect_success 'for-each-ref ahead-behind:all' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-2-4 + refs/heads/commit-4-2 + refs/heads/commit-4-4 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 0 24 + refs/heads/commit-2-4 0 17 + refs/heads/commit-4-2 0 17 + refs/heads/commit-4-4 0 9 + EOF + run_all_modes git for-each-ref \ + --format="%(refname) %(ahead-behind:commit-5-5)" --stdin +' + +test_expect_success 'for-each-ref ahead-behind:some' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-5-3 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 0 53 + refs/heads/commit-4-8 8 30 + refs/heads/commit-5-3 0 39 + refs/heads/commit-9-9 27 0 + EOF + run_all_modes git for-each-ref \ + --format="%(refname) %(ahead-behind:commit-9-6)" --stdin +' + +test_expect_success 'for-each-ref ahead-behind:some, multibase' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-5-3 + refs/heads/commit-7-8 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 0 53 0 53 + refs/heads/commit-4-8 8 30 0 22 + refs/heads/commit-5-3 0 39 0 39 + refs/heads/commit-7-8 14 12 8 6 + refs/heads/commit-9-9 27 0 27 0 + EOF + run_all_modes git for-each-ref \ + --format="%(refname) %(ahead-behind:commit-9-6) %(ahead-behind:commit-6-9)" \ + --stdin +' + +test_expect_success 'for-each-ref ahead-behind:none' ' + cat >input <<-\EOF && + refs/heads/commit-7-5 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + cat >expect <<-\EOF && + refs/heads/commit-4-8 16 16 + refs/heads/commit-7-5 7 4 + refs/heads/commit-9-9 49 0 + EOF + run_all_modes git for-each-ref \ + --format="%(refname) %(ahead-behind:commit-8-4)" --stdin +' + +test_expect_success 'for-each-ref merged:linear' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-1-3 + refs/heads/commit-1-5 + refs/heads/commit-1-8 + refs/heads/commit-2-1 + refs/heads/commit-5-1 + refs/heads/commit-9-1 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-1-3 + refs/heads/commit-1-5 + refs/heads/commit-1-8 + EOF + run_all_modes git for-each-ref --merged=commit-1-9 \ + --format="%(refname)" --stdin +' + +test_expect_success 'for-each-ref merged:all' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-2-4 + refs/heads/commit-4-2 + refs/heads/commit-4-4 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-2-4 + refs/heads/commit-4-2 + refs/heads/commit-4-4 + EOF + run_all_modes git for-each-ref --merged=commit-5-5 \ + --format="%(refname)" --stdin +' + +test_expect_success 'for-each-ref ahead-behind:some' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-5-3 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-5-3 + EOF + run_all_modes git for-each-ref --merged=commit-9-6 \ + --format="%(refname)" --stdin +' + +test_expect_success 'for-each-ref merged:some, multibase' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-5-3 + refs/heads/commit-7-8 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + cat >expect <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-4-8 + refs/heads/commit-5-3 + EOF + run_all_modes git for-each-ref \ + --merged=commit-5-8 \ + --merged=commit-8-5 \ + --format="%(refname)" \ + --stdin +' + +test_expect_success 'for-each-ref merged:none' ' + cat >input <<-\EOF && + refs/heads/commit-7-5 + refs/heads/commit-4-8 + refs/heads/commit-9-9 + EOF + >expect && + run_all_modes git for-each-ref --merged=commit-8-4 \ + --format="%(refname)" --stdin +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 9aa1660651..04a4b44183 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -792,6 +792,34 @@ test_expect_success 'annotations for blobs are empty' ' test_cmp expect actual ' +# Run this before doing any signing, so the test has the same results +# regardless of the GPG prereq. +test_expect_success 'git tag --format with ahead-behind' ' + test_when_finished git reset --hard tag-one-line && + git commit --allow-empty -m "left" && + git tag -a -m left tag-left && + git reset --hard HEAD~1 && + git commit --allow-empty -m "right" && + git tag -a -m left tag-right && + + # Use " !" at the end to demonstrate whitespace + # around empty ahead-behind token for tag-blob. + cat >expect <<-EOF && + refs/tags/tag-blob ! + refs/tags/tag-left 1 1 ! + refs/tags/tag-lines 0 1 ! + refs/tags/tag-one-line 0 1 ! + refs/tags/tag-right 0 0 ! + refs/tags/tag-zero-lines 0 1 ! + EOF + git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err && + grep "refs/tags/tag" actual >actual.focus && + test_cmp expect actual.focus && + + # Error reported for tags that point to non-commits. + grep "error: object [0-9a-f]* is a blob, not a commit" err +' + # trying to verify annotated non-signed tags: test_expect_success GPG \ diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index b19792b326..2b3c363078 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -56,12 +56,15 @@ chmod a+x fake-editor.sh test_expect_success 'interactive rebase with a dirty submodule' ' - test submodule = $(git diff --name-only) && + echo submodule >expect && + git diff --name-only >actual && + test_cmp expect actual && HEAD=$(git rev-parse HEAD) && GIT_EDITOR="\"$(pwd)/fake-editor.sh\"" EDITOR_TEXT="pick $HEAD" \ git rebase -i HEAD^ && - test submodule = $(git diff --name-only) - + echo submodule >expect && + git diff --name-only >actual && + test_cmp expect actual ' test_expect_success 'rebase with dirty file and submodule fails' ' @@ -83,11 +86,19 @@ test_expect_success 'stash with a dirty submodule' ' CURRENT=$(cd submodule && git rev-parse HEAD) && git stash && test new != $(cat file) && - test submodule = $(git diff --name-only) && - test $CURRENT = $(cd submodule && git rev-parse HEAD) && + echo submodule >expect && + git diff --name-only >actual && + test_cmp expect actual && + + echo "$CURRENT" >expect && + git -C submodule rev-parse HEAD >actual && + test_cmp expect actual && + git stash apply && test new = $(cat file) && - test $CURRENT = $(cd submodule && git rev-parse HEAD) + echo "$CURRENT" >expect && + git -C submodule rev-parse HEAD >actual && + test_cmp expect actual ' diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 07ca46fb0d..d1255228d5 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -102,7 +102,9 @@ test_expect_success 'setup: commit-msg hook that always fails' ' ' commit_msg_is () { - test "$(git log --pretty=format:%s%b -1)" = "$1" + printf "%s" "$1" >expect && + git log --pretty=format:%s%b -1 >actual && + test_cmp expect actual } test_expect_success 'with failing hook' ' diff --git a/t/t7516-commit-races.sh b/t/t7516-commit-races.sh index f2ce14e907..2d38a16480 100755 --- a/t/t7516-commit-races.sh +++ b/t/t7516-commit-races.sh @@ -10,7 +10,8 @@ test_expect_success 'race to create orphan commit' ' test_must_fail env EDITOR=./hare-editor git commit --allow-empty -m tortoise -e && git show -s --pretty=format:%s >subject && grep hare subject && - test -z "$(git show -s --pretty=format:%P)" + git show -s --pretty=format:%P >out && + test_must_be_empty out ' test_expect_success 'race to create non-orphan commit' ' diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index 4c0327b2bb..0c241d6c14 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh @@ -995,4 +995,41 @@ test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' grep -E "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace ' +test_expect_success 'split-index and FSMonitor work well together' ' + git init split-index && + test_when_finished "git -C \"$PWD/split-index\" \ + fsmonitor--daemon stop" && + ( + cd split-index && + git config core.splitIndex true && + # force split-index in most cases + git config splitIndex.maxPercentChange 99 && + git config core.fsmonitor true && + + # Create the following commit topology: + # + # * merge three + # |\ + # | * three + # * | merge two + # |\| + # | * two + # * | one + # |/ + # * 5a5efd7 initial + + test_commit initial && + test_commit two && + test_commit three && + git reset --hard initial && + test_commit one && + test_tick && + git merge two && + test_tick && + git merge three && + + git rebase --force-rebase -r one + ) +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 8eded6ab27..39d6d713ec 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -1001,7 +1001,9 @@ test_expect_success 'log --committer does not search in timestamp' ' test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && - test "$(git grep test)" = "t/t:test" && + echo "t/t:test" >expect && + git grep test >actual && + test_cmp expect actual && git update-index --no-assume-unchanged t/t && git checkout t/t ' diff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh index 77047e250d..156a647484 100755 --- a/t/t9351-fast-export-anonymize.sh +++ b/t/t9351-fast-export-anonymize.sh @@ -25,6 +25,7 @@ test_expect_success 'setup simple repo' ' test_expect_success 'export anonymized stream' ' git fast-export --anonymize --all \ --anonymize-map=retain-me \ + --anonymize-map=xyzzy:should-not-appear \ --anonymize-map=xyzzy:custom-name \ --anonymize-map=other \ >stream @@ -41,6 +42,7 @@ test_expect_success 'stream omits path names' ' test_expect_success 'stream contains user-specified names' ' grep retain-me stream && + ! grep should-not-appear stream && grep custom-name stream ' diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index d459fae655..d667dda654 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -13,10 +13,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh" actual="$TRASH_DIRECTORY/actual" -c_red='\\[\\e[31m\\]' -c_green='\\[\\e[32m\\]' -c_lblue='\\[\\e[1;34m\\]' -c_clear='\\[\\e[0m\\]' +c_red='\001\e[31m\002' +c_green='\001\e[32m\002' +c_lblue='\001\e[1;34m\002' +c_clear='\001\e[0m\002' test_expect_success 'setup for prompt tests' ' git init otherrepo && diff --git a/transport.c b/transport.c index bb532d0f72..c5e201f6a3 100644 --- a/transport.c +++ b/transport.c @@ -280,8 +280,12 @@ static int connect_setup(struct transport *transport, int for_push) } data->conn = git_connect(data->fd, transport->url, - for_push ? data->options.receivepack : - data->options.uploadpack, + for_push ? + "git-receive-pack" : + "git-upload-pack", + for_push ? + data->options.receivepack : + data->options.uploadpack, flags); return 0; @@ -915,7 +919,7 @@ static int connect_git(struct transport *transport, const char *name, { struct git_transport_data *data = transport->data; data->conn = git_connect(data->fd, transport->url, - executable, 0); + name, executable, 0); fd[0] = data->fd[0]; fd[1] = data->fd[1]; return 0; diff --git a/unicode-width.h b/unicode-width.h index 97c851b27d..e15fb0455b 100644 --- a/unicode-width.h +++ b/unicode-width.h @@ -94,7 +94,7 @@ static const struct interval zero_width[] = { { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EBC }, -{ 0x0EC8, 0x0ECD }, +{ 0x0EC8, 0x0ECE }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, @@ -228,6 +228,7 @@ static const struct interval zero_width[] = { { 0x10AE5, 0x10AE6 }, { 0x10D24, 0x10D27 }, { 0x10EAB, 0x10EAC }, +{ 0x10EFD, 0x10EFF }, { 0x10F46, 0x10F50 }, { 0x10F82, 0x10F85 }, { 0x11001, 0x11001 }, @@ -252,6 +253,7 @@ static const struct interval zero_width[] = { { 0x11234, 0x11234 }, { 0x11236, 0x11237 }, { 0x1123E, 0x1123E }, +{ 0x11241, 0x11241 }, { 0x112DF, 0x112DF }, { 0x112E3, 0x112EA }, { 0x11300, 0x11301 }, @@ -313,7 +315,12 @@ static const struct interval zero_width[] = { { 0x11D95, 0x11D95 }, { 0x11D97, 0x11D97 }, { 0x11EF3, 0x11EF4 }, -{ 0x13430, 0x13438 }, +{ 0x11F00, 0x11F01 }, +{ 0x11F36, 0x11F3A }, +{ 0x11F40, 0x11F40 }, +{ 0x11F42, 0x11F42 }, +{ 0x13430, 0x13440 }, +{ 0x13447, 0x13455 }, { 0x16AF0, 0x16AF4 }, { 0x16B30, 0x16B36 }, { 0x16F4F, 0x16F4F }, @@ -339,9 +346,11 @@ static const struct interval zero_width[] = { { 0x1E01B, 0x1E021 }, { 0x1E023, 0x1E024 }, { 0x1E026, 0x1E02A }, +{ 0x1E08F, 0x1E08F }, { 0x1E130, 0x1E136 }, { 0x1E2AE, 0x1E2AE }, { 0x1E2EC, 0x1E2EF }, +{ 0x1E4EC, 0x1E4EF }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A }, { 0xE0001, 0xE0001 }, @@ -417,7 +426,9 @@ static const struct interval double_width[] = { { 0x1AFF5, 0x1AFFB }, { 0x1AFFD, 0x1AFFE }, { 0x1B000, 0x1B122 }, +{ 0x1B132, 0x1B132 }, { 0x1B150, 0x1B152 }, +{ 0x1B155, 0x1B155 }, { 0x1B164, 0x1B167 }, { 0x1B170, 0x1B2FB }, { 0x1F004, 0x1F004 }, @@ -451,7 +462,7 @@ static const struct interval double_width[] = { { 0x1F6CC, 0x1F6CC }, { 0x1F6D0, 0x1F6D2 }, { 0x1F6D5, 0x1F6D7 }, -{ 0x1F6DD, 0x1F6DF }, +{ 0x1F6DC, 0x1F6DF }, { 0x1F6EB, 0x1F6EC }, { 0x1F6F4, 0x1F6FC }, { 0x1F7E0, 0x1F7EB }, @@ -459,15 +470,13 @@ static const struct interval double_width[] = { { 0x1F90C, 0x1F93A }, { 0x1F93C, 0x1F945 }, { 0x1F947, 0x1F9FF }, -{ 0x1FA70, 0x1FA74 }, -{ 0x1FA78, 0x1FA7C }, -{ 0x1FA80, 0x1FA86 }, -{ 0x1FA90, 0x1FAAC }, -{ 0x1FAB0, 0x1FABA }, -{ 0x1FAC0, 0x1FAC5 }, -{ 0x1FAD0, 0x1FAD9 }, -{ 0x1FAE0, 0x1FAE7 }, -{ 0x1FAF0, 0x1FAF6 }, +{ 0x1FA70, 0x1FA7C }, +{ 0x1FA80, 0x1FA88 }, +{ 0x1FA90, 0x1FABD }, +{ 0x1FABF, 0x1FAC5 }, +{ 0x1FACE, 0x1FADB }, +{ 0x1FAE0, 0x1FAE8 }, +{ 0x1FAF0, 0x1FAF8 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD } }; diff --git a/unpack-trees.c b/unpack-trees.c index a75fb9f05b..09d96f8ba1 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1926,6 +1926,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options * avoid having to create a new one. */ o->internal.result.split_index = o->src_index->split_index; + if (o->src_index->cache_changed & SPLIT_INDEX_ORDERED) + o->internal.result.cache_changed |= SPLIT_INDEX_ORDERED; o->internal.result.split_index->refcount++; } else { o->internal.result.split_index = diff --git a/wildmatch.c b/wildmatch.c index 42e38e3459..8ea29141bd 100644 --- a/wildmatch.c +++ b/wildmatch.c @@ -14,6 +14,10 @@ typedef unsigned char uchar; +/* Internal return values */ +#define WM_ABORT_ALL -1 +#define WM_ABORT_TO_STARSTAR -2 + /* What character marks an inverted character class? */ #define NEGATE_CLASS '!' #define NEGATE_CLASS2 '^' @@ -83,12 +87,12 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) continue; case '*': if (*++p == '*') { - const uchar *prev_p = p - 2; + const uchar *prev_p = p; while (*++p == '*') {} if (!(flags & WM_PATHNAME)) /* without WM_PATHNAME, '*' == '**' */ match_slash = 1; - else if ((prev_p < pattern || *prev_p == '/') && + else if ((prev_p - pattern < 2 || *(prev_p - 2) == '/') && (*p == '\0' || *p == '/' || (p[0] == '\\' && p[1] == '/'))) { /* @@ -114,7 +118,7 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) * only if there are no more slash characters. */ if (!match_slash) { if (strchr((char *)text, '/')) - return WM_NOMATCH; + return WM_ABORT_TO_STARSTAR; } return WM_MATCH; } else if (!match_slash && *p == '/') { @@ -125,7 +129,7 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) */ const char *slash = strchr((char*)text, '/'); if (!slash) - return WM_NOMATCH; + return WM_ABORT_ALL; text = (const uchar*)slash; /* the slash is consumed by the top-level for loop */ break; @@ -153,8 +157,12 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) break; text++; } - if (t_ch != p_ch) - return WM_NOMATCH; + if (t_ch != p_ch) { + if (match_slash) + return WM_ABORT_ALL; + else + return WM_ABORT_TO_STARSTAR; + } } if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { if (!match_slash || matched != WM_ABORT_TO_STARSTAR) @@ -274,5 +282,6 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) /* Match the "pattern" against the "text" string. */ int wildmatch(const char *pattern, const char *text, unsigned int flags) { - return dowild((const uchar*)pattern, (const uchar*)text, flags); + int res = dowild((const uchar*)pattern, (const uchar*)text, flags); + return res == WM_MATCH ? WM_MATCH : WM_NOMATCH; } diff --git a/wildmatch.h b/wildmatch.h index 5993696298..0c890cb56b 100644 --- a/wildmatch.h +++ b/wildmatch.h @@ -6,8 +6,6 @@ #define WM_NOMATCH 1 #define WM_MATCH 0 -#define WM_ABORT_ALL -1 -#define WM_ABORT_TO_STARSTAR -2 int wildmatch(const char *pattern, const char *text, unsigned int flags); #endif diff --git a/worktree.c b/worktree.c index c99939a4d1..e10594ef33 100644 --- a/worktree.c +++ b/worktree.c @@ -404,44 +404,43 @@ int is_worktree_being_bisected(const struct worktree *wt, * bisect). New commands that do similar things should update this * function as well. */ -const struct worktree *find_shared_symref(struct worktree **worktrees, - const char *symref, - const char *target) +int is_shared_symref(const struct worktree *wt, const char *symref, + const char *target) { - const struct worktree *existing = NULL; - int i = 0; + const char *symref_target; + struct ref_store *refs; + int flags; - for (i = 0; worktrees[i]; i++) { - struct worktree *wt = worktrees[i]; - const char *symref_target; - struct ref_store *refs; - int flags; + if (wt->is_bare) + return 0; - if (wt->is_bare) - continue; + if (wt->is_detached && !strcmp(symref, "HEAD")) { + if (is_worktree_being_rebased(wt, target)) + return 1; + if (is_worktree_being_bisected(wt, target)) + return 1; + } - if (wt->is_detached && !strcmp(symref, "HEAD")) { - if (is_worktree_being_rebased(wt, target)) { - existing = wt; - break; - } - if (is_worktree_being_bisected(wt, target)) { - existing = wt; - break; - } - } + refs = get_worktree_ref_store(wt); + symref_target = refs_resolve_ref_unsafe(refs, symref, 0, + NULL, &flags); + if ((flags & REF_ISSYMREF) && + symref_target && !strcmp(symref_target, target)) + return 1; - refs = get_worktree_ref_store(wt); - symref_target = refs_resolve_ref_unsafe(refs, symref, 0, - NULL, &flags); - if ((flags & REF_ISSYMREF) && - symref_target && !strcmp(symref_target, target)) { - existing = wt; - break; - } - } + return 0; +} - return existing; +const struct worktree *find_shared_symref(struct worktree **worktrees, + const char *symref, + const char *target) +{ + + for (int i = 0; worktrees[i]; i++) + if (is_shared_symref(worktrees[i], symref, target)) + return worktrees[i]; + + return NULL; } int submodule_uses_worktrees(const char *path) diff --git a/worktree.h b/worktree.h index 2baeca2a8a..ce45b66de9 100644 --- a/worktree.h +++ b/worktree.h @@ -149,6 +149,12 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, const char *target); /* + * Returns true if a symref points to a ref in a worktree. + */ +int is_shared_symref(const struct worktree *wt, + const char *symref, const char *target); + +/* * Similar to head_ref() for all HEADs _except_ one from the current * worktree, which is covered by head_ref(). */ |
