]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'bb/iso-strict-utc'
authorJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2024 21:55:14 +0000 (14:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2024 21:55:14 +0000 (14:55 -0700)
The output format for dates "iso-strict" has been tweaked to show
a time in the Zulu timezone with "Z" suffix, instead of "+00:00".

* bb/iso-strict-utc:
  date: make "iso-strict" conforming for the UTC timezone

83 files changed:
.github/workflows/main.yml
Documentation/RelNotes/2.45.0.txt
Documentation/config/advice.txt
Documentation/config/clean.txt
Documentation/config/core.txt
Documentation/config/init.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-init.txt
Documentation/git-status.txt
Documentation/gitremote-helpers.txt
Documentation/urls.txt
Documentation/user-manual.txt
advice.c
advice.h
branch.c
builtin/branch.c
builtin/clean.c
builtin/clone.c
builtin/index-pack.c
builtin/interpret-trailers.c
builtin/rebase.c
builtin/remote.c
builtin/repack.c
builtin/unpack-objects.c
config.mak.uname
contrib/completion/git-completion.bash
dir.c
dir.h
fsmonitor.c
git.c
lockfile.h
name-hash.c
name-hash.h
parse-options.c
pretty.c
ref-filter.c
refs/reftable-backend.c
reftable/block.c
reftable/block.h
reftable/iter.c
reftable/iter.h
reftable/merged.c
reftable/merged.h
reftable/merged_test.c
reftable/pq.c
reftable/pq.h
reftable/pq_test.c
reftable/readwrite_test.c
reftable/record.c
reftable/record.h
reftable/record_test.c
reftable/reftable-record.h
reftable/stack.c
reftable/stack_test.c
reftable/system.h
revision.c
sequencer.c
sequencer.h
setup.c
sideband.c
t/t0010-racy-git.sh
t/t0035-safe-bare-repository.sh
t/t0040-parse-options.sh
t/t0211-trace2-perf.sh
t/t1301-shared-repo.sh
t/t1502-rev-parse-parseopt.sh
t/t3200-branch.sh
t/t4201-shortlog.sh
t/t5606-clone-options.sh
t/t7300-clean.sh
t/t7301-clean-interactive.sh
t/t7513-interpret-trailers.sh
t/t7527-builtin-fsmonitor.sh
t/t9902-completion.sh
t/test-lib-functions.sh
tempfile.c
tempfile.h
trace2.c
trailer.c
trailer.h
transport-helper.c
wt-status.c

index 683a2d633ed5a95f7daab4ffb8ba5cf1ed8d0634..3428773b096e9ea0da0488b9d75171f09b7ca961 100644 (file)
@@ -159,7 +159,7 @@ jobs:
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       uses: actions/upload-artifact@v4
       with:
-        name: failed-tests-windows
+        name: failed-tests-windows-${{ matrix.nr }}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
   vs-build:
     name: win+VS build
@@ -250,7 +250,7 @@ jobs:
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
       uses: actions/upload-artifact@v4
       with:
-        name: failed-tests-windows
+        name: failed-tests-windows-vs-${{ matrix.nr }}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
   regular:
     name: ${{matrix.vector.jobname}} (${{matrix.vector.pool}})
index 2e759325393add7b2ac88fb69eaf501685e567bd..16a2613156e22aaefc07c5b374f8e677bfe4fa76 100644 (file)
@@ -34,6 +34,9 @@ UI, Workflows & Features
  * "git log --merge" learned to pay attention to CHERRY_PICK_HEAD and
    other kinds of *_HEAD pseudorefs.
 
+ * Platform specific tweaks for OS/390 has been added to
+   config.mak.uname.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -54,6 +57,19 @@ Performance, Internal Implementation, Development Support etc.
    fetching of objects from the promisor remote, which may be handy
    for debugging.
 
+ * The implementation in "git clean" that makes "-n" and "-i" ignore
+   clean.requireForce has been simplified, together with the
+   documentation.
+
+ * The code to iterate over refs with the reftable backend has seen
+   some optimization.
+
+ * Uses of xwrite() helper have been audited and updated for better
+   error checking and simpler code.
+
+ * Some trace2 events that lacked def_param have learned to show it,
+   enriching the output.
+
 
 Fixes since v2.44
 -----------------
@@ -130,6 +146,40 @@ Fixes since v2.44
  * Make sure failure return from merge_bases_many() is properly caught.
    (merge 25fd20eb44 js/merge-base-with-missing-commit later to maint).
 
+ * FSMonitor client code was confused when FSEvents were given in a
+   different case on a case-insensitive filesystem, which has been
+   corrected.
+   (merge 29c139ce78 jh/fsmonitor-icase-corner-case-fix later to maint).
+
+ * The "core.commentChar" configuration variable only allows an ASCII
+   character, which was not clearly documented, which has been
+   corrected.
+   (merge fb7c556f58 kh/doc-commentchar-is-a-byte later to maint).
+
+ * With release 2.44 we got rid of all uses of test_i18ngrep and there
+   is no in-flight topic that adds a new use of it.  Make a call to
+   test_i18ngrep a hard failure, so that we can remove it at the end
+   of this release cycle.
+   (merge 381a83dfa3 jc/test-i18ngrep later to maint).
+
+ * The command line completion script (in contrib/) learned to
+   complete "git reflog" better.
+   (merge 1284f9cc11 rj/complete-reflog later to maint).
+
+ * The logic to complete the command line arguments to "git worktree"
+   subcommand (in contrib/) has been updated to correctly honor things
+   like "git -C dir" etc.
+   (merge 3574816d98 rj/complete-worktree-paths-fix later to maint).
+
+ * When git refuses to create a branch because the proposed branch
+   name is not a valid refname, an advice message is given to refer
+   the user to exact naming rules.
+   (merge 8fbd903e58 kh/branch-ref-syntax-advice later to maint).
+
+ * Code simplification by getting rid of code that sets an environment
+   variable that is no longer used.
+   (merge 72a8d3f027 pw/rebase-i-ignore-cherry-pick-help-environment later to maint).
+
  * Other code cleanup, docfix, build fix, etc.
    (merge f0e578c69c rs/use-xstrncmpz later to maint).
    (merge 83e6eb7d7a ba/credential-test-clean-fix later to maint).
@@ -146,3 +196,5 @@ Fixes since v2.44
    (merge 40b8076462 ak/rebase-autosquash later to maint).
    (merge 3223204456 eg/add-uflags later to maint).
    (merge 5f78d52dce es/config-doc-sort-sections later to maint).
+   (merge 781fb7b4c2 as/option-names-in-messages later to maint).
+   (merge 51d41dc243 jk/doc-remote-helpers-markup-fix later to maint).
index dde8e7840ef38225995549411fbcf9305b160c37..f83341165367baac4f02779eedf432822644e615 100644 (file)
@@ -2,27 +2,27 @@ advice.*::
        These variables control various optional help messages designed to
        aid new users.  When left unconfigured, Git will give the message
        alongside instructions on how to squelch it.  You can tell Git
-       that you do not need the help message by setting these to 'false':
+       that you do not need the help message by setting these to `false`:
 +
 --
        addEmbeddedRepo::
-               Advice on what to do when you've accidentally added one
+               Shown when the user accidentally adds one
                git repo inside of another.
        addEmptyPathspec::
-               Advice shown if a user runs the add command without providing
+               Shown when the user runs `git add` without providing
                the pathspec parameter.
        addIgnoredFile::
-               Advice shown if a user attempts to add an ignored file to
+               Shown when the user attempts to add an ignored file to
                the index.
        amWorkDir::
-               Advice that shows the location of the patch file when
-               linkgit:git-am[1] fails to apply it.
+               Shown when linkgit:git-am[1] fails to apply a patch
+               file, to tell the user the location of the file.
        ambiguousFetchRefspec::
-               Advice shown when a fetch refspec for multiple remotes maps to
+               Shown when a fetch refspec for multiple remotes maps to
                the same remote-tracking branch namespace and causes branch
                tracking set-up to fail.
        checkoutAmbiguousRemoteBranchName::
-               Advice shown when the argument to
+               Shown when the argument to
                linkgit:git-checkout[1] and linkgit:git-switch[1]
                ambiguously resolves to a
                remote tracking branch on more than one remote in
@@ -33,31 +33,31 @@ advice.*::
                to be used by default in some situations where this
                advice would be printed.
        commitBeforeMerge::
-               Advice shown when linkgit:git-merge[1] refuses to
+               Shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
        detachedHead::
-               Advice shown when you used
+               Shown when the user uses
                linkgit:git-switch[1] or linkgit:git-checkout[1]
-               to move to the detached HEAD state, to instruct how to
-               create a local branch after the fact.
+               to move to the detached HEAD state, to tell the user how
+               to create a local branch after the fact.
        diverging::
-               Advice shown when a fast-forward is not possible.
+               Shown when a fast-forward is not possible.
        fetchShowForcedUpdates::
-               Advice shown when linkgit:git-fetch[1] takes a long time
+               Shown when linkgit:git-fetch[1] takes a long time
                to calculate forced updates after ref updates, or to warn
                that the check is disabled.
        forceDeleteBranch::
-               Advice shown when a user tries to delete a not fully merged
+               Shown when the user tries to delete a not fully merged
                branch without the force option set.
        ignoredHook::
-               Advice shown if a hook is ignored because the hook is not
+               Shown when a hook is ignored because the hook is not
                set as executable.
        implicitIdentity::
-               Advice on how to set your identity configuration when
-               your information is guessed from the system username and
-               domain name.
+               Shown when the user's information is guessed from the
+               system username and domain name, to tell the user how to
+               set their identity configuration.
        nestedTag::
-               Advice shown if a user attempts to recursively tag a tag object.
+               Shown when a user attempts to recursively tag a tag object.
        pushAlreadyExists::
                Shown when linkgit:git-push[1] rejects an update that
                does not qualify for fast-forwarding (e.g., a tag.)
@@ -71,12 +71,12 @@ advice.*::
                object that is not a commit-ish, or make the remote
                ref point at an object that is not a commit-ish.
        pushNonFFCurrent::
-               Advice shown when linkgit:git-push[1] fails due to a
+               Shown when linkgit:git-push[1] fails due to a
                non-fast-forward update to the current branch.
        pushNonFFMatching::
-               Advice shown when you ran linkgit:git-push[1] and pushed
-               'matching refs' explicitly (i.e. you used ':', or
-               specified a refspec that isn't your current branch) and
+               Shown when the user ran linkgit:git-push[1] and pushed
+               "matching refs" explicitly (i.e. used `:`, or
+               specified a refspec that isn't the current branch) and
                it resulted in a non-fast-forward error.
        pushRefNeedsUpdate::
                Shown when linkgit:git-push[1] rejects a forced update of
@@ -87,25 +87,28 @@ advice.*::
                guess based on the source and destination refs what
                remote ref namespace the source belongs in, but where
                we can still suggest that the user push to either
-               refs/heads/* or refs/tags/* based on the type of the
+               `refs/heads/*` or `refs/tags/*` based on the type of the
                source object.
        pushUpdateRejected::
-               Set this variable to 'false' if you want to disable
-               'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
-               'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
+               Set this variable to `false` if you want to disable
+               `pushNonFFCurrent`, `pushNonFFMatching`, `pushAlreadyExists`,
+               `pushFetchFirst`, `pushNeedsForce`, and `pushRefNeedsUpdate`
                simultaneously.
+       refSyntax::
+               Shown when the user provides an illegal ref name, to
+               tell the user about the ref syntax documentation.
        resetNoRefresh::
-               Advice to consider using the `--no-refresh` option to
-               linkgit:git-reset[1] when the command takes more than 2 seconds
-               to refresh the index after reset.
+               Shown when linkgit:git-reset[1] takes more than 2
+               seconds to refresh the index after reset, to tell the user
+               that they can use the `--no-refresh` option.
        resolveConflict::
-               Advice shown by various commands when conflicts
+               Shown by various commands when conflicts
                prevent the operation from being performed.
        rmHints::
-               In case of failure in the output of linkgit:git-rm[1],
-               show directions on how to proceed from the current state.
+               Shown on failure in the output of linkgit:git-rm[1], to
+               give directions on how to proceed from the current state.
        sequencerInUse::
-               Advice shown when a sequencer command is already in progress.
+               Shown when a sequencer command is already in progress.
        skippedCherryPicks::
                Shown when linkgit:git-rebase[1] skips a commit that has already
                been cherry-picked onto the upstream branch.
@@ -123,30 +126,30 @@ advice.*::
                by linkgit:git-switch[1] or
                linkgit:git-checkout[1] when switching branches.
        statusUoption::
-               Advise to consider using the `-u` option to linkgit:git-status[1]
-               when the command takes more than 2 seconds to enumerate untracked
-               files.
+               Shown when linkgit:git-status[1] takes more than 2
+               seconds to enumerate untracked files, to tell the user that
+               they can use the `-u` option.
        submoduleAlternateErrorStrategyDie::
-               Advice shown when a submodule.alternateErrorStrategy option
+               Shown when a submodule.alternateErrorStrategy option
                configured to "die" causes a fatal error.
        submoduleMergeConflict::
                Advice shown when a non-trivial submodule merge conflict is
                encountered.
        submodulesNotUpdated::
-               Advice shown when a user runs a submodule command that fails
+               Shown when a user runs a submodule command that fails
                because `git submodule update --init` was not run.
        suggestDetachingHead::
-               Advice shown when linkgit:git-switch[1] refuses to detach HEAD
+               Shown when linkgit:git-switch[1] refuses to detach HEAD
                without the explicit `--detach` option.
        updateSparsePath::
-               Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
+               Shown when either linkgit:git-add[1] or linkgit:git-rm[1]
                is asked to update index entries outside the current sparse
                checkout.
        waitingForEditor::
-               Print a message to the terminal whenever Git is waiting for
-               editor input from the user.
+               Shown when Git is waiting for editor input. Relevant
+               when e.g. the editor is not launched inside the terminal.
        worktreeAddOrphan::
-               Advice shown when a user tries to create a worktree from an
-               invalid reference, to instruct how to create a new unborn
+               Shown when the user tries to create a worktree from an
+               invalid reference, to tell the user how to create a new unborn
                branch instead.
 --
index f05b9403b5ad903ea68d68e896acaff253bfde37..c0188ead4e6cd8b2b38a5feecb20c24897e03161 100644 (file)
@@ -1,3 +1,3 @@
 clean.requireForce::
-       A boolean to make git-clean do nothing unless given -f,
-       -i, or -n.  Defaults to true.
+       A boolean to make git-clean refuse to delete files unless -f
+       is given. Defaults to true.
index 0e8c2832bf9b8a5251c51f346ecfcf47c7e8d530..2d4bbdb25fa3104bf5151ca98be42f0b4c438b2a 100644 (file)
@@ -521,7 +521,7 @@ core.editor::
 
 core.commentChar::
        Commands such as `commit` and `tag` that let you edit
-       messages consider a line that begins with this character
+       messages consider a line that begins with this ASCII character
        commented, and removes them after the editor returns
        (default '#').
 +
index 79c79d66174ebd9f2dfb867a6a39a339529b7fc0..dd1d8332737fe89a2feca44ce59adad4f64bce5a 100644 (file)
@@ -1,7 +1,10 @@
-init.templateDir::
-       Specify the directory from which templates will be copied.
-       (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+:see-git-init:
+ifndef::git-init[]
+:see-git-init: (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].)
+endif::[]
 
+init.templateDir::
+       Specify the directory from which templates will be copied. {see-git-init}
 init.defaultBranch::
        Allows overriding the default branch name e.g. when initializing
        a new repository.
index 69331e3f05a13eed6ffb084e292f8452b241f13c..fd171654163c1fc4e34de3b24cdd4063a604dae1 100644 (file)
@@ -37,7 +37,7 @@ OPTIONS
 --force::
        If the Git configuration variable clean.requireForce is not set
        to false, 'git clean' will refuse to delete files or directories
-       unless given -f or -i.  Git will refuse to modify untracked
+       unless given -f.  Git will refuse to modify untracked
        nested git repositories (directories with a .git subdirectory)
        unless a second -f is given.
 
@@ -45,10 +45,14 @@ OPTIONS
 --interactive::
        Show what would be done and clean files interactively. See
        ``Interactive mode'' for details.
+       Configuration variable `clean.requireForce` is ignored, as
+       this mode gives its own safety protection by going interactive.
 
 -n::
 --dry-run::
        Don't actually remove anything, just show what would be done.
+       Configuration variable `clean.requireForce` is ignored, as
+       nothing will be deleted anyway.
 
 -q::
 --quiet::
index 0c07720c6f4e4a53a20a9db8c210143e881a2347..f90977a8519b4c9c057438afa042b4bd25855567 100644 (file)
@@ -102,9 +102,9 @@ its source repository, you can simply run `git repack -a` to copy all
 objects from the source repository into a pack in the cloned repository.
 
 --reference[-if-able] <repository>::
-       If the reference repository is on the local machine,
+       If the reference _<repository>_ is on the local machine,
        automatically setup `.git/objects/info/alternates` to
-       obtain objects from the reference repository.  Using
+       obtain objects from the reference _<repository>_.  Using
        an already existing repository as an alternate will
        require fewer objects to be copied from the repository
        being cloned, reducing network and local storage costs.
@@ -156,13 +156,13 @@ objects from the source repository into a pack in the cloned repository.
 
 --[no-]reject-shallow::
        Fail if the source repository is a shallow repository.
-       The 'clone.rejectShallow' configuration variable can be used to
+       The `clone.rejectShallow` configuration variable can be used to
        specify the default.
 
 --bare::
        Make a 'bare' Git repository.  That is, instead of
-       creating `<directory>` and placing the administrative
-       files in `<directory>/.git`, make the `<directory>`
+       creating _<directory>_ and placing the administrative
+       files in `<directory>/.git`, make the _<directory>_
        itself the `$GIT_DIR`. This obviously implies the `--no-checkout`
        because there is nowhere to check out the working tree.
        Also the branch heads at the remote are copied directly
@@ -180,11 +180,11 @@ objects from the source repository into a pack in the cloned repository.
 --filter=<filter-spec>::
        Use the partial clone feature and request that the server sends
        a subset of reachable objects according to a given object filter.
-       When using `--filter`, the supplied `<filter-spec>` is used for
+       When using `--filter`, the supplied _<filter-spec>_ is used for
        the partial clone filter. For example, `--filter=blob:none` will
        filter out all blobs (file contents) until needed by Git. Also,
        `--filter=blob:limit=<size>` will filter out all blobs of size
-       at least `<size>`. For more details on filter specifications, see
+       at least _<size>_. For more details on filter specifications, see
        the `--filter` option in linkgit:git-rev-list[1].
 
 --also-filter-submodules::
@@ -203,13 +203,13 @@ objects from the source repository into a pack in the cloned repository.
 -o <name>::
 --origin <name>::
        Instead of using the remote name `origin` to keep track of the upstream
-       repository, use `<name>`.  Overrides `clone.defaultRemoteName` from the
+       repository, use _<name>_.  Overrides `clone.defaultRemoteName` from the
        config.
 
 -b <name>::
 --branch <name>::
        Instead of pointing the newly created HEAD to the branch pointed
-       to by the cloned repository's HEAD, point to `<name>` branch
+       to by the cloned repository's HEAD, point to _<name>_ branch
        instead. In a non-bare repository, this is the branch that will
        be checked out.
        `--branch` can also take tags and detaches the HEAD at that commit
@@ -230,7 +230,7 @@ objects from the source repository into a pack in the cloned repository.
        Set a configuration variable in the newly-created repository;
        this takes effect immediately after the repository is
        initialized, but before the remote history is fetched or any
-       files checked out.  The key is in the same format as expected by
+       files checked out.  The _<key>_ is in the same format as expected by
        linkgit:git-config[1] (e.g., `core.eol=true`). If multiple
        values are given for the same key, each value will be written to
        the config file. This makes it safe, for example, to add
@@ -263,7 +263,7 @@ corresponding `--mirror` and `--no-tags` options instead.
        branch remote's `HEAD` points at.
        Further fetches into the resulting repository will only update the
        remote-tracking branch for the branch this option was used for the
-       initial cloning.  If the HEAD at the remote did not point at any
+       initial cloning.  If the `HEAD` at the remote did not point at any
        branch when `--single-branch` clone was made, no remote-tracking
        branch is created.
 
@@ -281,7 +281,7 @@ branch of some repository for search indexing.
 
 --recurse-submodules[=<pathspec>]::
        After the clone is created, initialize and clone submodules
-       within based on the provided pathspec.  If no pathspec is
+       within based on the provided _<pathspec>_.  If no _=<pathspec>_ is
        provided, all submodules are initialized and cloned.
        This option can be given multiple times for pathspecs consisting
        of multiple entries.  The resulting clone has `submodule.active` set to
@@ -323,20 +323,20 @@ include::ref-storage-format.txt[]
        Defaults to the `submodule.fetchJobs` option.
 
 <repository>::
-       The (possibly remote) repository to clone from.  See the
+       The (possibly remote) _<repository>_ to clone from.  See the
        <<URLS,GIT URLS>> section below for more information on specifying
        repositories.
 
 <directory>::
        The name of a new directory to clone into.  The "humanish"
-       part of the source repository is used if no directory is
+       part of the source repository is used if no _<directory>_ is
        explicitly given (`repo` for `/path/to/repo.git` and `foo`
        for `host.xz:foo/.git`).  Cloning into an existing directory
        is only allowed if the directory is empty.
 
 --bundle-uri=<uri>::
        Before fetching from the remote, fetch a bundle from the given
-       `<uri>` and unbundle the data into the local repository. The refs
+       _<uri>_ and unbundle the data into the local repository. The refs
        in the bundle will be stored under the hidden `refs/bundle/*`
        namespace. This option is incompatible with `--depth`,
        `--shallow-since`, and `--shallow-exclude`.
index e8dc645bb59a8664865c9eed00698ee4374ea4a3..2f864e11ed9719a2443ece695534636fcb6e2631 100644 (file)
@@ -33,10 +33,10 @@ If the object storage directory is specified via the
 are created underneath; otherwise, the default `$GIT_DIR/objects`
 directory is used.
 
-Running 'git init' in an existing repository is safe. It will not
+Running `git init` in an existing repository is safe. It will not
 overwrite things that are already there. The primary reason for
-rerunning 'git init' is to pick up newly added templates (or to move
-the repository to another place if --separate-git-dir is given).
+rerunning `git init` is to pick up newly added templates (or to move
+the repository to another place if `--separate-git-dir` is given).
 
 OPTIONS
 -------
@@ -53,14 +53,14 @@ current working directory.
 
 --object-format=<format>::
 
-Specify the given object format (hash algorithm) for the repository.  The valid
-values are 'sha1' and (if enabled) 'sha256'.  'sha1' is the default.
+Specify the given object _<format>_ (hash algorithm) for the repository.  The valid
+values are `sha1` and (if enabled) `sha256`.  `sha1` is the default.
 +
 include::object-format-disclaimer.txt[]
 
 --ref-format=<format>::
 
-Specify the given ref storage format for the repository. The valid values are:
+Specify the given ref storage _<format>_ for the repository. The valid values are:
 +
 include::ref-storage-format.txt[]
 
@@ -81,7 +81,7 @@ If this is a reinitialization, the repository will be moved to the specified pat
 -b <branch-name>::
 --initial-branch=<branch-name>::
 
-Use the specified name for the initial branch in the newly created
+Use _<branch-name>_ for the initial branch in the newly created
 repository.  If not specified, fall back to the default name (currently
 `master`, but this is subject to change in the future; the name can be
 customized via the `init.defaultBranch` configuration variable).
@@ -90,40 +90,44 @@ customized via the `init.defaultBranch` configuration variable).
 
 Specify that the Git repository is to be shared amongst several users.  This
 allows users belonging to the same group to push into that
-repository.  When specified, the config variable "core.sharedRepository" is
+repository.  When specified, the config variable `core.sharedRepository` is
 set so that files and directories under `$GIT_DIR` are created with the
 requested permissions.  When not specified, Git will use permissions reported
-by umask(2).
+by `umask(2)`.
 +
-The option can have the following values, defaulting to 'group' if no value
+The option can have the following values, defaulting to `group` if no value
 is given:
 +
 --
-'umask' (or 'false')::
+umask::
+false::
 
 Use permissions reported by umask(2). The default, when `--shared` is not
 specified.
 
-'group' (or 'true')::
+group::
+true::
 
 Make the repository group-writable, (and g+sx, since the git group may not be
 the primary group of all users). This is used to loosen the permissions of an
 otherwise safe umask(2) value. Note that the umask still applies to the other
-permission bits (e.g. if umask is '0022', using 'group' will not remove read
-privileges from other (non-group) users). See '0xxx' for how to exactly specify
+permission bits (e.g. if umask is `0022`, using `group` will not remove read
+privileges from other (non-group) users). See `0xxx` for how to exactly specify
 the repository permissions.
 
-'all' (or 'world' or 'everybody')::
+all::
+world::
+everybody::
 
-Same as 'group', but make the repository readable by all users.
+Same as `group`, but make the repository readable by all users.
 
-'<perm>'::
+<perm>::
 
-'<perm>' is a 3-digit octal number prefixed with `0` and each file
-will have mode '<perm>'. '<perm>' will override users' umask(2)
-value (and not only loosen permissions as 'group' and 'all'
-do). '0640' will create a repository which is group-readable, but
-not group-writable or accessible to others. '0660' will create a repo
+_<perm>_ is a 3-digit octal number prefixed with `0` and each file
+will have mode _<perm>_. _<perm>_ will override users'`umask(2)`
+value (and not only loosen permissions as `group` and `all`
+do). `0640` will create a repository which is group-readable, but
+not group-writable or accessible to others. `0660` will create a repo
 that is readable and writable to the current user and group, but
 inaccessible to others (directories and executable files get their
 `x` bit from the `r` bit for corresponding classes of users).
@@ -133,7 +137,7 @@ By default, the configuration flag `receive.denyNonFastForwards` is enabled
 in shared repositories, so that you cannot force a non fast-forwarding push
 into it.
 
-If you provide a 'directory', the command is run inside it. If this directory
+If you provide a _<directory>_, the command is run inside it. If this directory
 does not exist, it will be created.
 
 TEMPLATE DIRECTORY
@@ -172,7 +176,7 @@ $ git add .     <2>
 $ git commit    <3>
 ----------------
 +
-<1> Create a /path/to/my/codebase/.git directory.
+<1> Create a `/path/to/my/codebase/.git` directory.
 <2> Add all existing files to the index.
 <3> Record the pristine state as the first commit in the history.
 
@@ -181,6 +185,8 @@ CONFIGURATION
 
 include::includes/cmd-config-section-all.txt[]
 
+:git-init:
+
 include::config/init.txt[]
 
 GIT
index 4dbb88373bcddadde2560e8159ef31d1f3580c58..b0f36fabfb391c10582a6f0feb63fa596e693ddc 100644 (file)
@@ -472,7 +472,7 @@ again, because your configuration may already be caching `git status`
 results, so it could be faster on subsequent runs.
 
 * The `--untracked-files=no` flag or the
-       `status.showUntrackedfiles=false` config (see above for both):
+       `status.showUntrackedFiles=no` config (see above for both):
        indicate that `git status` should not report untracked
        files. This is the fastest option. `git status` will not list
        the untracked files, so you need to be careful to remember if
index ed8da428c98bc96cafdbbcc8543ed0c8479a13a3..07c8439a6f784bc818108fad6fdd82891509dd84 100644 (file)
@@ -526,7 +526,7 @@ set by Git if the remote helper has the 'option' capability.
 'option pushcert' {'true'|'false'}::
        GPG sign pushes.
 
-'option push-option <string>::
+'option push-option' <string>::
        Transmit <string> as a push option. As the push option
        must not contain LF or NUL characters, the string is not encoded.
 
index ce671f812d4ce32b156361333155f459c9521647..0b9e0c4302d850a7a38044d03bd1146764bc83ca 100644 (file)
@@ -44,26 +44,26 @@ syntaxes may be used:
 
 ifndef::git-clone[]
 These two syntaxes are mostly equivalent, except when cloning, when
-the former implies --local option. See linkgit:git-clone[1] for
+the former implies `--local` option. See linkgit:git-clone[1] for
 details.
 endif::git-clone[]
 
 ifdef::git-clone[]
 These two syntaxes are mostly equivalent, except the former implies
---local option.
+`--local` option.
 endif::git-clone[]
 
-'git clone', 'git fetch' and 'git pull', but not 'git push', will also
+`git clone`, `git fetch` and `git pull`, but not `git push`, will also
 accept a suitable bundle file. See linkgit:git-bundle[1].
 
 When Git doesn't know how to handle a certain transport protocol, it
-attempts to use the 'remote-<transport>' remote helper, if one
+attempts to use the `remote-<transport>` remote helper, if one
 exists. To explicitly request a remote helper, the following syntax
 may be used:
 
-- <transport>::<address>
+- _<transport>_::_<address>_
 
-where <address> may be a path, a server and path, or an arbitrary
+where _<address>_ may be a path, a server and path, or an arbitrary
 URL-like string recognized by the specific remote helper being
 invoked. See linkgit:gitremote-helpers[7] for details.
 
index 6433903491054874c826880ea46e0fbc1fdb02bd..90a4189358300b0b594cdb18a90ce3b25d03de44 100644 (file)
@@ -4093,7 +4093,38 @@ that not only specifies their type, but also provides size information
 about the data in the object.  It's worth noting that the SHA-1 hash
 that is used to name the object is the hash of the original data
 plus this header, so `sha1sum` 'file' does not match the object name
-for 'file'.
+for 'file' (the earliest versions of Git hashed slightly differently
+but the conclusion is still the same).
+
+The following is a short example that demonstrates how these hashes
+can be generated manually:
+
+Let's assume a small text file with some simple content:
+
+-------------------------------------------------
+$ echo "Hello world" >hello.txt
+-------------------------------------------------
+
+We can now manually generate the hash Git would use for this file:
+
+- The object we want the hash for is of type "blob" and its size is
+  12 bytes.
+
+- Prepend the object header to the file content and feed this to
+  `sha1sum`:
+
+-------------------------------------------------
+$ { printf "blob 12\0"; cat hello.txt; } | sha1sum
+802992c4220de19a90767f3000a79a31b98d0df7  -
+-------------------------------------------------
+
+This manually constructed hash can be verified using `git hash-object`
+which of course hides the addition of the header:
+
+-------------------------------------------------
+$ git hash-object hello.txt
+802992c4220de19a90767f3000a79a31b98d0df7
+-------------------------------------------------
 
 As a result, the general consistency of an object can always be tested
 independently of the contents or the type of the object: all objects can
@@ -4123,7 +4154,8 @@ $ git switch --detach e83c5163
 ----------------------------------------------------
 
 The initial revision lays the foundation for almost everything Git has
-today, but is small enough to read in one sitting.
+today (even though details may differ in a few places), but is small
+enough to read in one sitting.
 
 Note that terminology has changed since that revision.  For example, the
 README in that revision uses the word "changeset" to describe what we
index 4ba64ee5b2d315eeeaa6143f5c8c2053d066a830..b0e05506871b9c402b75ad7ba52c47776797611d 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -68,6 +68,7 @@ static struct {
        [ADVICE_PUSH_UNQUALIFIED_REF_NAME]              = { "pushUnqualifiedRefName" },
        [ADVICE_PUSH_UPDATE_REJECTED]                   = { "pushUpdateRejected" },
        [ADVICE_PUSH_UPDATE_REJECTED_ALIAS]             = { "pushNonFastForward" }, /* backwards compatibility */
+       [ADVICE_REF_SYNTAX]                             = { "refSyntax" },
        [ADVICE_RESET_NO_REFRESH_WARNING]               = { "resetNoRefresh" },
        [ADVICE_RESOLVE_CONFLICT]                       = { "resolveConflict" },
        [ADVICE_RM_HINTS]                               = { "rmHints" },
index 7d0a821f5cb8a1818154852bbb76d7e15d4fe97b..bf630ee3ac3eae23b05b9554f4cc20ce2ea7a280 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -36,6 +36,7 @@ enum advice_type {
        ADVICE_PUSH_UNQUALIFIED_REF_NAME,
        ADVICE_PUSH_UPDATE_REJECTED,
        ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
+       ADVICE_REF_SYNTAX,
        ADVICE_RESET_NO_REFRESH_WARNING,
        ADVICE_RESOLVE_CONFLICT,
        ADVICE_RM_HINTS,
index 6719a181bd1f03af21b92d8be71a93142ef700e7..621019fcf4bde0a0568dbae2a1055e12cf8df030 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -370,8 +370,12 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
  */
 int validate_branchname(const char *name, struct strbuf *ref)
 {
-       if (strbuf_check_branch_ref(ref, name))
-               die(_("'%s' is not a valid branch name"), name);
+       if (strbuf_check_branch_ref(ref, name)) {
+               int code = die_message(_("'%s' is not a valid branch name"), name);
+               advise_if_enabled(ADVICE_REF_SYNTAX,
+                                 _("See `man git check-ref-format`"));
+               exit(code);
+       }
 
        return ref_exists(ref->buf);
 }
index b3cbb7fd440486300b14e76400ac58f927532995..8c2305ad2c85586c960c986582f1d9e874a26a24 100644 (file)
@@ -582,8 +582,12 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
                 */
                if (ref_exists(oldref.buf))
                        recovery = 1;
-               else
-                       die(_("invalid branch name: '%s'"), oldname);
+               else {
+                       int code = die_message(_("invalid branch name: '%s'"), oldname);
+                       advise_if_enabled(ADVICE_REF_SYNTAX,
+                                         _("See `man git check-ref-format`"));
+                       exit(code);
+               }
        }
 
        for (int i = 0; worktrees[i]; i++) {
index d90766cad3a0ba13c41dd0b90c03254ba5eb83ee..29efe841537b341776bdca2fc70f3df4d333e066 100644 (file)
@@ -25,7 +25,7 @@
 #include "help.h"
 #include "prompt.h"
 
-static int force = -1; /* unset */
+static int require_force = -1; /* unset */
 static int interactive;
 static struct string_list del_list = STRING_LIST_INIT_DUP;
 static unsigned int colopts;
@@ -128,7 +128,7 @@ static int git_clean_config(const char *var, const char *value,
        }
 
        if (!strcmp(var, "clean.requireforce")) {
-               force = !git_config_bool(var, value);
+               require_force = git_config_bool(var, value);
                return 0;
        }
 
@@ -920,7 +920,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 {
        int i, res;
        int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
-       int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
+       int ignored_only = 0, force = 0, errors = 0, gone = 1;
        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
        struct strbuf abs_path = STRBUF_INIT;
        struct dir_struct dir = DIR_INIT;
@@ -946,22 +946,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        };
 
        git_config(git_clean_config, NULL);
-       if (force < 0)
-               force = 0;
-       else
-               config_set = 1;
 
        argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
                             0);
 
-       if (!interactive && !dry_run && !force) {
-               if (config_set)
-                       die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
-                                 "refusing to clean"));
-               else
-                       die(_("clean.requireForce defaults to true and neither -i, -n, nor -f given;"
-                                 " refusing to clean"));
-       }
+       if (require_force != 0 && !force && !interactive && !dry_run)
+               die(_("clean.requireForce is true and -f not given: refusing to clean"));
 
        if (force > 1)
                rm_flags = 0;
index 93892ab5682ce51e1a131d845386e555a99b893c..f3bc6edef8ce4bf15998bbf87fd2d5af4d7e3353 100644 (file)
@@ -116,7 +116,7 @@ static struct option builtin_clone_options[] = {
        OPT_HIDDEN_BOOL(0, "naked", &option_bare,
                        N_("create a bare repository")),
        OPT_BOOL(0, "mirror", &option_mirror,
-                N_("create a mirror repository (implies bare)")),
+                N_("create a mirror repository (implies --bare)")),
        OPT_BOOL('l', "local", &option_local,
                N_("to clone from a local repository")),
        OPT_BOOL(0, "no-hardlinks", &option_no_hardlinks,
index a3a37bd215d844806814d677d2c7dfa71c8f31c6..856428fef9577eaa7f4407d598e073e488e4d089 100644 (file)
@@ -1524,14 +1524,12 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
        struct strbuf pack_name = STRBUF_INIT;
        struct strbuf index_name = STRBUF_INIT;
        struct strbuf rev_index_name = STRBUF_INIT;
-       int err;
 
        if (!from_stdin) {
                close(input_fd);
        } else {
                fsync_component_or_die(FSYNC_COMPONENT_PACK, output_fd, curr_pack_name);
-               err = close(output_fd);
-               if (err)
+               if (close(output_fd))
                        die_errno(_("error while closing pack file"));
        }
 
@@ -1566,17 +1564,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                write_or_die(1, buf.buf, buf.len);
                strbuf_release(&buf);
 
-               /*
-                * Let's just mimic git-unpack-objects here and write
-                * the last part of the input buffer to stdout.
-                */
-               while (input_len) {
-                       err = xwrite(1, input_buffer + input_offset, input_len);
-                       if (err <= 0)
-                               break;
-                       input_len -= err;
-                       input_offset += err;
-               }
+               /* Write the last part of the buffer to stdout */
+               write_in_full(1, input_buffer + input_offset, input_len);
        }
 
        strbuf_release(&rev_index_name);
index 033bd1556cf9f8d6e56c2730a3e868500388144e..11f4ce9e4a274c666fe4127a904872653d35010d 100644 (file)
@@ -9,6 +9,7 @@
 #include "gettext.h"
 #include "parse-options.h"
 #include "string-list.h"
+#include "tempfile.h"
 #include "trailer.h"
 #include "config.h"
 
@@ -91,6 +92,102 @@ static int parse_opt_parse(const struct option *opt, const char *arg,
        return 0;
 }
 
+static struct tempfile *trailers_tempfile;
+
+static FILE *create_in_place_tempfile(const char *file)
+{
+       struct stat st;
+       struct strbuf filename_template = STRBUF_INIT;
+       const char *tail;
+       FILE *outfile;
+
+       if (stat(file, &st))
+               die_errno(_("could not stat %s"), file);
+       if (!S_ISREG(st.st_mode))
+               die(_("file %s is not a regular file"), file);
+       if (!(st.st_mode & S_IWUSR))
+               die(_("file %s is not writable by user"), file);
+
+       /* Create temporary file in the same directory as the original */
+       tail = strrchr(file, '/');
+       if (tail)
+               strbuf_add(&filename_template, file, tail - file + 1);
+       strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX");
+
+       trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);
+       strbuf_release(&filename_template);
+       outfile = fdopen_tempfile(trailers_tempfile, "w");
+       if (!outfile)
+               die_errno(_("could not open temporary file"));
+
+       return outfile;
+}
+
+static void read_input_file(struct strbuf *sb, const char *file)
+{
+       if (file) {
+               if (strbuf_read_file(sb, file, 0) < 0)
+                       die_errno(_("could not read input file '%s'"), file);
+       } else {
+               if (strbuf_read(sb, fileno(stdin), 0) < 0)
+                       die_errno(_("could not read from stdin"));
+       }
+}
+
+static void interpret_trailers(const struct process_trailer_options *opts,
+                              struct list_head *new_trailer_head,
+                              const char *file)
+{
+       LIST_HEAD(head);
+       struct strbuf sb = STRBUF_INIT;
+       struct strbuf trailer_block = STRBUF_INIT;
+       struct trailer_info info;
+       FILE *outfile = stdout;
+
+       trailer_config_init();
+
+       read_input_file(&sb, file);
+
+       if (opts->in_place)
+               outfile = create_in_place_tempfile(file);
+
+       parse_trailers(opts, &info, sb.buf, &head);
+
+       /* Print the lines before the trailers */
+       if (!opts->only_trailers)
+               fwrite(sb.buf, 1, info.trailer_block_start, outfile);
+
+       if (!opts->only_trailers && !info.blank_line_before_trailer)
+               fprintf(outfile, "\n");
+
+
+       if (!opts->only_input) {
+               LIST_HEAD(config_head);
+               LIST_HEAD(arg_head);
+               parse_trailers_from_config(&config_head);
+               parse_trailers_from_command_line_args(&arg_head, new_trailer_head);
+               list_splice(&config_head, &arg_head);
+               process_trailers_lists(&head, &arg_head);
+       }
+
+       /* Print trailer block. */
+       format_trailers(opts, &head, &trailer_block);
+       free_trailers(&head);
+       fwrite(trailer_block.buf, 1, trailer_block.len, outfile);
+       strbuf_release(&trailer_block);
+
+       /* Print the lines after the trailers as is */
+       if (!opts->only_trailers)
+               fwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);
+       trailer_info_release(&info);
+
+       if (opts->in_place)
+               if (rename_tempfile(&trailers_tempfile, file))
+                       die_errno(_("could not rename temporary file to %s"), file);
+
+       strbuf_release(&sb);
+}
+
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
 {
        struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
@@ -132,11 +229,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
        if (argc) {
                int i;
                for (i = 0; i < argc; i++)
-                       process_trailers(argv[i], &opts, &trailers);
+                       interpret_trailers(&opts, &trailers, argv[i]);
        } else {
                if (opts.in_place)
                        die(_("no input file given for in-place editing"));
-               process_trailers(NULL, &opts, &trailers);
+               interpret_trailers(&opts, &trailers, NULL);
        }
 
        new_trailers_clear(&trailers);
index be787690bd7c2b6396784a1476314b8f757ab749..e444ab102dfe5c99dae8b004960b27a8a600a16b 100644 (file)
@@ -567,13 +567,6 @@ static int move_to_original_branch(struct rebase_options *opts)
        return ret;
 }
 
-static const char *resolvemsg =
-N_("Resolve all conflicts manually, mark them as resolved with\n"
-"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
-"You can instead skip this commit: run \"git rebase --skip\".\n"
-"To abort and get back to the state before \"git rebase\", run "
-"\"git rebase --abort\".");
-
 static int run_am(struct rebase_options *opts)
 {
        struct child_process am = CHILD_PROCESS_INIT;
@@ -587,7 +580,7 @@ static int run_am(struct rebase_options *opts)
                     opts->reflog_action);
        if (opts->action == ACTION_CONTINUE) {
                strvec_push(&am.args, "--resolved");
-               strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+               strvec_pushf(&am.args, "--resolvemsg=%s", rebase_resolvemsg);
                if (opts->gpg_sign_opt)
                        strvec_push(&am.args, opts->gpg_sign_opt);
                status = run_command(&am);
@@ -598,7 +591,7 @@ static int run_am(struct rebase_options *opts)
        }
        if (opts->action == ACTION_SKIP) {
                strvec_push(&am.args, "--skip");
-               strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+               strvec_pushf(&am.args, "--resolvemsg=%s", rebase_resolvemsg);
                status = run_command(&am);
                if (status)
                        return status;
@@ -672,7 +665,7 @@ static int run_am(struct rebase_options *opts)
 
        strvec_pushv(&am.args, opts->git_am_opts.v);
        strvec_push(&am.args, "--rebasing");
-       strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+       strvec_pushf(&am.args, "--resolvemsg=%s", rebase_resolvemsg);
        strvec_push(&am.args, "--patch-format=mboxrd");
        if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE)
                strvec_push(&am.args, "--rerere-autoupdate");
@@ -700,7 +693,6 @@ static int run_specific_rebase(struct rebase_options *opts)
 
        if (opts->type == REBASE_MERGE) {
                /* Run sequencer-based rebase */
-               setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
                if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT))
                        setenv("GIT_SEQUENCE_EDITOR", ":", 1);
                if (opts->gpg_sign_opt) {
index d91bbe728d739e074e5f4fa54f70c186f371c1b7..8412d12fa55100132c69ae24914b2db536bf77c9 100644 (file)
@@ -150,7 +150,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
        else if (!strcmp(arg, "push"))
                *mirror = MIRROR_PUSH;
        else
-               return error(_("unknown mirror argument: %s"), arg);
+               return error(_("unknown --mirror argument: %s"), arg);
        return 0;
 }
 
index ede36328a3cab9170bc59cb98dda097ea6eb24f7..15e4cccc45687d4902979911e7162e7c82567e6e 100644 (file)
@@ -314,8 +314,9 @@ static int write_oid(const struct object_id *oid,
                        die(_("could not start pack-objects to repack promisor objects"));
        }
 
-       xwrite(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz);
-       xwrite(cmd->in, "\n", 1);
+       if (write_in_full(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz) < 0 ||
+           write_in_full(cmd->in, "\n", 1) < 0)
+               die(_("failed to feed promisor objects to pack-objects"));
        return 0;
 }
 
index e0a701f2b383bbbd95fc1e7fef3fec30c3783c37..f1c85a00ae9bb6586527a845c302cba47c4c8daf 100644 (file)
@@ -679,13 +679,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix UNUSED)
        use(the_hash_algo->rawsz);
 
        /* Write the last part of the buffer to stdout */
-       while (len) {
-               int ret = xwrite(1, buffer + offset, len);
-               if (ret <= 0)
-                       break;
-               len -= ret;
-               offset += ret;
-       }
+       write_in_full(1, buffer + offset, len);
 
        /* All done */
        return has_errors;
index dacc95172dcdcbd85086d9dacf57d2be588c90e1..d0dcca2ec554cddf61447215357a7e15877b0aca 100644 (file)
@@ -638,6 +638,18 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
        SHELL_PATH = /usr/coreutils/bin/bash
 endif
+ifeq ($(uname_S),OS/390)
+       NO_SYS_POLL_H = YesPlease
+       NO_STRCASESTR = YesPlease
+       NO_REGEX = YesPlease
+       NO_MMAP = YesPlease
+       NO_NSEC = YesPlease
+       NO_STRLCPY = YesPlease
+       NO_MEMMEM = YesPlease
+       NO_GECOS_IN_PWENT = YesPlease
+       HAVE_STRINGS_H = YesPlease
+       NEEDS_MODE_TRANSLATION = YesPlease
+endif
 ifeq ($(uname_S),MINGW)
        ifeq ($(shell expr "$(uname_R)" : '1\.'),2)
                $(error "Building with MSys is no longer supported")
index fcf1afd75de4eb14a0123a2a75692298f2c39a0b..75193ded4bdeda01fc440db26b08e40ae2d3a73f 100644 (file)
@@ -454,16 +454,18 @@ fi
 
 # This function is equivalent to
 #
-#    __gitcomp "$(git xxx --git-completion-helper) ..."
+#    ___git_resolved_builtins=$(git xxx --git-completion-helper)
 #
-# except that the output is cached. Accept 1-3 arguments:
+# except that the result of the execution is cached.
+#
+# Accept 1-3 arguments:
 # 1: the git command to execute, this is also the cache key
+#    (use "_" when the command contains spaces, e.g. "remote add"
+#    becomes "remote_add")
 # 2: extra options to be added on top (e.g. negative forms)
 # 3: options to be excluded
-__gitcomp_builtin ()
+__git_resolve_builtins ()
 {
-       # spaces must be replaced with underscore for multi-word
-       # commands, e.g. "git remote add" becomes remote_add.
        local cmd="$1"
        local incl="${2-}"
        local excl="${3-}"
@@ -489,7 +491,24 @@ __gitcomp_builtin ()
                eval "$var=\"$options\""
        fi
 
-       __gitcomp "$options"
+       ___git_resolved_builtins="$options"
+}
+
+# This function is equivalent to
+#
+#    __gitcomp "$(git xxx --git-completion-helper) ..."
+#
+# except that the output is cached. Accept 1-3 arguments:
+# 1: the git command to execute, this is also the cache key
+#    (use "_" when the command contains spaces, e.g. "remote add"
+#    becomes "remote_add")
+# 2: extra options to be added on top (e.g. negative forms)
+# 3: options to be excluded
+__gitcomp_builtin ()
+{
+       __git_resolve_builtins "$1" "$2" "$3"
+
+       __gitcomp "$___git_resolved_builtins"
 }
 
 # Variation of __gitcomp_nl () that appends to the existing list of
@@ -556,6 +575,26 @@ __gitcomp_file ()
        true
 }
 
+# Find the current subcommand for commands that follow the syntax:
+#
+#    git <command> <subcommand>
+#
+# 1: List of possible subcommands.
+# 2: Optional subcommand to return when none is found.
+__git_find_subcommand ()
+{
+       local subcommand subcommands="$1" default_subcommand="$2"
+
+       for subcommand in $subcommands; do
+               if [ "$subcommand" = "${words[__git_cmd_idx+1]}" ]; then
+                       echo $subcommand
+                       return
+               fi
+       done
+
+       echo $default_subcommand
+}
+
 # Execute 'git ls-files', unless the --committable option is specified, in
 # which case it runs 'git diff-index' to find out the files that can be
 # committed.  It return paths relative to the directory specified in the first
@@ -2471,13 +2510,30 @@ _git_rebase ()
 
 _git_reflog ()
 {
-       local subcommands="show delete expire"
-       local subcommand="$(__git_find_on_cmdline "$subcommands")"
+       local subcommands subcommand
 
-       if [ -z "$subcommand" ]; then
-               __gitcomp "$subcommands"
-       else
-               __git_complete_refs
+       __git_resolve_builtins "reflog"
+
+       subcommands="$___git_resolved_builtins"
+       subcommand="$(__git_find_subcommand "$subcommands" "show")"
+
+       case "$subcommand,$cur" in
+       show,--*)
+               __gitcomp "
+                       $__git_log_common_options
+                       "
+               return
+               ;;
+       $subcommand,--*)
+               __gitcomp_builtin "reflog_$subcommand"
+               return
+               ;;
+       esac
+
+       __git_complete_refs
+
+       if [ $((cword - __git_cmd_idx)) -eq 1 ]; then
+               __gitcompappend "$subcommands" "" "$cur" " "
        fi
 }
 
@@ -3573,7 +3629,7 @@ __git_complete_worktree_paths ()
        # Generate completion reply from worktree list skipping the first
        # entry: it's the path of the main worktree, which can't be moved,
        # removed, locked, etc.
-       __gitcomp_nl "$(git worktree list --porcelain |
+       __gitcomp_nl "$(__git worktree list --porcelain |
                sed -n -e '2,$ s/^worktree //p')"
 }
 
diff --git a/dir.c b/dir.c
index ac699542302cece1d1aea426cd4cc37940e6fead..20ebe4cba2687e027765876ddc57b086fa85dd71 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -3918,6 +3918,26 @@ void untracked_cache_invalidate_path(struct index_state *istate,
                                 path, strlen(path));
 }
 
+void untracked_cache_invalidate_trimmed_path(struct index_state *istate,
+                                            const char *path,
+                                            int safe_path)
+{
+       size_t len = strlen(path);
+
+       if (!len)
+               BUG("untracked_cache_invalidate_trimmed_path given zero length path");
+
+       if (path[len - 1] != '/') {
+               untracked_cache_invalidate_path(istate, path, safe_path);
+       } else {
+               struct strbuf tmp = STRBUF_INIT;
+
+               strbuf_add(&tmp, path, len - 1);
+               untracked_cache_invalidate_path(istate, tmp.buf, safe_path);
+               strbuf_release(&tmp);
+       }
+}
+
 void untracked_cache_remove_from_index(struct index_state *istate,
                                       const char *path)
 {
diff --git a/dir.h b/dir.h
index 98aa85fcc0ee357a2df50014008c3e5ec12acb25..45a7b9ec5f2d52214e8000dbd68913056a1a2d77 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -576,6 +576,13 @@ int cmp_dir_entry(const void *p1, const void *p2);
 int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
 
 void untracked_cache_invalidate_path(struct index_state *, const char *, int safe_path);
+/*
+ * Invalidate the untracked-cache for this path, but first strip
+ * off a trailing slash, if present.
+ */
+void untracked_cache_invalidate_trimmed_path(struct index_state *,
+                                            const char *path,
+                                            int safe_path);
 void untracked_cache_remove_from_index(struct index_state *, const char *);
 void untracked_cache_add_to_index(struct index_state *, const char *);
 
index f670c50937898342f693708c706a0db270be3a6d..2b17d60bbbecb0e0e53a934b4376e2207649e826 100644 (file)
@@ -5,6 +5,7 @@
 #include "ewah/ewok.h"
 #include "fsmonitor.h"
 #include "fsmonitor-ipc.h"
+#include "name-hash.h"
 #include "run-command.h"
 #include "strbuf.h"
 #include "trace2.h"
@@ -183,79 +184,282 @@ static int query_fsmonitor_hook(struct repository *r,
        return result;
 }
 
-static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
+/*
+ * Invalidate the FSM bit on this CE.  This is like mark_fsmonitor_invalid()
+ * but we've already handled the untracked-cache, so let's not repeat that
+ * work.  This also lets us have a different trace message so that we can
+ * see everything that was done as part of the refresh-callback.
+ */
+static void invalidate_ce_fsm(struct cache_entry *ce)
 {
-       int i, len = strlen(name);
-       int pos = index_name_pos(istate, name, len);
+       if (ce->ce_flags & CE_FSMONITOR_VALID) {
+               trace_printf_key(&trace_fsmonitor,
+                                "fsmonitor_refresh_callback INV: '%s'",
+                                ce->name);
+               ce->ce_flags &= ~CE_FSMONITOR_VALID;
+       }
+}
+
+static size_t handle_path_with_trailing_slash(
+       struct index_state *istate, const char *name, int pos);
+
+/*
+ * Use the name-hash to do a case-insensitive cache-entry lookup with
+ * the pathname and invalidate the cache-entry.
+ *
+ * Returns the number of cache-entries that we invalidated.
+ */
+static size_t handle_using_name_hash_icase(
+       struct index_state *istate, const char *name)
+{
+       struct cache_entry *ce = NULL;
+
+       ce = index_file_exists(istate, name, strlen(name), 1);
+       if (!ce)
+               return 0;
 
+       /*
+        * A case-insensitive search in the name-hash using the
+        * observed pathname found a cache-entry, so the observed path
+        * is case-incorrect.  Invalidate the cache-entry and use the
+        * correct spelling from the cache-entry to invalidate the
+        * untracked-cache.  Since we now have sparse-directories in
+        * the index, the observed pathname may represent a regular
+        * file or a sparse-index directory.
+        *
+        * Note that we should not have seen FSEvents for a
+        * sparse-index directory, but we handle it just in case.
+        *
+        * Either way, we know that there are not any cache-entries for
+        * children inside the cone of the directory, so we don't need to
+        * do the usual scan.
+        */
        trace_printf_key(&trace_fsmonitor,
-                        "fsmonitor_refresh_callback '%s' (pos %d)",
-                        name, pos);
+                        "fsmonitor_refresh_callback MAP: '%s' '%s'",
+                        name, ce->name);
 
-       if (name[len - 1] == '/') {
-               /*
-                * The daemon can decorate directory events, such as
-                * moves or renames, with a trailing slash if the OS
-                * FS Event contains sufficient information, such as
-                * MacOS.
-                *
-                * Use this to invalidate the entire cone under that
-                * directory.
-                *
-                * We do not expect an exact match because the index
-                * does not normally contain directory entries, so we
-                * start at the insertion point and scan.
-                */
-               if (pos < 0)
-                       pos = -pos - 1;
+       /*
+        * NEEDSWORK: We used the name-hash to find the correct
+        * case-spelling of the pathname in the cache-entry[], so
+        * technically this is a tracked file or a sparse-directory.
+        * It should not have any entries in the untracked-cache, so
+        * we should not need to use the case-corrected spelling to
+        * invalidate the the untracked-cache.  So we may not need to
+        * do this.  For now, I'm going to be conservative and always
+        * do it; we can revisit this later.
+        */
+       untracked_cache_invalidate_trimmed_path(istate, ce->name, 0);
 
-               /* Mark all entries for the folder invalid */
-               for (i = pos; i < istate->cache_nr; i++) {
-                       if (!starts_with(istate->cache[i]->name, name))
-                               break;
-                       istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
-               }
+       invalidate_ce_fsm(ce);
+       return 1;
+}
+
+/*
+ * Use the dir-name-hash to find the correct-case spelling of the
+ * directory.  Use the canonical spelling to invalidate all of the
+ * cache-entries within the matching cone.
+ *
+ * Returns the number of cache-entries that we invalidated.
+ */
+static size_t handle_using_dir_name_hash_icase(
+       struct index_state *istate, const char *name)
+{
+       struct strbuf canonical_path = STRBUF_INIT;
+       int pos;
+       size_t len = strlen(name);
+       size_t nr_in_cone;
+
+       if (name[len - 1] == '/')
+               len--;
+
+       if (!index_dir_find(istate, name, len, &canonical_path))
+               return 0; /* name is untracked */
 
+       if (!memcmp(name, canonical_path.buf, canonical_path.len)) {
+               strbuf_release(&canonical_path);
                /*
-                * We need to remove the traling "/" from the path
-                * for the untracked cache.
+                * NEEDSWORK: Our caller already tried an exact match
+                * and failed to find one.  They called us to do an
+                * ICASE match, so we should never get an exact match,
+                * so we could promote this to a BUG() here if we
+                * wanted to.  It doesn't hurt anything to just return
+                * 0 and go on because we should never get here.  Or we
+                * could just get rid of the memcmp() and this "if"
+                * clause completely.
                 */
-               name[len - 1] = '\0';
-       } else if (pos >= 0) {
+               BUG("handle_using_dir_name_hash_icase(%s) did not exact match",
+                   name);
+       }
+
+       trace_printf_key(&trace_fsmonitor,
+                        "fsmonitor_refresh_callback MAP: '%s' '%s'",
+                        name, canonical_path.buf);
+
+       /*
+        * The dir-name-hash only tells us the corrected spelling of
+        * the prefix.  We have to use this canonical path to do a
+        * lookup in the cache-entry array so that we repeat the
+        * original search using the case-corrected spelling.
+        */
+       strbuf_addch(&canonical_path, '/');
+       pos = index_name_pos(istate, canonical_path.buf,
+                            canonical_path.len);
+       nr_in_cone = handle_path_with_trailing_slash(
+               istate, canonical_path.buf, pos);
+       strbuf_release(&canonical_path);
+       return nr_in_cone;
+}
+
+/*
+ * The daemon sent an observed pathname without a trailing slash.
+ * (This is the normal case.)  We do not know if it is a tracked or
+ * untracked file, a sparse-directory, or a populated directory (on a
+ * platform such as Windows where FSEvents are not qualified).
+ *
+ * The pathname contains the observed case reported by the FS. We
+ * do not know it is case-correct or -incorrect.
+ *
+ * Assume it is case-correct and try an exact match.
+ *
+ * Return the number of cache-entries that we invalidated.
+ */
+static size_t handle_path_without_trailing_slash(
+       struct index_state *istate, const char *name, int pos)
+{
+       /*
+        * Mark the untracked cache dirty for this path (regardless of
+        * whether or not we find an exact match for it in the index).
+        * Since the path is unqualified (no trailing slash hint in the
+        * FSEvent), it may refer to a file or directory. So we should
+        * not assume one or the other and should always let the untracked
+        * cache decide what needs to invalidated.
+        */
+       untracked_cache_invalidate_trimmed_path(istate, name, 0);
+
+       if (pos >= 0) {
                /*
-                * We have an exact match for this path and can just
-                * invalidate it.
+                * An exact match on a tracked file. We assume that we
+                * do not need to scan forward for a sparse-directory
+                * cache-entry with the same pathname, nor for a cone
+                * at that directory. (That is, assume no D/F conflicts.)
                 */
-               istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID;
+               invalidate_ce_fsm(istate->cache[pos]);
+               return 1;
        } else {
+               size_t nr_in_cone;
+               struct strbuf work_path = STRBUF_INIT;
+
                /*
-                * The path is not a tracked file -or- it is a
-                * directory event on a platform that cannot
-                * distinguish between file and directory events in
-                * the event handler, such as Windows.
-                *
-                * Scan as if it is a directory and invalidate the
-                * cone under it.  (But remember to ignore items
-                * between "name" and "name/", such as "name-" and
-                * "name.".
+                * The negative "pos" gives us the suggested insertion
+                * point for the pathname (without the trailing slash).
+                * We need to see if there is a directory with that
+                * prefix, but there can be lots of pathnames between
+                * "foo" and "foo/" like "foo-" or "foo-bar", so we
+                * don't want to do our own scan.
                 */
+               strbuf_add(&work_path, name, strlen(name));
+               strbuf_addch(&work_path, '/');
+               pos = index_name_pos(istate, work_path.buf, work_path.len);
+               nr_in_cone = handle_path_with_trailing_slash(
+                       istate, work_path.buf, pos);
+               strbuf_release(&work_path);
+               return nr_in_cone;
+       }
+}
+
+/*
+ * The daemon can decorate directory events, such as a move or rename,
+ * by adding a trailing slash to the observed name.  Use this to
+ * explicitly invalidate the entire cone under that directory.
+ *
+ * The daemon can only reliably do that if the OS FSEvent contains
+ * sufficient information in the event.
+ *
+ * macOS FSEvents have enough information.
+ *
+ * Other platforms may or may not be able to do it (and it might
+ * depend on the type of event (for example, a daemon could lstat() an
+ * observed pathname after a rename, but not after a delete)).
+ *
+ * If we find an exact match in the index for a path with a trailing
+ * slash, it means that we matched a sparse-index directory in a
+ * cone-mode sparse-checkout (since that's the only time we have
+ * directories in the index).  We should never see this in practice
+ * (because sparse directories should not be present and therefore
+ * not generating FS events).  Either way, we can treat them in the
+ * same way and just invalidate the cache-entry and the untracked
+ * cache (and in this case, the forward cache-entry scan won't find
+ * anything and it doesn't hurt to let it run).
+ *
+ * Return the number of cache-entries that we invalidated.  We will
+ * use this later to determine if we need to attempt a second
+ * case-insensitive search on case-insensitive file systems.  That is,
+ * if the search using the observed-case in the FSEvent yields any
+ * results, we assume the prefix is case-correct.  If there are no
+ * matches, we still don't know if the observed path is simply
+ * untracked or case-incorrect.
+ */
+static size_t handle_path_with_trailing_slash(
+       struct index_state *istate, const char *name, int pos)
+{
+       int i;
+       size_t nr_in_cone = 0;
+
+       /*
+        * Mark the untracked cache dirty for this directory path
+        * (regardless of whether or not we find an exact match for it
+        * in the index or find it to be proper prefix of one or more
+        * files in the index), since the FSEvent is hinting that
+        * there may be changes on or within the directory.
+        */
+       untracked_cache_invalidate_trimmed_path(istate, name, 0);
+
+       if (pos < 0)
                pos = -pos - 1;
 
-               for (i = pos; i < istate->cache_nr; i++) {
-                       if (!starts_with(istate->cache[i]->name, name))
-                               break;
-                       if ((unsigned char)istate->cache[i]->name[len] > '/')
-                               break;
-                       if (istate->cache[i]->name[len] == '/')
-                               istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
-               }
+       /* Mark all entries for the folder invalid */
+       for (i = pos; i < istate->cache_nr; i++) {
+               if (!starts_with(istate->cache[i]->name, name))
+                       break;
+               invalidate_ce_fsm(istate->cache[i]);
+               nr_in_cone++;
        }
 
+       return nr_in_cone;
+}
+
+static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
+{
+       int len = strlen(name);
+       int pos = index_name_pos(istate, name, len);
+       size_t nr_in_cone;
+
+       trace_printf_key(&trace_fsmonitor,
+                        "fsmonitor_refresh_callback '%s' (pos %d)",
+                        name, pos);
+
+       if (name[len - 1] == '/')
+               nr_in_cone = handle_path_with_trailing_slash(istate, name, pos);
+       else
+               nr_in_cone = handle_path_without_trailing_slash(istate, name, pos);
+
        /*
-        * Mark the untracked cache dirty even if it wasn't found in the index
-        * as it could be a new untracked file.
+        * If we did not find an exact match for this pathname or any
+        * cache-entries with this directory prefix and we're on a
+        * case-insensitive file system, try again using the name-hash
+        * and dir-name-hash.
         */
-       untracked_cache_invalidate_path(istate, name, 0);
+       if (!nr_in_cone && ignore_case) {
+               nr_in_cone = handle_using_name_hash_icase(istate, name);
+               if (!nr_in_cone)
+                       nr_in_cone = handle_using_dir_name_hash_icase(
+                               istate, name);
+       }
+
+       if (nr_in_cone)
+               trace_printf_key(&trace_fsmonitor,
+                                "fsmonitor_refresh_callback CNT: %d",
+                                (int)nr_in_cone);
 }
 
 /*
diff --git a/git.c b/git.c
index 5265f920f165d99f01584eed54554dd51563acca..654d615a18845185e2ece17a7ca6229cea431a07 100644 (file)
--- a/git.c
+++ b/git.c
@@ -379,8 +379,6 @@ static int handle_alias(int *argcp, const char ***argv)
                        strvec_pushv(&child.args, (*argv) + 1);
 
                        trace2_cmd_alias(alias_command, child.args.v);
-                       trace2_cmd_list_config();
-                       trace2_cmd_list_env_vars();
                        trace2_cmd_name("_run_shell_alias_");
 
                        ret = run_command(&child);
@@ -417,8 +415,6 @@ static int handle_alias(int *argcp, const char ***argv)
                COPY_ARRAY(new_argv + count, *argv + 1, *argcp);
 
                trace2_cmd_alias(alias_command, new_argv);
-               trace2_cmd_list_config();
-               trace2_cmd_list_env_vars();
 
                *argv = new_argv;
                *argcp += count - 1;
@@ -468,8 +464,6 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 
        trace_argv_printf(argv, "trace: built-in: git");
        trace2_cmd_name(p->cmd);
-       trace2_cmd_list_config();
-       trace2_cmd_list_env_vars();
 
        validate_cache_entries(the_repository->index);
        status = p->fn(argc, argv, prefix);
index 90af4e66b28c8f338cb62cf228a2b8d80000e8a8..1bb99264976d27095f348caf0fc35efe5598348b 100644 (file)
@@ -321,11 +321,11 @@ static inline int commit_lock_file_to(struct lock_file *lk, const char *path)
  * Roll back `lk`: close the file descriptor and/or file pointer and
  * remove the lockfile. It is a NOOP to call `rollback_lock_file()`
  * for a `lock_file` object that has already been committed or rolled
- * back.
+ * back. No error will be returned in this case.
  */
-static inline void rollback_lock_file(struct lock_file *lk)
+static inline int rollback_lock_file(struct lock_file *lk)
 {
-       delete_tempfile(&lk->tempfile);
+       return delete_tempfile(&lk->tempfile);
 }
 
 #endif /* LOCKFILE_H */
index 251f036eef6983a66ac13013e5bee3ef770e8f9f..3a58ce03d9c4a6721941f0ff8b262b6ddb73acd2 100644 (file)
@@ -685,13 +685,20 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
        return slow_same_name(name, namelen, ce->name, len);
 }
 
-int index_dir_exists(struct index_state *istate, const char *name, int namelen)
+int index_dir_find(struct index_state *istate, const char *name, int namelen,
+                  struct strbuf *canonical_path)
 {
        struct dir_entry *dir;
 
        lazy_init_name_hash(istate);
        expand_to_path(istate, name, namelen, 0);
        dir = find_dir_entry(istate, name, namelen);
+
+       if (canonical_path && dir && dir->nr) {
+               strbuf_reset(canonical_path);
+               strbuf_add(canonical_path, dir->name, dir->namelen);
+       }
+
        return dir && dir->nr;
 }
 
index b1b4b0fb337f12eb5878dbedcaa66ae394cba425..0cbfc4286316b244a0902c7fffd1168ba320d52f 100644 (file)
@@ -4,7 +4,12 @@
 struct cache_entry;
 struct index_state;
 
-int index_dir_exists(struct index_state *istate, const char *name, int namelen);
+
+int index_dir_find(struct index_state *istate, const char *name, int namelen,
+                  struct strbuf *canonical_path);
+
+#define index_dir_exists(i, n, l) index_dir_find((i), (n), (l), NULL)
+
 void adjust_dirname_case(struct index_state *istate, char *name);
 struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
 
index 63a99dea6ef06b19bbb679a3ddd76087abc2a028..30b9e68f8ac85df1c6700d7a512eef618988b06c 100644 (file)
@@ -350,98 +350,107 @@ static int is_alias(struct parse_opt_ctx_t *ctx,
        return 0;
 }
 
+struct parsed_option {
+       const struct option *option;
+       enum opt_parsed flags;
+};
+
+static void register_abbrev(struct parse_opt_ctx_t *p,
+                           const struct option *option, enum opt_parsed flags,
+                           struct parsed_option *abbrev,
+                           struct parsed_option *ambiguous)
+{
+       if (p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
+               return;
+       if (abbrev->option &&
+           !(abbrev->flags == flags && is_alias(p, abbrev->option, option))) {
+               /*
+                * If this is abbreviated, it is
+                * ambiguous. So when there is no
+                * exact match later, we need to
+                * error out.
+                */
+               ambiguous->option = abbrev->option;
+               ambiguous->flags = abbrev->flags;
+       }
+       abbrev->option = option;
+       abbrev->flags = flags;
+}
+
 static enum parse_opt_result parse_long_opt(
        struct parse_opt_ctx_t *p, const char *arg,
        const struct option *options)
 {
        const char *arg_end = strchrnul(arg, '=');
-       const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
-       enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG;
-       int allow_abbrev = !(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT);
+       const char *arg_start = arg;
+       enum opt_parsed flags = OPT_LONG;
+       int arg_starts_with_no_no = 0;
+       struct parsed_option abbrev = { .option = NULL, .flags = OPT_LONG };
+       struct parsed_option ambiguous = { .option = NULL, .flags = OPT_LONG };
+
+       if (skip_prefix(arg_start, "no-", &arg_start)) {
+               if (skip_prefix(arg_start, "no-", &arg_start))
+                       arg_starts_with_no_no = 1;
+               else
+                       flags |= OPT_UNSET;
+       }
 
        for (; options->type != OPTION_END; options++) {
                const char *rest, *long_name = options->long_name;
-               enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
+               enum opt_parsed opt_flags = OPT_LONG;
+               int allow_unset = !(options->flags & PARSE_OPT_NONEG);
 
                if (options->type == OPTION_SUBCOMMAND)
                        continue;
                if (!long_name)
                        continue;
 
-               if (!starts_with(arg, "no-") &&
-                   !(options->flags & PARSE_OPT_NONEG) &&
-                   skip_prefix(long_name, "no-", &long_name))
+               if (skip_prefix(long_name, "no-", &long_name))
                        opt_flags |= OPT_UNSET;
+               else if (arg_starts_with_no_no)
+                       continue;
 
-               if (!skip_prefix(arg, long_name, &rest))
-                       rest = NULL;
-               if (!rest) {
-                       /* abbreviated? */
-                       if (allow_abbrev &&
-                           !strncmp(long_name, arg, arg_end - arg)) {
-is_abbreviated:
-                               if (abbrev_option &&
-                                   !is_alias(p, abbrev_option, options)) {
-                                       /*
-                                        * If this is abbreviated, it is
-                                        * ambiguous. So when there is no
-                                        * exact match later, we need to
-                                        * error out.
-                                        */
-                                       ambiguous_option = abbrev_option;
-                                       ambiguous_flags = abbrev_flags;
-                               }
-                               if (!(flags & OPT_UNSET) && *arg_end)
-                                       p->opt = arg_end + 1;
-                               abbrev_option = options;
-                               abbrev_flags = flags ^ opt_flags;
-                               continue;
-                       }
-                       /* negation allowed? */
-                       if (options->flags & PARSE_OPT_NONEG)
-                               continue;
-                       /* negated and abbreviated very much? */
-                       if (allow_abbrev && starts_with("no-", arg)) {
-                               flags |= OPT_UNSET;
-                               goto is_abbreviated;
-                       }
-                       /* negated? */
-                       if (!starts_with(arg, "no-"))
-                               continue;
-                       flags |= OPT_UNSET;
-                       if (!skip_prefix(arg + 3, long_name, &rest)) {
-                               /* abbreviated and negated? */
-                               if (allow_abbrev &&
-                                   starts_with(long_name, arg + 3))
-                                       goto is_abbreviated;
-                               else
-                                       continue;
-                       }
-               }
-               if (*rest) {
-                       if (*rest != '=')
+               if (((flags ^ opt_flags) & OPT_UNSET) && !allow_unset)
+                       continue;
+
+               if (skip_prefix(arg_start, long_name, &rest)) {
+                       if (*rest == '=')
+                               p->opt = rest + 1;
+                       else if (*rest)
                                continue;
-                       p->opt = rest + 1;
+                       return get_value(p, options, flags ^ opt_flags);
                }
-               return get_value(p, options, flags ^ opt_flags);
+
+               /* abbreviated? */
+               if (!strncmp(long_name, arg_start, arg_end - arg_start))
+                       register_abbrev(p, options, flags ^ opt_flags,
+                                       &abbrev, &ambiguous);
+
+               /* negated and abbreviated very much? */
+               if (allow_unset && starts_with("no-", arg))
+                       register_abbrev(p, options, OPT_UNSET ^ opt_flags,
+                                       &abbrev, &ambiguous);
        }
 
-       if (disallow_abbreviated_options && (ambiguous_option || abbrev_option))
+       if (disallow_abbreviated_options && (ambiguous.option || abbrev.option))
                die("disallowed abbreviated or ambiguous option '%.*s'",
                    (int)(arg_end - arg), arg);
 
-       if (ambiguous_option) {
+       if (ambiguous.option) {
                error(_("ambiguous option: %s "
                        "(could be --%s%s or --%s%s)"),
                        arg,
-                       (ambiguous_flags & OPT_UNSET) ?  "no-" : "",
-                       ambiguous_option->long_name,
-                       (abbrev_flags & OPT_UNSET) ?  "no-" : "",
-                       abbrev_option->long_name);
+                       (ambiguous.flags & OPT_UNSET) ?  "no-" : "",
+                       ambiguous.option->long_name,
+                       (abbrev.flags & OPT_UNSET) ?  "no-" : "",
+                       abbrev.option->long_name);
                return PARSE_OPT_HELP;
        }
-       if (abbrev_option)
-               return get_value(p, abbrev_option, abbrev_flags);
+       if (abbrev.option) {
+               if (*arg_end)
+                       p->opt = arg_end + 1;
+               return get_value(p, abbrev.option, abbrev.flags);
+       }
        return PARSE_OPT_UNKNOWN;
 }
 
index cf964b060cd128e2bc271c07ed8bb8b4c28bfe29..bdbed4295aab2f0309eb872170251e2e9b185bbf 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1759,7 +1759,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                                goto trailer_out;
                }
                if (*arg == ')') {
-                       format_trailers_from_commit(sb, msg + c->subject_off, &opts);
+                       format_trailers_from_commit(&opts, msg + c->subject_off, sb);
                        ret = arg - placeholder + 1;
                }
        trailer_out:
index 0ec29f7385480ad4ee71a6cc684307b2bf19c814..03542d023686f8b9d7089dddef0d17caf6c1ae44 100644 (file)
@@ -1991,7 +1991,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp
                        struct strbuf s = STRBUF_INIT;
 
                        /* Format the trailer info according to the trailer_opts given */
-                       format_trailers_from_commit(&s, subpos, &atom->u.contents.trailer_opts);
+                       format_trailers_from_commit(&atom->u.contents.trailer_opts, subpos, &s);
 
                        v->s = strbuf_detach(&s, NULL);
                } else if (atom->u.contents.option == C_BARE)
index 2c88bbd448b18785200a83b7a9e4e794cc364d60..e206d5a073ced9c24d9020f03a674ca207abdd06 100644 (file)
@@ -171,23 +171,6 @@ static int should_write_log(struct ref_store *refs, const char *refname)
        }
 }
 
-static void clear_reftable_log_record(struct reftable_log_record *log)
-{
-       switch (log->value_type) {
-       case REFTABLE_LOG_UPDATE:
-               /*
-                * When we write log records, the hashes are owned by the
-                * caller and thus shouldn't be free'd.
-                */
-               log->value.update.old_hash = NULL;
-               log->value.update.new_hash = NULL;
-               break;
-       case REFTABLE_LOG_DELETION:
-               break;
-       }
-       reftable_log_record_release(log);
-}
-
 static void fill_reftable_log_record(struct reftable_log_record *log)
 {
        const char *info = git_committer_info(0);
@@ -346,6 +329,7 @@ struct reftable_ref_iterator {
        struct object_id oid;
 
        const char *prefix;
+       size_t prefix_len;
        unsigned int flags;
        int err;
 };
@@ -374,8 +358,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
                        continue;
                }
 
-               if (iter->prefix &&
-                   strncmp(iter->prefix, iter->ref.refname, strlen(iter->prefix))) {
+               if (iter->prefix_len &&
+                   strncmp(iter->prefix, iter->ref.refname, iter->prefix_len)) {
                        iter->err = 1;
                        break;
                }
@@ -484,6 +468,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
        iter = xcalloc(1, sizeof(*iter));
        base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);
        iter->prefix = prefix;
+       iter->prefix_len = prefix ? strlen(prefix) : 0;
        iter->base.oid = &iter->oid;
        iter->flags = flags;
        iter->refs = refs;
@@ -1104,8 +1089,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
                        fill_reftable_log_record(log);
                        log->update_index = ts;
                        log->refname = xstrdup(u->refname);
-                       log->value.update.new_hash = u->new_oid.hash;
-                       log->value.update.old_hash = tx_update->current_oid.hash;
+                       memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
+                       memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
                        log->value.update.message =
                                xstrndup(u->msg, arg->refs->write_options.block_size / 2);
                }
@@ -1160,7 +1145,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
 done:
        assert(ret != REFTABLE_API_ERROR);
        for (i = 0; i < logs_nr; i++)
-               clear_reftable_log_record(&logs[i]);
+               reftable_log_record_release(&logs[i]);
        free(logs);
        return ret;
 }
@@ -1277,13 +1262,13 @@ static int write_create_symref_table(struct reftable_writer *writer, void *cb_da
        log.update_index = ts;
        log.value.update.message = xstrndup(create->logmsg,
                                            create->refs->write_options.block_size / 2);
-       log.value.update.new_hash = new_oid.hash;
+       memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
        if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
                                    RESOLVE_REF_READING, &old_oid, NULL))
-               log.value.update.old_hash = old_oid.hash;
+               memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
 
        ret = reftable_writer_add_log(writer, &log);
-       clear_reftable_log_record(&log);
+       reftable_log_record_release(&log);
        return ret;
 }
 
@@ -1422,7 +1407,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
                logs[logs_nr].update_index = deletion_ts;
                logs[logs_nr].value.update.message =
                        xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
-               logs[logs_nr].value.update.old_hash = old_ref.value.val1;
+               memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ);
                logs_nr++;
 
                ret = read_ref_without_reload(arg->stack, "HEAD", &head_oid, &head_referent, &head_type);
@@ -1454,7 +1439,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)
        logs[logs_nr].update_index = creation_ts;
        logs[logs_nr].value.update.message =
                xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);
-       logs[logs_nr].value.update.new_hash = old_ref.value.val1;
+       memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ);
        logs_nr++;
 
        /*
@@ -1517,10 +1502,6 @@ done:
        for (i = 0; i < logs_nr; i++) {
                if (!strcmp(logs[i].refname, "HEAD"))
                        continue;
-               if (logs[i].value.update.old_hash == old_ref.value.val1)
-                       logs[i].value.update.old_hash = NULL;
-               if (logs[i].value.update.new_hash == old_ref.value.val1)
-                       logs[i].value.update.new_hash = NULL;
                logs[i].refname = NULL;
                reftable_log_record_release(&logs[i]);
        }
@@ -1598,7 +1579,7 @@ struct reftable_reflog_iterator {
        struct reftable_ref_store *refs;
        struct reftable_iterator iter;
        struct reftable_log_record log;
-       char *last_name;
+       struct strbuf last_name;
        int err;
 };
 
@@ -1617,15 +1598,15 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
                 * we've already produced this name. This could be faster by
                 * seeking directly to reflog@update_index==0.
                 */
-               if (iter->last_name && !strcmp(iter->log.refname, iter->last_name))
+               if (!strcmp(iter->log.refname, iter->last_name.buf))
                        continue;
 
                if (check_refname_format(iter->log.refname,
                                         REFNAME_ALLOW_ONELEVEL))
                        continue;
 
-               free(iter->last_name);
-               iter->last_name = xstrdup(iter->log.refname);
+               strbuf_reset(&iter->last_name);
+               strbuf_addstr(&iter->last_name, iter->log.refname);
                iter->base.refname = iter->log.refname;
 
                break;
@@ -1658,7 +1639,7 @@ static int reftable_reflog_iterator_abort(struct ref_iterator *ref_iterator)
                (struct reftable_reflog_iterator *)ref_iterator;
        reftable_log_record_release(&iter->log);
        reftable_iterator_destroy(&iter->iter);
-       free(iter->last_name);
+       strbuf_release(&iter->last_name);
        free(iter);
        return ITER_DONE;
 }
@@ -1678,13 +1659,14 @@ static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftabl
 
        iter = xcalloc(1, sizeof(*iter));
        base_ref_iterator_init(&iter->base, &reftable_reflog_iterator_vtable);
+       strbuf_init(&iter->last_name, 0);
        iter->refs = refs;
 
        ret = refs->err;
        if (ret)
                goto done;
 
-       ret = reftable_stack_reload(refs->main_stack);
+       ret = reftable_stack_reload(stack);
        if (ret < 0)
                goto done;
 
@@ -2182,7 +2164,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
                        dest->value_type = REFTABLE_LOG_DELETION;
                } else {
                        if ((flags & EXPIRE_REFLOGS_REWRITE) && last_hash)
-                               dest->value.update.old_hash = last_hash;
+                               memcpy(dest->value.update.old_hash, last_hash, GIT_MAX_RAWSZ);
                        last_hash = logs[i].value.update.new_hash;
                }
        }
index 72eb73b3806d623010c0e224f4f8ce77d555ce43..e2a2cee58d2a35c6183e7083d3320d2520a397f4 100644 (file)
@@ -291,9 +291,8 @@ static int restart_key_less(size_t idx, void *args)
        /* the restart key is verbatim in the block, so this could avoid the
           alloc for decoding the key */
        struct strbuf rkey = STRBUF_INIT;
-       struct strbuf last_key = STRBUF_INIT;
        uint8_t unused_extra;
-       int n = reftable_decode_key(&rkey, &unused_extra, last_key, in);
+       int n = reftable_decode_key(&rkey, &unused_extra, in);
        int result;
        if (n < 0) {
                a->error = 1;
@@ -302,7 +301,7 @@ static int restart_key_less(size_t idx, void *args)
 
        result = strbuf_cmp(&a->key, &rkey);
        strbuf_release(&rkey);
-       return result;
+       return result < 0;
 }
 
 void block_iter_copy_from(struct block_iter *dest, struct block_iter *src)
@@ -326,35 +325,35 @@ int block_iter_next(struct block_iter *it, struct reftable_record *rec)
        if (it->next_off >= it->br->block_len)
                return 1;
 
-       n = reftable_decode_key(&it->key, &extra, it->last_key, in);
+       n = reftable_decode_key(&it->last_key, &extra, in);
        if (n < 0)
                return -1;
-
-       if (!it->key.len)
+       if (!it->last_key.len)
                return REFTABLE_FORMAT_ERROR;
 
        string_view_consume(&in, n);
-       n = reftable_record_decode(rec, it->key, extra, in, it->br->hash_size);
+       n = reftable_record_decode(rec, it->last_key, extra, in, it->br->hash_size,
+                                  &it->scratch);
        if (n < 0)
                return -1;
        string_view_consume(&in, n);
 
-       strbuf_swap(&it->last_key, &it->key);
        it->next_off += start.len - in.len;
        return 0;
 }
 
 int block_reader_first_key(struct block_reader *br, struct strbuf *key)
 {
-       struct strbuf empty = STRBUF_INIT;
-       int off = br->header_off + 4;
+       int off = br->header_off + 4, n;
        struct string_view in = {
                .buf = br->block.data + off,
                .len = br->block_len - off,
        };
-
        uint8_t extra = 0;
-       int n = reftable_decode_key(key, &extra, empty, in);
+
+       strbuf_reset(key);
+
+       n = reftable_decode_key(key, &extra, in);
        if (n < 0)
                return n;
        if (!key->len)
@@ -371,7 +370,7 @@ int block_iter_seek(struct block_iter *it, struct strbuf *want)
 void block_iter_close(struct block_iter *it)
 {
        strbuf_release(&it->last_key);
-       strbuf_release(&it->key);
+       strbuf_release(&it->scratch);
 }
 
 int block_reader_seek(struct block_reader *br, struct block_iter *it,
@@ -408,8 +407,8 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
                if (err < 0)
                        goto done;
 
-               reftable_record_key(&rec, &it->key);
-               if (err > 0 || strbuf_cmp(&it->key, want) >= 0) {
+               reftable_record_key(&rec, &it->last_key);
+               if (err > 0 || strbuf_cmp(&it->last_key, want) >= 0) {
                        err = 0;
                        goto done;
                }
index 17481e6331979cc31972ee3dad576e9c594a1769..47acc62c0ab8cdd6816d49152312b1e61dfe3d6d 100644 (file)
@@ -84,12 +84,12 @@ struct block_iter {
 
        /* key for last entry we read. */
        struct strbuf last_key;
-       struct strbuf key;
+       struct strbuf scratch;
 };
 
 #define BLOCK_ITER_INIT { \
        .last_key = STRBUF_INIT, \
-       .key = STRBUF_INIT, \
+       .scratch = STRBUF_INIT, \
 }
 
 /* initializes a block reader. */
index 8b5ebf6183a97f3ea16a0773880ccd3b835ef68e..7aa30c4a51e54ec5442b9d8a3bf90b8119c5a4f0 100644 (file)
@@ -16,11 +16,6 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reader.h"
 #include "reftable-error.h"
 
-int iterator_is_null(struct reftable_iterator *it)
-{
-       return !it->ops;
-}
-
 static void filtering_ref_iterator_close(void *iter_arg)
 {
        struct filtering_ref_iterator *fri = iter_arg;
index 47d67d84df679c522ce50d1d7aade2d1be683b5a..537431baba075ad72614b1c68eff1fd28fa51288 100644 (file)
@@ -16,10 +16,6 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-iterator.h"
 #include "reftable-generic.h"
 
-/* Returns true for a zeroed out iterator, such as the one returned from
- * iterator_destroy. */
-int iterator_is_null(struct reftable_iterator *it);
-
 /* iterator that produces only ref records that point to `oid` */
 struct filtering_ref_iterator {
        int double_check;
index 1aa6cd31b74954da9b1b5140cd350459c37c1378..f85a24c6786c6fb8e102ffc9ceb35f23706c8c9b 100644 (file)
@@ -17,23 +17,37 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-error.h"
 #include "system.h"
 
+struct merged_subiter {
+       struct reftable_iterator iter;
+       struct reftable_record rec;
+};
+
+struct merged_iter {
+       struct merged_subiter *subiters;
+       struct merged_iter_pqueue pq;
+       uint32_t hash_id;
+       size_t stack_len;
+       uint8_t typ;
+       int suppress_deletions;
+       ssize_t advance_index;
+};
+
 static int merged_iter_init(struct merged_iter *mi)
 {
        for (size_t i = 0; i < mi->stack_len; i++) {
                struct pq_entry e = {
                        .index = i,
+                       .rec = &mi->subiters[i].rec,
                };
                int err;
 
-               reftable_record_init(&e.rec, mi->typ);
-               err = iterator_next(&mi->stack[i], &e.rec);
+               reftable_record_init(&mi->subiters[i].rec, mi->typ);
+               err = iterator_next(&mi->subiters[i].iter,
+                                   &mi->subiters[i].rec);
                if (err < 0)
                        return err;
-               if (err > 0) {
-                       reftable_iterator_destroy(&mi->stack[i]);
-                       reftable_record_release(&e.rec);
+               if (err > 0)
                        continue;
-               }
 
                merged_iter_pqueue_add(&mi->pq, &e);
        }
@@ -46,54 +60,66 @@ static void merged_iter_close(void *p)
        struct merged_iter *mi = p;
 
        merged_iter_pqueue_release(&mi->pq);
-       for (size_t i = 0; i < mi->stack_len; i++)
-               reftable_iterator_destroy(&mi->stack[i]);
-       reftable_free(mi->stack);
+       for (size_t i = 0; i < mi->stack_len; i++) {
+               reftable_iterator_destroy(&mi->subiters[i].iter);
+               reftable_record_release(&mi->subiters[i].rec);
+       }
+       reftable_free(mi->subiters);
 }
 
-static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
-                                              size_t idx)
+static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)
 {
        struct pq_entry e = {
                .index = idx,
+               .rec = &mi->subiters[idx].rec,
        };
        int err;
 
-       reftable_record_init(&e.rec, mi->typ);
-       err = iterator_next(&mi->stack[idx], &e.rec);
-       if (err < 0)
+       err = iterator_next(&mi->subiters[idx].iter, &mi->subiters[idx].rec);
+       if (err)
                return err;
 
-       if (err > 0) {
-               reftable_iterator_destroy(&mi->stack[idx]);
-               reftable_record_release(&e.rec);
-               return 0;
-       }
-
        merged_iter_pqueue_add(&mi->pq, &e);
        return 0;
 }
 
-static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)
-{
-       if (iterator_is_null(&mi->stack[idx]))
-               return 0;
-       return merged_iter_advance_nonnull_subiter(mi, idx);
-}
-
 static int merged_iter_next_entry(struct merged_iter *mi,
                                  struct reftable_record *rec)
 {
        struct pq_entry entry = { 0 };
-       int err = 0;
+       int err = 0, empty;
+
+       empty = merged_iter_pqueue_is_empty(mi->pq);
+
+       if (mi->advance_index >= 0) {
+               /*
+                * When there are no pqueue entries then we only have a single
+                * subiter left. There is no need to use the pqueue in that
+                * case anymore as we know that the subiter will return entries
+                * in the correct order already.
+                *
+                * While this may sound like a very specific edge case, it may
+                * happen more frequently than you think. Most repositories
+                * will end up having a single large base table that contains
+                * most of the refs. It's thus likely that we exhaust all
+                * subiters but the one from that base ref.
+                */
+               if (empty)
+                       return iterator_next(&mi->subiters[mi->advance_index].iter,
+                                            rec);
+
+               err = merged_iter_advance_subiter(mi, mi->advance_index);
+               if (err < 0)
+                       return err;
+               if (!err)
+                       empty = 0;
+               mi->advance_index = -1;
+       }
 
-       if (merged_iter_pqueue_is_empty(mi->pq))
+       if (empty)
                return 1;
 
        entry = merged_iter_pqueue_remove(&mi->pq);
-       err = merged_iter_advance_subiter(mi, entry.index);
-       if (err < 0)
-               return err;
 
        /*
          One can also use reftable as datacenter-local storage, where the ref
@@ -107,56 +133,34 @@ static int merged_iter_next_entry(struct merged_iter *mi,
                struct pq_entry top = merged_iter_pqueue_top(mi->pq);
                int cmp;
 
-               /*
-                * When the next entry comes from the same queue as the current
-                * entry then it must by definition be larger. This avoids a
-                * comparison in the most common case.
-                */
-               if (top.index == entry.index)
-                       break;
-
-               cmp = reftable_record_cmp(&top.rec, &entry.rec);
+               cmp = reftable_record_cmp(top.rec, entry.rec);
                if (cmp > 0)
                        break;
 
                merged_iter_pqueue_remove(&mi->pq);
                err = merged_iter_advance_subiter(mi, top.index);
                if (err < 0)
-                       goto done;
-               reftable_record_release(&top.rec);
+                       return err;
        }
 
-       reftable_record_release(rec);
-       *rec = entry.rec;
-
-done:
-       if (err)
-               reftable_record_release(&entry.rec);
-       return err;
+       mi->advance_index = entry.index;
+       SWAP(*rec, *entry.rec);
+       return 0;
 }
 
-static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)
+static int merged_iter_next_void(void *p, struct reftable_record *rec)
 {
+       struct merged_iter *mi = p;
        while (1) {
                int err = merged_iter_next_entry(mi, rec);
-               if (err == 0 && mi->suppress_deletions &&
-                   reftable_record_is_deletion(rec)) {
+               if (err)
+                       return err;
+               if (mi->suppress_deletions && reftable_record_is_deletion(rec))
                        continue;
-               }
-
-               return err;
+               return 0;
        }
 }
 
-static int merged_iter_next_void(void *p, struct reftable_record *rec)
-{
-       struct merged_iter *mi = p;
-       if (merged_iter_pqueue_is_empty(mi->pq))
-               return 1;
-
-       return merged_iter_next(mi, rec);
-}
-
 static struct reftable_iterator_vtable merged_iter_vtable = {
        .next = &merged_iter_next_void,
        .close = &merged_iter_close,
@@ -246,14 +250,15 @@ static int merged_table_seek_record(struct reftable_merged_table *mt,
                .typ = reftable_record_type(rec),
                .hash_id = mt->hash_id,
                .suppress_deletions = mt->suppress_deletions,
+               .advance_index = -1,
        };
        struct merged_iter *p;
        int err;
 
-       REFTABLE_CALLOC_ARRAY(merged.stack, mt->stack_len);
+       REFTABLE_CALLOC_ARRAY(merged.subiters, mt->stack_len);
        for (size_t i = 0; i < mt->stack_len; i++) {
                err = reftable_table_seek_record(&mt->stack[i],
-                                                &merged.stack[merged.stack_len], rec);
+                                                &merged.subiters[merged.stack_len].iter, rec);
                if (err < 0)
                        goto out;
                if (!err)
index 7d9f95d27ed0a44c4208e743b6b83ba4cdc5536c..a2571dbc99d68c9954e9d07372bae984cf5ccccc 100644 (file)
@@ -9,7 +9,7 @@ https://developers.google.com/open-source/licenses/bsd
 #ifndef MERGED_H
 #define MERGED_H
 
-#include "pq.h"
+#include "system.h"
 
 struct reftable_merged_table {
        struct reftable_table *stack;
@@ -24,15 +24,6 @@ struct reftable_merged_table {
        uint64_t max;
 };
 
-struct merged_iter {
-       struct reftable_iterator *stack;
-       uint32_t hash_id;
-       size_t stack_len;
-       uint8_t typ;
-       int suppress_deletions;
-       struct merged_iter_pqueue pq;
-};
-
 void merged_table_release(struct reftable_merged_table *mt);
 
 #endif
index d0f77a3b8f51e0abb9af1162106c757d3fc3e553..530fc82d1c2458d57826281d4865bc0aa4127adf 100644 (file)
@@ -289,16 +289,13 @@ merged_table_from_log_records(struct reftable_log_record **logs,
 
 static void test_merged_logs(void)
 {
-       uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
-       uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
-       uint8_t hash3[GIT_SHA1_RAWSZ] = { 3 };
        struct reftable_log_record r1[] = {
                {
                        .refname = "a",
                        .update_index = 2,
                        .value_type = REFTABLE_LOG_UPDATE,
                        .value.update = {
-                               .old_hash = hash2,
+                               .old_hash = { 2 },
                                /* deletion */
                                .name = "jane doe",
                                .email = "jane@invalid",
@@ -310,8 +307,8 @@ static void test_merged_logs(void)
                        .update_index = 1,
                        .value_type = REFTABLE_LOG_UPDATE,
                        .value.update = {
-                               .old_hash = hash1,
-                               .new_hash = hash2,
+                               .old_hash = { 1 },
+                               .new_hash = { 2 },
                                .name = "jane doe",
                                .email = "jane@invalid",
                                .message = "message1",
@@ -324,7 +321,7 @@ static void test_merged_logs(void)
                        .update_index = 3,
                        .value_type = REFTABLE_LOG_UPDATE,
                        .value.update = {
-                               .new_hash = hash3,
+                               .new_hash = { 3 },
                                .name = "jane doe",
                                .email = "jane@invalid",
                                .message = "message3",
index e0ccce2b9779a8bfd1acac6d42bc7d0e46e3fe0e..7fb45d8c60dbca6106c51732005ce15c59d2c7e0 100644 (file)
@@ -14,22 +14,12 @@ https://developers.google.com/open-source/licenses/bsd
 
 int pq_less(struct pq_entry *a, struct pq_entry *b)
 {
-       int cmp = reftable_record_cmp(&a->rec, &b->rec);
+       int cmp = reftable_record_cmp(a->rec, b->rec);
        if (cmp == 0)
                return a->index > b->index;
        return cmp < 0;
 }
 
-struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
-{
-       return pq.heap[0];
-}
-
-int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
-{
-       return pq.len == 0;
-}
-
 struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
 {
        int i = 0;
@@ -82,10 +72,6 @@ void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry
 
 void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
 {
-       int i = 0;
-       for (i = 0; i < pq->len; i++) {
-               reftable_record_release(&pq->heap[i].rec);
-       }
        FREE_AND_NULL(pq->heap);
-       pq->len = pq->cap = 0;
+       memset(pq, 0, sizeof(*pq));
 }
index e85bac9b52e0039bd378cb1073215af4d48196c7..f796c2317948be2c82aaa15c29b5e9aba1730168 100644 (file)
@@ -12,8 +12,8 @@ https://developers.google.com/open-source/licenses/bsd
 #include "record.h"
 
 struct pq_entry {
-       int index;
-       struct reftable_record rec;
+       size_t index;
+       struct reftable_record *rec;
 };
 
 struct merged_iter_pqueue {
@@ -22,12 +22,20 @@ struct merged_iter_pqueue {
        size_t cap;
 };
 
-struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq);
-int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq);
 void merged_iter_pqueue_check(struct merged_iter_pqueue pq);
 struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq);
 void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry *e);
 void merged_iter_pqueue_release(struct merged_iter_pqueue *pq);
 int pq_less(struct pq_entry *a, struct pq_entry *b);
 
+static inline struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
+{
+       return pq.heap[0];
+}
+
+static inline int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
+{
+       return pq.len == 0;
+}
+
 #endif
index c202eff84801820b28c56ddb7de28a2d041f3c08..b7d3c80cc7260298749800696e4a8bcc5be3284c 100644 (file)
@@ -27,48 +27,43 @@ void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
 
 static void test_pq(void)
 {
-       char *names[54] = { NULL };
-       int N = ARRAY_SIZE(names) - 1;
-
        struct merged_iter_pqueue pq = { NULL };
+       struct reftable_record recs[54];
+       int N = ARRAY_SIZE(recs) - 1, i;
        char *last = NULL;
 
-       int i = 0;
        for (i = 0; i < N; i++) {
-               char name[100];
-               snprintf(name, sizeof(name), "%02d", i);
-               names[i] = xstrdup(name);
+               struct strbuf refname = STRBUF_INIT;
+               strbuf_addf(&refname, "%02d", i);
+
+               reftable_record_init(&recs[i], BLOCK_TYPE_REF);
+               recs[i].u.ref.refname = strbuf_detach(&refname, NULL);
        }
 
        i = 1;
        do {
-               struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
-                                              .u.ref = {
-                                                      .refname = names[i],
-                                              } } };
+               struct pq_entry e = {
+                       .rec = &recs[i],
+               };
+
                merged_iter_pqueue_add(&pq, &e);
                merged_iter_pqueue_check(pq);
+
                i = (i * 7) % N;
        } while (i != 1);
 
        while (!merged_iter_pqueue_is_empty(pq)) {
                struct pq_entry e = merged_iter_pqueue_remove(&pq);
-               struct reftable_record *rec = &e.rec;
                merged_iter_pqueue_check(pq);
 
-               EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
-               if (last) {
-                       EXPECT(strcmp(last, rec->u.ref.refname) < 0);
-               }
-               /* this is names[i], so don't dealloc. */
-               last = rec->u.ref.refname;
-               rec->u.ref.refname = NULL;
-               reftable_record_release(rec);
-       }
-       for (i = 0; i < N; i++) {
-               reftable_free(names[i]);
+               EXPECT(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
+               if (last)
+                       EXPECT(strcmp(last, e.rec->u.ref.refname) < 0);
+               last = e.rec->u.ref.refname;
        }
 
+       for (i = 0; i < N; i++)
+               reftable_record_release(&recs[i]);
        merged_iter_pqueue_release(&pq);
 }
 
index 363fe0f998b91f72002f819c396893b8086dc992..a6dbd214c5d2f8c9a82632776691fe1c292533bd 100644 (file)
@@ -77,18 +77,15 @@ static void write_table(char ***names, struct strbuf *buf, int N,
        }
 
        for (i = 0; i < N; i++) {
-               uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
                char name[100];
                int n;
 
-               set_test_hash(hash, i);
-
                snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
 
                log.refname = name;
                log.update_index = update_index;
                log.value_type = REFTABLE_LOG_UPDATE;
-               log.value.update.new_hash = hash;
+               set_test_hash(log.value.update.new_hash, i);
                log.value.update.message = "message";
 
                n = reftable_writer_add_log(w, &log);
@@ -137,13 +134,10 @@ static void test_log_buffer_size(void)
        /* This tests buffer extension for log compression. Must use a random
           hash, to ensure that the compressed part is larger than the original.
        */
-       uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
        for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
-               hash1[i] = (uint8_t)(git_rand() % 256);
-               hash2[i] = (uint8_t)(git_rand() % 256);
+               log.value.update.old_hash[i] = (uint8_t)(git_rand() % 256);
+               log.value.update.new_hash[i] = (uint8_t)(git_rand() % 256);
        }
-       log.value.update.old_hash = hash1;
-       log.value.update.new_hash = hash2;
        reftable_writer_set_limits(w, update_index, update_index);
        err = reftable_writer_add_log(w, &log);
        EXPECT_ERR(err);
@@ -161,25 +155,26 @@ static void test_log_overflow(void)
                .block_size = ARRAY_SIZE(msg),
        };
        int err;
-       struct reftable_log_record
-               log = { .refname = "refs/heads/master",
-                       .update_index = 0xa,
-                       .value_type = REFTABLE_LOG_UPDATE,
-                       .value = { .update = {
-                                          .name = "Han-Wen Nienhuys",
-                                          .email = "hanwen@google.com",
-                                          .tz_offset = 100,
-                                          .time = 0x5e430672,
-                                          .message = msg,
-                                  } } };
+       struct reftable_log_record log = {
+               .refname = "refs/heads/master",
+               .update_index = 0xa,
+               .value_type = REFTABLE_LOG_UPDATE,
+               .value = {
+                       .update = {
+                               .old_hash = { 1 },
+                               .new_hash = { 2 },
+                               .name = "Han-Wen Nienhuys",
+                               .email = "hanwen@google.com",
+                               .tz_offset = 100,
+                               .time = 0x5e430672,
+                               .message = msg,
+                       },
+               },
+       };
        struct reftable_writer *w =
                reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
-       uint8_t hash1[GIT_SHA1_RAWSZ]  = {1}, hash2[GIT_SHA1_RAWSZ] = { 2 };
-
        memset(msg, 'x', sizeof(msg) - 1);
-       log.value.update.old_hash = hash1;
-       log.value.update.new_hash = hash2;
        reftable_writer_set_limits(w, update_index, update_index);
        err = reftable_writer_add_log(w, &log);
        EXPECT(err == REFTABLE_ENTRY_TOO_BIG_ERROR);
@@ -219,16 +214,13 @@ static void test_log_write_read(void)
                EXPECT_ERR(err);
        }
        for (i = 0; i < N; i++) {
-               uint8_t hash1[GIT_SHA1_RAWSZ], hash2[GIT_SHA1_RAWSZ];
                struct reftable_log_record log = { NULL };
-               set_test_hash(hash1, i);
-               set_test_hash(hash2, i + 1);
 
                log.refname = names[i];
                log.update_index = i;
                log.value_type = REFTABLE_LOG_UPDATE;
-               log.value.update.old_hash = hash1;
-               log.value.update.new_hash = hash2;
+               set_test_hash(log.value.update.old_hash, i);
+               set_test_hash(log.value.update.new_hash, i + 1);
 
                err = reftable_writer_add_log(w, &log);
                EXPECT_ERR(err);
@@ -298,18 +290,15 @@ static void test_log_zlib_corruption(void)
        struct reftable_writer *w =
                reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        const struct reftable_stats *stats = NULL;
-       uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
-       uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
        char message[100] = { 0 };
        int err, i, n;
-
        struct reftable_log_record log = {
                .refname = "refname",
                .value_type = REFTABLE_LOG_UPDATE,
                .value = {
                        .update = {
-                               .new_hash = hash1,
-                               .old_hash = hash2,
+                               .new_hash = { 1 },
+                               .old_hash = { 2 },
                                .name = "My Name",
                                .email = "myname@invalid",
                                .message = message,
@@ -821,13 +810,12 @@ static void test_write_multiple_indices(void)
        }
 
        for (i = 0; i < 100; i++) {
-               unsigned char hash[GIT_SHA1_RAWSZ] = {i};
                struct reftable_log_record log = {
                        .update_index = 1,
                        .value_type = REFTABLE_LOG_UPDATE,
                        .value.update = {
-                               .old_hash = hash,
-                               .new_hash = hash,
+                               .old_hash = { i },
+                               .new_hash = { i },
                        },
                };
 
index d6bb42e887423d95f2fcc1b2b146505ab4bef62a..23b497adab8b3478026959a802bddb58fb1c6002 100644 (file)
@@ -159,20 +159,19 @@ int reftable_encode_key(int *restart, struct string_view dest,
        return start.len - dest.len;
 }
 
-int reftable_decode_key(struct strbuf *key, uint8_t *extra,
-                       struct strbuf last_key, struct string_view in)
+int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
+                       struct string_view in)
 {
        int start_len = in.len;
        uint64_t prefix_len = 0;
        uint64_t suffix_len = 0;
-       int n = get_var_int(&prefix_len, &in);
+       int n;
+
+       n = get_var_int(&prefix_len, &in);
        if (n < 0)
                return -1;
        string_view_consume(&in, n);
 
-       if (prefix_len > last_key.len)
-               return -1;
-
        n = get_var_int(&suffix_len, &in);
        if (n <= 0)
                return -1;
@@ -181,12 +180,12 @@ int reftable_decode_key(struct strbuf *key, uint8_t *extra,
        *extra = (uint8_t)(suffix_len & 0x7);
        suffix_len >>= 3;
 
-       if (in.len < suffix_len)
+       if (in.len < suffix_len ||
+           prefix_len > last_key->len)
                return -1;
 
-       strbuf_reset(key);
-       strbuf_add(key, last_key.buf, prefix_len);
-       strbuf_add(key, in.buf, suffix_len);
+       strbuf_setlen(last_key, prefix_len);
+       strbuf_add(last_key, in.buf, suffix_len);
        string_view_consume(&in, suffix_len);
 
        return start_len - in.len;
@@ -205,14 +204,26 @@ static void reftable_ref_record_copy_from(void *rec, const void *src_rec,
 {
        struct reftable_ref_record *ref = rec;
        const struct reftable_ref_record *src = src_rec;
+       char *refname = NULL;
+       size_t refname_cap = 0;
+
        assert(hash_size > 0);
 
-       /* This is simple and correct, but we could probably reuse the hash
-        * fields. */
+       SWAP(refname, ref->refname);
+       SWAP(refname_cap, ref->refname_cap);
        reftable_ref_record_release(ref);
+       SWAP(ref->refname, refname);
+       SWAP(ref->refname_cap, refname_cap);
+
        if (src->refname) {
-               ref->refname = xstrdup(src->refname);
+               size_t refname_len = strlen(src->refname);
+
+               REFTABLE_ALLOC_GROW(ref->refname, refname_len + 1,
+                                   ref->refname_cap);
+               memcpy(ref->refname, src->refname, refname_len);
+               ref->refname[refname_len] = 0;
        }
+
        ref->update_index = src->update_index;
        ref->value_type = src->value_type;
        switch (src->value_type) {
@@ -363,21 +374,29 @@ static int reftable_ref_record_encode(const void *rec, struct string_view s,
 
 static int reftable_ref_record_decode(void *rec, struct strbuf key,
                                      uint8_t val_type, struct string_view in,
-                                     int hash_size)
+                                     int hash_size, struct strbuf *scratch)
 {
        struct reftable_ref_record *r = rec;
        struct string_view start = in;
        uint64_t update_index = 0;
-       int n = get_var_int(&update_index, &in);
+       const char *refname = NULL;
+       size_t refname_cap = 0;
+       int n;
+
+       assert(hash_size > 0);
+
+       n = get_var_int(&update_index, &in);
        if (n < 0)
                return n;
        string_view_consume(&in, n);
 
+       SWAP(refname, r->refname);
+       SWAP(refname_cap, r->refname_cap);
        reftable_ref_record_release(r);
+       SWAP(r->refname, refname);
+       SWAP(r->refname_cap, refname_cap);
 
-       assert(hash_size > 0);
-
-       r->refname = reftable_malloc(key.len + 1);
+       REFTABLE_ALLOC_GROW(r->refname, key.len + 1, r->refname_cap);
        memcpy(r->refname, key.buf, key.len);
        r->refname[key.len] = 0;
 
@@ -406,13 +425,12 @@ static int reftable_ref_record_decode(void *rec, struct strbuf key,
                break;
 
        case REFTABLE_REF_SYMREF: {
-               struct strbuf dest = STRBUF_INIT;
-               int n = decode_string(&dest, in);
+               int n = decode_string(scratch, in);
                if (n < 0) {
                        return -1;
                }
                string_view_consume(&in, n);
-               r->value.symref = dest.buf;
+               r->value.symref = strbuf_detach(scratch, NULL);
        } break;
 
        case REFTABLE_REF_DELETION:
@@ -560,7 +578,7 @@ static int reftable_obj_record_encode(const void *rec, struct string_view s,
 
 static int reftable_obj_record_decode(void *rec, struct strbuf key,
                                      uint8_t val_type, struct string_view in,
-                                     int hash_size)
+                                     int hash_size, struct strbuf *scratch UNUSED)
 {
        struct string_view start = in;
        struct reftable_obj_record *r = rec;
@@ -569,6 +587,8 @@ static int reftable_obj_record_decode(void *rec, struct strbuf key,
        uint64_t last;
        int j;
 
+       reftable_obj_record_release(r);
+
        REFTABLE_ALLOC_ARRAY(r->hash_prefix, key.len);
        memcpy(r->hash_prefix, key.buf, key.len);
        r->hash_prefix_len = key.len;
@@ -744,16 +764,10 @@ static void reftable_log_record_copy_from(void *rec, const void *src_rec,
                                xstrdup(dst->value.update.message);
                }
 
-               if (dst->value.update.new_hash) {
-                       REFTABLE_ALLOC_ARRAY(dst->value.update.new_hash, hash_size);
-                       memcpy(dst->value.update.new_hash,
-                              src->value.update.new_hash, hash_size);
-               }
-               if (dst->value.update.old_hash) {
-                       REFTABLE_ALLOC_ARRAY(dst->value.update.old_hash, hash_size);
-                       memcpy(dst->value.update.old_hash,
-                              src->value.update.old_hash, hash_size);
-               }
+               memcpy(dst->value.update.new_hash,
+                      src->value.update.new_hash, hash_size);
+               memcpy(dst->value.update.old_hash,
+                      src->value.update.old_hash, hash_size);
                break;
        }
 }
@@ -771,8 +785,6 @@ void reftable_log_record_release(struct reftable_log_record *r)
        case REFTABLE_LOG_DELETION:
                break;
        case REFTABLE_LOG_UPDATE:
-               reftable_free(r->value.update.new_hash);
-               reftable_free(r->value.update.old_hash);
                reftable_free(r->value.update.name);
                reftable_free(r->value.update.email);
                reftable_free(r->value.update.message);
@@ -789,33 +801,20 @@ static uint8_t reftable_log_record_val_type(const void *rec)
        return reftable_log_record_is_deletion(log) ? 0 : 1;
 }
 
-static uint8_t zero[GIT_SHA256_RAWSZ] = { 0 };
-
 static int reftable_log_record_encode(const void *rec, struct string_view s,
                                      int hash_size)
 {
        const struct reftable_log_record *r = rec;
        struct string_view start = s;
        int n = 0;
-       uint8_t *oldh = NULL;
-       uint8_t *newh = NULL;
        if (reftable_log_record_is_deletion(r))
                return 0;
 
-       oldh = r->value.update.old_hash;
-       newh = r->value.update.new_hash;
-       if (!oldh) {
-               oldh = zero;
-       }
-       if (!newh) {
-               newh = zero;
-       }
-
        if (s.len < 2 * hash_size)
                return -1;
 
-       memcpy(s.buf, oldh, hash_size);
-       memcpy(s.buf + hash_size, newh, hash_size);
+       memcpy(s.buf, r->value.update.old_hash, hash_size);
+       memcpy(s.buf + hash_size, r->value.update.new_hash, hash_size);
        string_view_consume(&s, 2 * hash_size);
 
        n = encode_string(r->value.update.name ? r->value.update.name : "", s);
@@ -851,19 +850,18 @@ static int reftable_log_record_encode(const void *rec, struct string_view s,
 
 static int reftable_log_record_decode(void *rec, struct strbuf key,
                                      uint8_t val_type, struct string_view in,
-                                     int hash_size)
+                                     int hash_size, struct strbuf *scratch)
 {
        struct string_view start = in;
        struct reftable_log_record *r = rec;
        uint64_t max = 0;
        uint64_t ts = 0;
-       struct strbuf dest = STRBUF_INIT;
        int n;
 
        if (key.len <= 9 || key.buf[key.len - 9] != 0)
                return REFTABLE_FORMAT_ERROR;
 
-       r->refname = reftable_realloc(r->refname, key.len - 8);
+       REFTABLE_ALLOC_GROW(r->refname, key.len - 8, r->refname_cap);
        memcpy(r->refname, key.buf, key.len - 8);
        ts = get_be64(key.buf + key.len - 8);
 
@@ -872,9 +870,8 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
        if (val_type != r->value_type) {
                switch (r->value_type) {
                case REFTABLE_LOG_UPDATE:
-                       FREE_AND_NULL(r->value.update.old_hash);
-                       FREE_AND_NULL(r->value.update.new_hash);
                        FREE_AND_NULL(r->value.update.message);
+                       r->value.update.message_cap = 0;
                        FREE_AND_NULL(r->value.update.email);
                        FREE_AND_NULL(r->value.update.name);
                        break;
@@ -890,36 +887,43 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
        if (in.len < 2 * hash_size)
                return REFTABLE_FORMAT_ERROR;
 
-       r->value.update.old_hash =
-               reftable_realloc(r->value.update.old_hash, hash_size);
-       r->value.update.new_hash =
-               reftable_realloc(r->value.update.new_hash, hash_size);
-
        memcpy(r->value.update.old_hash, in.buf, hash_size);
        memcpy(r->value.update.new_hash, in.buf + hash_size, hash_size);
 
        string_view_consume(&in, 2 * hash_size);
 
-       n = decode_string(&dest, in);
+       n = decode_string(scratch, in);
        if (n < 0)
                goto done;
        string_view_consume(&in, n);
 
-       r->value.update.name =
-               reftable_realloc(r->value.update.name, dest.len + 1);
-       memcpy(r->value.update.name, dest.buf, dest.len);
-       r->value.update.name[dest.len] = 0;
+       /*
+        * In almost all cases we can expect the reflog name to not change for
+        * reflog entries as they are tied to the local identity, not to the
+        * target commits. As an optimization for this common case we can thus
+        * skip copying over the name in case it's accurate already.
+        */
+       if (!r->value.update.name ||
+           strcmp(r->value.update.name, scratch->buf)) {
+               r->value.update.name =
+                       reftable_realloc(r->value.update.name, scratch->len + 1);
+               memcpy(r->value.update.name, scratch->buf, scratch->len);
+               r->value.update.name[scratch->len] = 0;
+       }
 
-       strbuf_reset(&dest);
-       n = decode_string(&dest, in);
+       n = decode_string(scratch, in);
        if (n < 0)
                goto done;
        string_view_consume(&in, n);
 
-       r->value.update.email =
-               reftable_realloc(r->value.update.email, dest.len + 1);
-       memcpy(r->value.update.email, dest.buf, dest.len);
-       r->value.update.email[dest.len] = 0;
+       /* Same as above, but for the reflog email. */
+       if (!r->value.update.email ||
+           strcmp(r->value.update.email, scratch->buf)) {
+               r->value.update.email =
+                       reftable_realloc(r->value.update.email, scratch->len + 1);
+               memcpy(r->value.update.email, scratch->buf, scratch->len);
+               r->value.update.email[scratch->len] = 0;
+       }
 
        ts = 0;
        n = get_var_int(&ts, &in);
@@ -933,22 +937,19 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,
        r->value.update.tz_offset = get_be16(in.buf);
        string_view_consume(&in, 2);
 
-       strbuf_reset(&dest);
-       n = decode_string(&dest, in);
+       n = decode_string(scratch, in);
        if (n < 0)
                goto done;
        string_view_consume(&in, n);
 
-       r->value.update.message =
-               reftable_realloc(r->value.update.message, dest.len + 1);
-       memcpy(r->value.update.message, dest.buf, dest.len);
-       r->value.update.message[dest.len] = 0;
+       REFTABLE_ALLOC_GROW(r->value.update.message, scratch->len + 1,
+                           r->value.update.message_cap);
+       memcpy(r->value.update.message, scratch->buf, scratch->len);
+       r->value.update.message[scratch->len] = 0;
 
-       strbuf_release(&dest);
        return start.len - in.len;
 
 done:
-       strbuf_release(&dest);
        return REFTABLE_FORMAT_ERROR;
 }
 
@@ -964,17 +965,6 @@ static int null_streq(char *a, char *b)
        return 0 == strcmp(a, b);
 }
 
-static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
-{
-       if (!a)
-               a = zero;
-
-       if (!b)
-               b = zero;
-
-       return !memcmp(a, b, sz);
-}
-
 static int reftable_log_record_equal_void(const void *a,
                                          const void *b, int hash_size)
 {
@@ -1018,10 +1008,10 @@ int reftable_log_record_equal(const struct reftable_log_record *a,
                                  b->value.update.email) &&
                       null_streq(a->value.update.message,
                                  b->value.update.message) &&
-                      zero_hash_eq(a->value.update.old_hash,
-                                   b->value.update.old_hash, hash_size) &&
-                      zero_hash_eq(a->value.update.new_hash,
-                                   b->value.update.new_hash, hash_size);
+                      !memcmp(a->value.update.old_hash,
+                              b->value.update.old_hash, hash_size) &&
+                      !memcmp(a->value.update.new_hash,
+                              b->value.update.new_hash, hash_size);
        }
 
        abort();
@@ -1099,7 +1089,7 @@ static int reftable_index_record_encode(const void *rec, struct string_view out,
 
 static int reftable_index_record_decode(void *rec, struct strbuf key,
                                        uint8_t val_type, struct string_view in,
-                                       int hash_size)
+                                       int hash_size, struct strbuf *scratch UNUSED)
 {
        struct string_view start = in;
        struct reftable_index_record *r = rec;
@@ -1157,11 +1147,6 @@ void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
        reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
 }
 
-uint8_t reftable_record_type(struct reftable_record *rec)
-{
-       return rec->type;
-}
-
 int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
                           int hash_size)
 {
@@ -1185,10 +1170,12 @@ uint8_t reftable_record_val_type(struct reftable_record *rec)
 }
 
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
-                          uint8_t extra, struct string_view src, int hash_size)
+                          uint8_t extra, struct string_view src, int hash_size,
+                          struct strbuf *scratch)
 {
        return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
-                                                  key, extra, src, hash_size);
+                                                  key, extra, src, hash_size,
+                                                  scratch);
 }
 
 void reftable_record_release(struct reftable_record *rec)
@@ -1283,12 +1270,6 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log)
        return (log->value_type == REFTABLE_LOG_DELETION);
 }
 
-void string_view_consume(struct string_view *s, int n)
-{
-       s->buf += n;
-       s->len -= n;
-}
-
 static void *reftable_record_data(struct reftable_record *rec)
 {
        switch (rec->type) {
index a05e2be17971d5e26f27871159cda293aa2640ad..826ee1c55c3b64d2bf4cfe1a170cf99ca3511259 100644 (file)
@@ -25,7 +25,11 @@ struct string_view {
 };
 
 /* Advance `s.buf` by `n`, and decrease length. */
-void string_view_consume(struct string_view *s, int n);
+static inline void string_view_consume(struct string_view *s, int n)
+{
+       s->buf += n;
+       s->len -= n;
+}
 
 /* utilities for de/encoding varints */
 
@@ -51,7 +55,8 @@ struct reftable_record_vtable {
 
        /* decode data from `src` into the record. */
        int (*decode)(void *rec, struct strbuf key, uint8_t extra,
-                     struct string_view src, int hash_size);
+                     struct string_view src, int hash_size,
+                     struct strbuf *scratch);
 
        /* deallocate and null the record. */
        void (*release)(void *rec);
@@ -81,9 +86,12 @@ int reftable_encode_key(int *is_restart, struct string_view dest,
                        struct strbuf prev_key, struct strbuf key,
                        uint8_t extra);
 
-/* Decode into `key` and `extra` from `in` */
-int reftable_decode_key(struct strbuf *key, uint8_t *extra,
-                       struct strbuf last_key, struct string_view in);
+/*
+ * Decode into `last_key` and `extra` from `in`. `last_key` is expected to
+ * contain the decoded key of the preceding record, if any.
+ */
+int reftable_decode_key(struct strbuf *last_key, uint8_t *extra,
+                       struct string_view in);
 
 /* reftable_index_record are used internally to speed up lookups. */
 struct reftable_index_record {
@@ -124,7 +132,6 @@ int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b);
 int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
 void reftable_record_print(struct reftable_record *rec, int hash_size);
 void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
-uint8_t reftable_record_type(struct reftable_record *rec);
 void reftable_record_copy_from(struct reftable_record *rec,
                               struct reftable_record *src, int hash_size);
 uint8_t reftable_record_val_type(struct reftable_record *rec);
@@ -132,9 +139,14 @@ int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
                           int hash_size);
 int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
                           uint8_t extra, struct string_view src,
-                          int hash_size);
+                          int hash_size, struct strbuf *scratch);
 int reftable_record_is_deletion(struct reftable_record *rec);
 
+static inline uint8_t reftable_record_type(struct reftable_record *rec)
+{
+       return rec->type;
+}
+
 /* frees and zeroes out the embedded record */
 void reftable_record_release(struct reftable_record *rec);
 
index a86cff552661ba0f00be32200bf8b0c800a9fa87..c158ee79ffe36a0bdf13259f83c4eef119f36981 100644 (file)
@@ -99,6 +99,7 @@ static void set_hash(uint8_t *h, int j)
 
 static void test_reftable_ref_record_roundtrip(void)
 {
+       struct strbuf scratch = STRBUF_INIT;
        int i = 0;
 
        for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
@@ -140,7 +141,7 @@ static void test_reftable_ref_record_roundtrip(void)
                EXPECT(n > 0);
 
                /* decode into a non-zero reftable_record to test for leaks. */
-               m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
+               m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ, &scratch);
                EXPECT(n == m);
 
                EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
@@ -150,6 +151,8 @@ static void test_reftable_ref_record_roundtrip(void)
                strbuf_release(&key);
                reftable_record_release(&out);
        }
+
+       strbuf_release(&scratch);
 }
 
 static void test_reftable_log_record_equal(void)
@@ -175,7 +178,6 @@ static void test_reftable_log_record_equal(void)
 static void test_reftable_log_record_roundtrip(void)
 {
        int i;
-
        struct reftable_log_record in[] = {
                {
                        .refname = xstrdup("refs/heads/master"),
@@ -183,8 +185,6 @@ static void test_reftable_log_record_roundtrip(void)
                        .value_type = REFTABLE_LOG_UPDATE,
                        .value = {
                                .update = {
-                                       .old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
-                                       .new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
                                        .name = xstrdup("han-wen"),
                                        .email = xstrdup("hanwen@google.com"),
                                        .message = xstrdup("test"),
@@ -202,15 +202,10 @@ static void test_reftable_log_record_roundtrip(void)
                        .refname = xstrdup("branch"),
                        .update_index = 33,
                        .value_type = REFTABLE_LOG_UPDATE,
-                       .value = {
-                               .update = {
-                                       .old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
-                                       .new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
-                                       /* rest of fields left empty. */
-                               },
-                       },
                }
        };
+       struct strbuf scratch = STRBUF_INIT;
+
        set_test_hash(in[0].value.update.new_hash, 1);
        set_test_hash(in[0].value.update.old_hash, 2);
        set_test_hash(in[2].value.update.new_hash, 3);
@@ -231,8 +226,6 @@ static void test_reftable_log_record_roundtrip(void)
                                .value_type = REFTABLE_LOG_UPDATE,
                                .value = {
                                        .update = {
-                                               .new_hash = reftable_calloc(GIT_SHA1_RAWSZ, 1),
-                                               .old_hash = reftable_calloc(GIT_SHA1_RAWSZ, 1),
                                                .name = xstrdup("old name"),
                                                .email = xstrdup("old@email"),
                                                .message = xstrdup("old message"),
@@ -252,7 +245,7 @@ static void test_reftable_log_record_roundtrip(void)
                EXPECT(n >= 0);
                valtype = reftable_record_val_type(&rec);
                m = reftable_record_decode(&out, key, valtype, dest,
-                                          GIT_SHA1_RAWSZ);
+                                          GIT_SHA1_RAWSZ, &scratch);
                EXPECT(n == m);
 
                EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
@@ -261,6 +254,8 @@ static void test_reftable_log_record_roundtrip(void)
                strbuf_release(&key);
                reftable_record_release(&out);
        }
+
+       strbuf_release(&scratch);
 }
 
 static void test_u24_roundtrip(void)
@@ -295,7 +290,8 @@ static void test_key_roundtrip(void)
        EXPECT(!restart);
        EXPECT(n > 0);
 
-       m = reftable_decode_key(&roundtrip, &rt_extra, last_key, dest);
+       strbuf_addstr(&roundtrip, "refs/heads/master");
+       m = reftable_decode_key(&roundtrip, &rt_extra, dest);
        EXPECT(n == m);
        EXPECT(0 == strbuf_cmp(&key, &roundtrip));
        EXPECT(rt_extra == extra);
@@ -309,23 +305,27 @@ static void test_reftable_obj_record_roundtrip(void)
 {
        uint8_t testHash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 4, 0 };
        uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
-       struct reftable_obj_record recs[3] = { {
-                                                      .hash_prefix = testHash1,
-                                                      .hash_prefix_len = 5,
-                                                      .offsets = till9,
-                                                      .offset_len = 3,
-                                              },
-                                              {
-                                                      .hash_prefix = testHash1,
-                                                      .hash_prefix_len = 5,
-                                                      .offsets = till9,
-                                                      .offset_len = 9,
-                                              },
-                                              {
-                                                      .hash_prefix = testHash1,
-                                                      .hash_prefix_len = 5,
-                                              } };
+       struct reftable_obj_record recs[3] = {
+               {
+                       .hash_prefix = testHash1,
+                       .hash_prefix_len = 5,
+                       .offsets = till9,
+                       .offset_len = 3,
+               },
+               {
+                       .hash_prefix = testHash1,
+                       .hash_prefix_len = 5,
+                       .offsets = till9,
+                       .offset_len = 9,
+               },
+               {
+                       .hash_prefix = testHash1,
+                       .hash_prefix_len = 5,
+               },
+       };
+       struct strbuf scratch = STRBUF_INIT;
        int i = 0;
+
        for (i = 0; i < ARRAY_SIZE(recs); i++) {
                uint8_t buffer[1024] = { 0 };
                struct string_view dest = {
@@ -349,13 +349,15 @@ static void test_reftable_obj_record_roundtrip(void)
                EXPECT(n > 0);
                extra = reftable_record_val_type(&in);
                m = reftable_record_decode(&out, key, extra, dest,
-                                          GIT_SHA1_RAWSZ);
+                                          GIT_SHA1_RAWSZ, &scratch);
                EXPECT(n == m);
 
                EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
                strbuf_release(&key);
                reftable_record_release(&out);
        }
+
+       strbuf_release(&scratch);
 }
 
 static void test_reftable_index_record_roundtrip(void)
@@ -372,6 +374,7 @@ static void test_reftable_index_record_roundtrip(void)
                .buf = buffer,
                .len = sizeof(buffer),
        };
+       struct strbuf scratch = STRBUF_INIT;
        struct strbuf key = STRBUF_INIT;
        struct reftable_record out = {
                .type = BLOCK_TYPE_INDEX,
@@ -389,13 +392,15 @@ static void test_reftable_index_record_roundtrip(void)
        EXPECT(n > 0);
 
        extra = reftable_record_val_type(&in);
-       m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
+       m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ,
+                                  &scratch);
        EXPECT(m == n);
 
        EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
 
        reftable_record_release(&out);
        strbuf_release(&key);
+       strbuf_release(&scratch);
        strbuf_release(&in.u.idx.last_key);
 }
 
index bb6e99acd3151ec75a7a2dc60e930064b7322e60..2a2943cd134cebf1e6b9713613ba228d49576281 100644 (file)
@@ -22,6 +22,7 @@ https://developers.google.com/open-source/licenses/bsd
 /* reftable_ref_record holds a ref database entry target_value */
 struct reftable_ref_record {
        char *refname; /* Name of the ref, malloced. */
+       size_t refname_cap;
        uint64_t update_index; /* Logical timestamp at which this value is
                                * written */
 
@@ -73,6 +74,7 @@ int reftable_ref_record_equal(const struct reftable_ref_record *a,
 /* reftable_log_record holds a reflog entry */
 struct reftable_log_record {
        char *refname;
+       size_t refname_cap;
        uint64_t update_index; /* logical timestamp of a transactional update.
                                */
 
@@ -87,13 +89,14 @@ struct reftable_log_record {
 
        union {
                struct {
-                       uint8_t *new_hash;
-                       uint8_t *old_hash;
+                       unsigned char new_hash[GIT_MAX_RAWSZ];
+                       unsigned char old_hash[GIT_MAX_RAWSZ];
                        char *name;
                        char *email;
                        uint64_t time;
                        int16_t tz_offset;
                        char *message;
+                       size_t message_cap;
                } update;
        } value;
 };
index b64e55648aa8798054bb7b8a781376e7fa3bc2af..1ecf1b9751ce4975580ab7deed2bbe2a9a7847bc 100644 (file)
@@ -737,8 +737,9 @@ int reftable_addition_add(struct reftable_addition *add,
        struct strbuf tab_file_name = STRBUF_INIT;
        struct strbuf next_name = STRBUF_INIT;
        struct reftable_writer *wr = NULL;
+       struct tempfile *tab_file = NULL;
        int err = 0;
-       int tab_fd = 0;
+       int tab_fd;
 
        strbuf_reset(&next_name);
        format_name(&next_name, add->next_update_index, add->next_update_index);
@@ -746,17 +747,20 @@ int reftable_addition_add(struct reftable_addition *add,
        stack_filename(&temp_tab_file_name, add->stack, next_name.buf);
        strbuf_addstr(&temp_tab_file_name, ".temp.XXXXXX");
 
-       tab_fd = mkstemp(temp_tab_file_name.buf);
-       if (tab_fd < 0) {
+       tab_file = mks_tempfile(temp_tab_file_name.buf);
+       if (!tab_file) {
                err = REFTABLE_IO_ERROR;
                goto done;
        }
        if (add->stack->config.default_permissions) {
-               if (chmod(temp_tab_file_name.buf, add->stack->config.default_permissions)) {
+               if (chmod(get_tempfile_path(tab_file),
+                         add->stack->config.default_permissions)) {
                        err = REFTABLE_IO_ERROR;
                        goto done;
                }
        }
+       tab_fd = get_tempfile_fd(tab_file);
+
        wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd,
                                 &add->stack->config);
        err = write_table(wr, arg);
@@ -771,14 +775,13 @@ int reftable_addition_add(struct reftable_addition *add,
        if (err < 0)
                goto done;
 
-       err = close(tab_fd);
-       tab_fd = 0;
+       err = close_tempfile_gently(tab_file);
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                goto done;
        }
 
-       err = stack_check_addition(add->stack, temp_tab_file_name.buf);
+       err = stack_check_addition(add->stack, get_tempfile_path(tab_file));
        if (err < 0)
                goto done;
 
@@ -789,14 +792,13 @@ int reftable_addition_add(struct reftable_addition *add,
 
        format_name(&next_name, wr->min_update_index, wr->max_update_index);
        strbuf_addstr(&next_name, ".ref");
-
        stack_filename(&tab_file_name, add->stack, next_name.buf);
 
        /*
          On windows, this relies on rand() picking a unique destination name.
          Maybe we should do retry loop as well?
         */
-       err = rename(temp_tab_file_name.buf, tab_file_name.buf);
+       err = rename_tempfile(&tab_file, tab_file_name.buf);
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                goto done;
@@ -806,14 +808,7 @@ int reftable_addition_add(struct reftable_addition *add,
                            add->new_tables_cap);
        add->new_tables[add->new_tables_len++] = strbuf_detach(&next_name, NULL);
 done:
-       if (tab_fd > 0) {
-               close(tab_fd);
-               tab_fd = 0;
-       }
-       if (temp_tab_file_name.len > 0) {
-               unlink(temp_tab_file_name.buf);
-       }
-
+       delete_tempfile(&tab_file);
        strbuf_release(&temp_tab_file_name);
        strbuf_release(&tab_file_name);
        strbuf_release(&next_name);
@@ -832,51 +827,56 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
 
 static int stack_compact_locked(struct reftable_stack *st,
                                size_t first, size_t last,
-                               struct strbuf *temp_tab,
-                               struct reftable_log_expiry_config *config)
+                               struct reftable_log_expiry_config *config,
+                               struct tempfile **tab_file_out)
 {
        struct strbuf next_name = STRBUF_INIT;
-       int tab_fd = -1;
+       struct strbuf tab_file_path = STRBUF_INIT;
        struct reftable_writer *wr = NULL;
-       int err = 0;
+       struct tempfile *tab_file;
+       int tab_fd, err = 0;
 
        format_name(&next_name,
                    reftable_reader_min_update_index(st->readers[first]),
                    reftable_reader_max_update_index(st->readers[last]));
+       stack_filename(&tab_file_path, st, next_name.buf);
+       strbuf_addstr(&tab_file_path, ".temp.XXXXXX");
 
-       stack_filename(temp_tab, st, next_name.buf);
-       strbuf_addstr(temp_tab, ".temp.XXXXXX");
+       tab_file = mks_tempfile(tab_file_path.buf);
+       if (!tab_file) {
+               err = REFTABLE_IO_ERROR;
+               goto done;
+       }
+       tab_fd = get_tempfile_fd(tab_file);
 
-       tab_fd = mkstemp(temp_tab->buf);
        if (st->config.default_permissions &&
-           chmod(temp_tab->buf, st->config.default_permissions) < 0) {
+           chmod(get_tempfile_path(tab_file), st->config.default_permissions) < 0) {
                err = REFTABLE_IO_ERROR;
                goto done;
        }
 
-       wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, &st->config);
-
+       wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush,
+                                &tab_fd, &st->config);
        err = stack_write_compact(st, wr, first, last, config);
        if (err < 0)
                goto done;
+
        err = reftable_writer_close(wr);
        if (err < 0)
                goto done;
 
-       err = close(tab_fd);
-       tab_fd = 0;
+       err = close_tempfile_gently(tab_file);
+       if (err < 0)
+               goto done;
+
+       *tab_file_out = tab_file;
+       tab_file = NULL;
 
 done:
+       delete_tempfile(&tab_file);
        reftable_writer_free(wr);
-       if (tab_fd > 0) {
-               close(tab_fd);
-               tab_fd = 0;
-       }
-       if (err != 0 && temp_tab->len > 0) {
-               unlink(temp_tab->buf);
-               strbuf_release(temp_tab);
-       }
        strbuf_release(&next_name);
+       strbuf_release(&tab_file_path);
        return err;
 }
 
@@ -983,212 +983,200 @@ static int stack_compact_range(struct reftable_stack *st,
                               size_t first, size_t last,
                               struct reftable_log_expiry_config *expiry)
 {
-       char **delete_on_success = NULL, **subtable_locks = NULL, **listp = NULL;
-       struct strbuf temp_tab_file_name = STRBUF_INIT;
+       struct strbuf tables_list_buf = STRBUF_INIT;
        struct strbuf new_table_name = STRBUF_INIT;
-       struct strbuf lock_file_name = STRBUF_INIT;
-       struct strbuf ref_list_contents = STRBUF_INIT;
        struct strbuf new_table_path = STRBUF_INIT;
-       size_t i, j, compact_count;
-       int err = 0;
-       int have_lock = 0;
-       int lock_file_fd = -1;
-       int is_empty_table = 0;
+       struct strbuf table_name = STRBUF_INIT;
+       struct lock_file tables_list_lock = LOCK_INIT;
+       struct lock_file *table_locks = NULL;
+       struct tempfile *new_table = NULL;
+       int is_empty_table = 0, err = 0;
+       size_t i;
 
        if (first > last || (!expiry && first == last)) {
                err = 0;
                goto done;
        }
 
-       compact_count = last - first + 1;
-       REFTABLE_CALLOC_ARRAY(delete_on_success, compact_count + 1);
-       REFTABLE_CALLOC_ARRAY(subtable_locks, compact_count + 1);
-
        st->stats.attempts++;
 
-       strbuf_reset(&lock_file_name);
-       strbuf_addstr(&lock_file_name, st->list_file);
-       strbuf_addstr(&lock_file_name, ".lock");
-
-       lock_file_fd =
-               open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0666);
-       if (lock_file_fd < 0) {
-               if (errno == EEXIST) {
+       /*
+        * Hold the lock so that we can read "tables.list" and lock all tables
+        * which are part of the user-specified range.
+        */
+       err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
+                                       LOCK_NO_DEREF);
+       if (err < 0) {
+               if (errno == EEXIST)
                        err = 1;
-               } else {
+               else
                        err = REFTABLE_IO_ERROR;
-               }
                goto done;
        }
-       /* Don't want to write to the lock for now.  */
-       close(lock_file_fd);
-       lock_file_fd = -1;
 
-       have_lock = 1;
        err = stack_uptodate(st);
-       if (err != 0)
+       if (err)
                goto done;
 
-       for (i = first, j = 0; i <= last; i++) {
-               struct strbuf subtab_file_name = STRBUF_INIT;
-               struct strbuf subtab_lock = STRBUF_INIT;
-               int sublock_file_fd = -1;
-
-               stack_filename(&subtab_file_name, st,
-                              reader_name(st->readers[i]));
-
-               strbuf_reset(&subtab_lock);
-               strbuf_addbuf(&subtab_lock, &subtab_file_name);
-               strbuf_addstr(&subtab_lock, ".lock");
+       /*
+        * Lock all tables in the user-provided range. This is the slice of our
+        * stack which we'll compact.
+        */
+       REFTABLE_CALLOC_ARRAY(table_locks, last - first + 1);
+       for (i = first; i <= last; i++) {
+               stack_filename(&table_name, st, reader_name(st->readers[i]));
 
-               sublock_file_fd = open(subtab_lock.buf,
-                                      O_EXCL | O_CREAT | O_WRONLY, 0666);
-               if (sublock_file_fd >= 0) {
-                       close(sublock_file_fd);
-               } else if (sublock_file_fd < 0) {
-                       if (errno == EEXIST) {
+               err = hold_lock_file_for_update(&table_locks[i - first],
+                                               table_name.buf, LOCK_NO_DEREF);
+               if (err < 0) {
+                       if (errno == EEXIST)
                                err = 1;
-                       } else {
+                       else
                                err = REFTABLE_IO_ERROR;
-                       }
+                       goto done;
                }
 
-               subtable_locks[j] = subtab_lock.buf;
-               delete_on_success[j] = subtab_file_name.buf;
-               j++;
-
-               if (err != 0)
+               /*
+                * We need to close the lockfiles as we might otherwise easily
+                * run into file descriptor exhaustion when we compress a lot
+                * of tables.
+                */
+               err = close_lock_file_gently(&table_locks[i - first]);
+               if (err < 0) {
+                       err = REFTABLE_IO_ERROR;
                        goto done;
+               }
        }
 
-       err = unlink(lock_file_name.buf);
-       if (err < 0)
+       /*
+        * We have locked all tables in our range and can thus release the
+        * "tables.list" lock while compacting the locked tables. This allows
+        * concurrent updates to the stack to proceed.
+        */
+       err = rollback_lock_file(&tables_list_lock);
+       if (err < 0) {
+               err = REFTABLE_IO_ERROR;
                goto done;
-       have_lock = 0;
-
-       err = stack_compact_locked(st, first, last, &temp_tab_file_name,
-                                  expiry);
-       /* Compaction + tombstones can create an empty table out of non-empty
-        * tables. */
-       is_empty_table = (err == REFTABLE_EMPTY_TABLE_ERROR);
-       if (is_empty_table) {
-               err = 0;
        }
-       if (err < 0)
-               goto done;
 
-       lock_file_fd =
-               open(lock_file_name.buf, O_EXCL | O_CREAT | O_WRONLY, 0666);
-       if (lock_file_fd < 0) {
-               if (errno == EEXIST) {
+       /*
+        * Compact the now-locked tables into a new table. Note that compacting
+        * these tables may end up with an empty new table in case tombstones
+        * end up cancelling out all refs in that range.
+        */
+       err = stack_compact_locked(st, first, last, expiry, &new_table);
+       if (err < 0) {
+               if (err != REFTABLE_EMPTY_TABLE_ERROR)
+                       goto done;
+               is_empty_table = 1;
+       }
+
+       /*
+        * Now that we have written the new, compacted table we need to re-lock
+        * "tables.list". We'll then replace the compacted range of tables with
+        * the new table.
+        */
+       err = hold_lock_file_for_update(&tables_list_lock, st->list_file,
+                                       LOCK_NO_DEREF);
+       if (err < 0) {
+               if (errno == EEXIST)
                        err = 1;
-               } else {
+               else
                        err = REFTABLE_IO_ERROR;
-               }
                goto done;
        }
-       have_lock = 1;
+
        if (st->config.default_permissions) {
-               if (chmod(lock_file_name.buf, st->config.default_permissions) < 0) {
+               if (chmod(get_lock_file_path(&tables_list_lock),
+                         st->config.default_permissions) < 0) {
                        err = REFTABLE_IO_ERROR;
                        goto done;
                }
        }
 
-       format_name(&new_table_name, st->readers[first]->min_update_index,
-                   st->readers[last]->max_update_index);
-       strbuf_addstr(&new_table_name, ".ref");
-
-       stack_filename(&new_table_path, st, new_table_name.buf);
-
+       /*
+        * If the resulting compacted table is not empty, then we need to move
+        * it into place now.
+        */
        if (!is_empty_table) {
-               /* retry? */
-               err = rename(temp_tab_file_name.buf, new_table_path.buf);
+               format_name(&new_table_name, st->readers[first]->min_update_index,
+                           st->readers[last]->max_update_index);
+               strbuf_addstr(&new_table_name, ".ref");
+               stack_filename(&new_table_path, st, new_table_name.buf);
+
+               err = rename_tempfile(&new_table, new_table_path.buf);
                if (err < 0) {
                        err = REFTABLE_IO_ERROR;
                        goto done;
                }
        }
 
-       for (i = 0; i < first; i++) {
-               strbuf_addstr(&ref_list_contents, st->readers[i]->name);
-               strbuf_addstr(&ref_list_contents, "\n");
-       }
-       if (!is_empty_table) {
-               strbuf_addbuf(&ref_list_contents, &new_table_name);
-               strbuf_addstr(&ref_list_contents, "\n");
-       }
-       for (i = last + 1; i < st->merged->stack_len; i++) {
-               strbuf_addstr(&ref_list_contents, st->readers[i]->name);
-               strbuf_addstr(&ref_list_contents, "\n");
-       }
-
-       err = write_in_full(lock_file_fd, ref_list_contents.buf, ref_list_contents.len);
-       if (err < 0) {
-               err = REFTABLE_IO_ERROR;
-               unlink(new_table_path.buf);
-               goto done;
-       }
-
-       err = fsync_component(FSYNC_COMPONENT_REFERENCE, lock_file_fd);
+       /*
+        * Write the new "tables.list" contents with the compacted table we
+        * have just written. In case the compacted table became empty we
+        * simply skip writing it.
+        */
+       for (i = 0; i < first; i++)
+               strbuf_addf(&tables_list_buf, "%s\n", st->readers[i]->name);
+       if (!is_empty_table)
+               strbuf_addf(&tables_list_buf, "%s\n", new_table_name.buf);
+       for (i = last + 1; i < st->merged->stack_len; i++)
+               strbuf_addf(&tables_list_buf, "%s\n", st->readers[i]->name);
+
+       err = write_in_full(get_lock_file_fd(&tables_list_lock),
+                           tables_list_buf.buf, tables_list_buf.len);
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                unlink(new_table_path.buf);
                goto done;
        }
 
-       err = close(lock_file_fd);
-       lock_file_fd = -1;
+       err = fsync_component(FSYNC_COMPONENT_REFERENCE, get_lock_file_fd(&tables_list_lock));
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                unlink(new_table_path.buf);
                goto done;
        }
 
-       err = rename(lock_file_name.buf, st->list_file);
+       err = commit_lock_file(&tables_list_lock);
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
                unlink(new_table_path.buf);
                goto done;
        }
-       have_lock = 0;
 
-       /* Reload the stack before deleting. On windows, we can only delete the
-          files after we closed them.
-       */
+       /*
+        * Reload the stack before deleting the compacted tables. We can only
+        * delete the files after we closed them on Windows, so this needs to
+        * happen first.
+        */
        err = reftable_stack_reload_maybe_reuse(st, first < last);
+       if (err < 0)
+               goto done;
 
-       listp = delete_on_success;
-       while (*listp) {
-               if (strcmp(*listp, new_table_path.buf)) {
-                       unlink(*listp);
-               }
-               listp++;
+       /*
+        * Delete the old tables. They may still be in use by concurrent
+        * readers, so it is expected that unlinking tables may fail.
+        */
+       for (i = first; i <= last; i++) {
+               struct lock_file *table_lock = &table_locks[i - first];
+               char *table_path = get_locked_file_path(table_lock);
+               unlink(table_path);
+               free(table_path);
        }
 
 done:
-       free_names(delete_on_success);
+       rollback_lock_file(&tables_list_lock);
+       for (i = first; table_locks && i <= last; i++)
+               rollback_lock_file(&table_locks[i - first]);
+       reftable_free(table_locks);
 
-       if (subtable_locks) {
-               listp = subtable_locks;
-               while (*listp) {
-                       unlink(*listp);
-                       listp++;
-               }
-               free_names(subtable_locks);
-       }
-       if (lock_file_fd >= 0) {
-               close(lock_file_fd);
-               lock_file_fd = -1;
-       }
-       if (have_lock) {
-               unlink(lock_file_name.buf);
-       }
+       delete_tempfile(&new_table);
        strbuf_release(&new_table_name);
        strbuf_release(&new_table_path);
-       strbuf_release(&ref_list_contents);
-       strbuf_release(&temp_tab_file_name);
-       strbuf_release(&lock_file_name);
+
+       strbuf_release(&tables_list_buf);
+       strbuf_release(&table_name);
        return err;
 }
 
index 509f4866236024f5546100da7121996f1c963e08..7336757cf534058dd6f3e8346d15874036c5b763 100644 (file)
@@ -468,8 +468,6 @@ static void test_reftable_stack_add(void)
                logs[i].refname = xstrdup(buf);
                logs[i].update_index = N + i + 1;
                logs[i].value_type = REFTABLE_LOG_UPDATE;
-
-               logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
                logs[i].value.update.email = xstrdup("identity@invalid");
                set_test_hash(logs[i].value.update.new_hash, i);
        }
@@ -547,16 +545,17 @@ static void test_reftable_stack_log_normalize(void)
        };
        struct reftable_stack *st = NULL;
        char *dir = get_tmp_dir(__LINE__);
-
-       uint8_t h1[GIT_SHA1_RAWSZ] = { 0x01 }, h2[GIT_SHA1_RAWSZ] = { 0x02 };
-
-       struct reftable_log_record input = { .refname = "branch",
-                                            .update_index = 1,
-                                            .value_type = REFTABLE_LOG_UPDATE,
-                                            .value = { .update = {
-                                                               .new_hash = h1,
-                                                               .old_hash = h2,
-                                                       } } };
+       struct reftable_log_record input = {
+               .refname = "branch",
+               .update_index = 1,
+               .value_type = REFTABLE_LOG_UPDATE,
+               .value = {
+                       .update = {
+                               .new_hash = { 1 },
+                               .old_hash = { 2 },
+                       },
+               },
+       };
        struct reftable_log_record dest = {
                .update_index = 0,
        };
@@ -627,8 +626,6 @@ static void test_reftable_stack_tombstone(void)
                logs[i].update_index = 42;
                if (i % 2 == 0) {
                        logs[i].value_type = REFTABLE_LOG_UPDATE;
-                       logs[i].value.update.new_hash =
-                               reftable_malloc(GIT_SHA1_RAWSZ);
                        set_test_hash(logs[i].value.update.new_hash, i);
                        logs[i].value.update.email =
                                xstrdup("identity@invalid");
@@ -810,7 +807,6 @@ static void test_reflog_expire(void)
                logs[i].update_index = i;
                logs[i].value_type = REFTABLE_LOG_UPDATE;
                logs[i].value.update.time = i;
-               logs[i].value.update.new_hash = reftable_malloc(GIT_SHA1_RAWSZ);
                logs[i].value.update.email = xstrdup("identity@invalid");
                set_test_hash(logs[i].value.update.new_hash, i);
        }
index 6b74a8151436144225c2e579340dfc3426a26470..5d8b6dede50414b750f39778c6070731b096d218 100644 (file)
@@ -12,7 +12,9 @@ https://developers.google.com/open-source/licenses/bsd
 /* This header glues the reftable library to the rest of Git */
 
 #include "git-compat-util.h"
+#include "lockfile.h"
 #include "strbuf.h"
+#include "tempfile.h"
 #include "hash-ll.h" /* hash ID, sizes.*/
 #include "dir.h" /* remove_dir_recursively, for tests.*/
 
index 45893651c22f2669da8d5fe38a0b809e650d6f6d..d6436ee66b146f63e4aabf16377491d470583328 100644 (file)
@@ -2358,7 +2358,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (skip_prefix(arg, "--ancestry-path=", &optarg)) {
                struct commit *c;
                struct object_id oid;
-               const char *msg = _("could not get commit for ancestry-path argument %s");
+               const char *msg = _("could not get commit for --ancestry-path argument %s");
 
                revs->ancestry_path = 1;
                revs->simplify_history = 0;
index 5c6f541126c74bb1c70adef36520213b31d3265b..4e14fa6541c7012c8549e84957c47f386efbc7ec 100644 (file)
@@ -332,7 +332,7 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
                sb->buf[sb->len - ignore_footer] = '\0';
        }
 
-       trailer_info_get(&info, sb->buf, &opts);
+       trailer_info_get(&opts, sb->buf, &info);
 
        if (ignore_footer)
                sb->buf[sb->len - ignore_footer] = saved_char;
@@ -461,10 +461,22 @@ static void free_message(struct commit *commit, struct commit_message *msg)
        repo_unuse_commit_buffer(the_repository, commit, msg->message);
 }
 
+const char *rebase_resolvemsg =
+N_("Resolve all conflicts manually, mark them as resolved with\n"
+"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
+"You can instead skip this commit: run \"git rebase --skip\".\n"
+"To abort and get back to the state before \"git rebase\", run "
+"\"git rebase --abort\".");
+
 static void print_advice(struct repository *r, int show_hint,
                         struct replay_opts *opts)
 {
-       char *msg = getenv("GIT_CHERRY_PICK_HELP");
+       const char *msg;
+
+       if (is_rebase_i(opts))
+               msg = rebase_resolvemsg;
+       else
+               msg = getenv("GIT_CHERRY_PICK_HELP");
 
        if (msg) {
                advise("%s\n", msg);
index dcef7bb99c08b0f8e5a29905f8f3a4d6d2d34a45..437eabd38af0398ae9f72b5badd2f691290aa694 100644 (file)
@@ -14,6 +14,8 @@ const char *rebase_path_todo(void);
 const char *rebase_path_todo_backup(void);
 const char *rebase_path_dropped(void);
 
+extern const char *rebase_resolvemsg;
+
 #define APPEND_SIGNOFF_DEDUP (1u << 0)
 
 enum replay_action {
diff --git a/setup.c b/setup.c
index e3b76e84b5b8676fdd23b7709800e3df24d8c08d..0b798591c0c5c2b23285ce1c4f8fcfb71caddb14 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1243,6 +1243,32 @@ static const char *allowed_bare_repo_to_string(
        return NULL;
 }
 
+static int is_implicit_bare_repo(const char *path)
+{
+       /*
+        * what we found is a ".git" directory at the root of
+        * the working tree.
+        */
+       if (ends_with_path_components(path, ".git"))
+               return 1;
+
+       /*
+        * we are inside $GIT_DIR of a secondary worktree of a
+        * non-bare repository.
+        */
+       if (strstr(path, "/.git/worktrees/"))
+               return 1;
+
+       /*
+        * we are inside $GIT_DIR of a worktree of a non-embedded
+        * submodule, whose superproject is not a bare repository.
+        */
+       if (strstr(path, "/.git/modules/"))
+               return 1;
+
+       return 0;
+}
+
 /*
  * We cannot decide in this function whether we are in the work tree or
  * not, since the config can only be read _after_ this function was called.
@@ -1372,7 +1398,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
                if (is_git_directory(dir->buf)) {
                        trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
                        if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
-                           !ends_with_path_components(dir->buf, ".git"))
+                           !is_implicit_bare_repo(dir->buf))
                                return GIT_DIR_DISALLOWED_BARE;
                        if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
                                return GIT_DIR_INVALID_OWNERSHIP;
@@ -1968,7 +1994,6 @@ void create_reference_database(unsigned int ref_storage_format,
 static int create_default_files(const char *template_path,
                                const char *original_git_dir,
                                const struct repository_format *fmt,
-                               int prev_bare_repository,
                                int init_shared_repository)
 {
        struct stat st1;
@@ -2003,34 +2028,8 @@ static int create_default_files(const char *template_path,
         */
        if (init_shared_repository != -1)
                set_shared_repository(init_shared_repository);
-       /*
-        * TODO: heed core.bare from config file in templates if no
-        *       command-line override given
-        */
-       is_bare_repository_cfg = prev_bare_repository || !work_tree;
-       /* TODO (continued):
-        *
-        * Unfortunately, the line above is equivalent to
-        *    is_bare_repository_cfg = !work_tree;
-        * which ignores the config entirely even if no `--[no-]bare`
-        * command line option was present.
-        *
-        * To see why, note that before this function, there was this call:
-        *    prev_bare_repository = is_bare_repository()
-        * expanding the right hand side:
-        *                 = is_bare_repository_cfg && !get_git_work_tree()
-        *                 = is_bare_repository_cfg && !work_tree
-        * note that the last simplification above is valid because nothing
-        * calls repo_init() or set_git_work_tree() between any of the
-        * relevant calls in the code, and thus the !get_git_work_tree()
-        * calls will return the same result each time.  So, what we are
-        * interested in computing is the right hand side of the line of
-        * code just above this comment:
-        *     prev_bare_repository || !work_tree
-        *        = is_bare_repository_cfg && !work_tree || !work_tree
-        *        = !work_tree
-        * because "A && !B || !B == !B" for all boolean values of A & B.
-        */
+
+       is_bare_repository_cfg = !work_tree;
 
        /*
         * We would have created the above under user's umask -- under
@@ -2182,7 +2181,6 @@ int init_db(const char *git_dir, const char *real_git_dir,
        int exist_ok = flags & INIT_DB_EXIST_OK;
        char *original_git_dir = real_pathdup(git_dir, 1);
        struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
-       int prev_bare_repository;
 
        if (real_git_dir) {
                struct stat st;
@@ -2208,7 +2206,6 @@ int init_db(const char *git_dir, const char *real_git_dir,
 
        safe_create_dir(git_dir, 0);
 
-       prev_bare_repository = is_bare_repository();
 
        /* Check to see if the repository version is right.
         * Note that a newly created repository does not have
@@ -2221,8 +2218,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
        validate_ref_storage_format(&repo_fmt, ref_storage_format);
 
        reinit = create_default_files(template_dir, original_git_dir,
-                                     &repo_fmt, prev_bare_repository,
-                                     init_shared_repository);
+                                     &repo_fmt, init_shared_repository);
 
        /*
         * Now that we have set up both the hash algorithm and the ref storage
index 266a67342be7245ae3c1dfae5a1a5ad6647f95df..5d8907151fec3982e906025fc19c2165ef4572d3 100644 (file)
@@ -220,7 +220,7 @@ int demultiplex_sideband(const char *me, int status,
                        }
 
                        strbuf_addch(scratch, *brk);
-                       xwrite(2, scratch->buf, scratch->len);
+                       write_in_full(2, scratch->buf, scratch->len);
                        strbuf_reset(scratch);
 
                        b = brk + 1;
@@ -247,7 +247,7 @@ cleanup:
                die("%s", scratch->buf);
        if (scratch->len) {
                strbuf_addch(scratch, '\n');
-               xwrite(2, scratch->buf, scratch->len);
+               write_in_full(2, scratch->buf, scratch->len);
        }
        strbuf_release(scratch);
        return 1;
index 837c8b7228b98e8e7b3dbfecd5046673554ef464..84172a3739094adc6cdf6496b016fe68e0b2a771 100755 (executable)
@@ -10,25 +10,24 @@ TEST_PASSES_SANITIZE_LEAK=true
 
 for trial in 0 1 2 3 4
 do
-       rm -f .git/index
-       echo frotz >infocom
-       git update-index --add infocom
-       echo xyzzy >infocom
-
-       files=$(git diff-files -p)
-       test_expect_success \
-       "Racy GIT trial #$trial part A" \
-       'test "" != "$files"'
-
+       test_expect_success "Racy git trial #$trial part A" '
+               rm -f .git/index &&
+               echo frotz >infocom &&
+               git update-index --add infocom &&
+               echo xyzzy >infocom &&
+
+               git diff-files -p >out &&
+               test_file_not_empty out
+       '
        sleep 1
-       echo xyzzy >cornerstone
-       git update-index --add cornerstone
 
-       files=$(git diff-files -p)
-       test_expect_success \
-       "Racy GIT trial #$trial part B" \
-       'test "" != "$files"'
+       test_expect_success "Racy git trial #$trial part B" '
+               echo xyzzy >cornerstone &&
+               git update-index --add cornerstone &&
 
+               git diff-files -p >out &&
+               test_file_not_empty out
+       '
 done
 
 test_done
index 804885637954a57ba85e139591e6a0f5a5167034..d3cb2a1cb9edb8f9ad7480be6ec3e02b464046bd 100755 (executable)
@@ -29,9 +29,20 @@ expect_rejected () {
        grep -F "implicit-bare-repository:$pwd" "$pwd/trace.perf"
 }
 
-test_expect_success 'setup bare repo in worktree' '
+test_expect_success 'setup an embedded bare repo, secondary worktree and submodule' '
        git init outer-repo &&
-       git init --bare outer-repo/bare-repo
+       git init --bare --initial-branch=main outer-repo/bare-repo &&
+       git -C outer-repo worktree add ../outer-secondary &&
+       test_path_is_dir outer-secondary &&
+       (
+               cd outer-repo &&
+               test_commit A &&
+               git push bare-repo +HEAD:refs/heads/main &&
+               git -c protocol.file.allow=always \
+                       submodule add --name subn -- ./bare-repo subd
+       ) &&
+       test_path_is_dir outer-repo/.git/worktrees/outer-secondary &&
+       test_path_is_dir outer-repo/.git/modules/subn
 '
 
 test_expect_success 'safe.bareRepository unset' '
@@ -53,8 +64,7 @@ test_expect_success 'safe.bareRepository in the repository' '
        # safe.bareRepository must not be "explicit", otherwise
        # git config fails with "fatal: not in a git directory" (like
        # safe.directory)
-       test_config -C outer-repo/bare-repo safe.bareRepository \
-               all &&
+       test_config -C outer-repo/bare-repo safe.bareRepository all &&
        test_config_global safe.bareRepository explicit &&
        expect_rejected -C outer-repo/bare-repo
 '
@@ -86,4 +96,12 @@ test_expect_success 'no trace when "bare repository" is a subdir of .git' '
        expect_accepted_implicit -C outer-repo/.git/objects
 '
 
+test_expect_success 'no trace in $GIT_DIR of secondary worktree' '
+       expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary
+'
+
+test_expect_success 'no trace in $GIT_DIR of a submodule' '
+       expect_accepted_implicit -C outer-repo/.git/modules/subn
+'
+
 test_done
index ec974867e4337ca503cb817fd79926193c518710..8bb2a8b453ce0bd6ce1960542e21d3bcd74567e0 100755 (executable)
@@ -210,6 +210,22 @@ test_expect_success 'superfluous value provided: boolean' '
        test_cmp expect actual
 '
 
+test_expect_success 'superfluous value provided: boolean, abbreviated' '
+       cat >expect <<-\EOF &&
+       error: option `yes'\'' takes no value
+       EOF
+       test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
+       test-tool parse-options --ye=hi 2>actual &&
+       test_cmp expect actual &&
+
+       cat >expect <<-\EOF &&
+       error: option `no-yes'\'' takes no value
+       EOF
+       test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
+       test-tool parse-options --no-ye=hi 2>actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'superfluous value provided: cmdmode' '
        cat >expect <<-\EOF &&
        error: option `mode1'\'' takes no value
index 290b6eaaab16052b84ca3c4e5527c8910f67ebf0..13ef69b92f897b0e4bd5019ea3a7b48f15061e37 100755 (executable)
@@ -287,4 +287,235 @@ test_expect_success 'unsafe URLs are redacted by default' '
        grep "d0|main|def_param|.*|remote.origin.url:https://user:pwd@example.com" actual
 '
 
+# Confirm that the requested command produces a "cmd_name" and a
+# set of "def_param" events.
+#
+try_simple () {
+       test_when_finished "rm prop.perf actual" &&
+
+       cmd=$1 &&
+       cmd_name=$2 &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+                       $cmd &&
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+       grep "d0|main|cmd_name|.*|$cmd_name" actual &&
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual
+}
+
+# Representative mainstream builtin Git command dispatched
+# in run_builtin() in git.c
+#
+test_expect_success 'expect def_params for normal builtin command' '
+       try_simple "git version" "version"
+'
+
+# Representative query command dispatched in handle_options()
+# in git.c
+#
+test_expect_success 'expect def_params for query command' '
+       try_simple "git --man-path" "_query_"
+'
+
+# remote-curl.c does not use the builtin setup in git.c, so confirm
+# that executables built from remote-curl.c emit def_params.
+#
+# Also tests the dashed-command handling where "git foo" silently
+# spawns "git-foo".  Make sure that both commands should emit
+# def_params.
+#
+# Pass bogus arguments to remote-https and allow the command to fail
+# because we don't actually have a remote to fetch from.  We just want
+# to see the run-dashed code run an executable built from
+# remote-curl.c rather than git.c.  Confirm that we get def_param
+# events from both layers.
+#
+test_expect_success 'expect def_params for remote-curl and _run_dashed_' '
+       test_when_finished "rm prop.perf actual" &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       test_might_fail env \
+               ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+               git remote-http x y &&
+
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+
+       grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
+
+       grep "d1|main|cmd_name|.*|remote-curl" actual &&
+       grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
+'
+
+# Similarly, `git-http-fetch` is not built from git.c so do a
+# trivial fetch so that the main git.c run-dashed code spawns
+# an executable built from http-fetch.c.  Confirm that we get
+# def_param events from both layers.
+#
+test_expect_success 'expect def_params for http-fetch and _run_dashed_' '
+       test_when_finished "rm prop.perf actual" &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       test_might_fail env \
+               ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+               git http-fetch --stdin file:/// <<-EOF &&
+       EOF
+
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+
+       grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
+
+       grep "d1|main|cmd_name|.*|http-fetch" actual &&
+       grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
+'
+
+# Historically, alias expansion explicitly emitted the def_param
+# events (independent of whether the command was a builtin, a Git
+# command or arbitrary shell command) so that it wasn't dependent
+# upon the unpeeling of the alias. Let's make sure that we preserve
+# the net effect.
+#
+test_expect_success 'expect def_params during git alias expansion' '
+       test_when_finished "rm prop.perf actual" &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       test_config_global "alias.xxx" "version" &&
+
+       ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+                       git xxx &&
+
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+
+       # "git xxx" is first mapped to "git-xxx" and the child will fail.
+       grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
+
+       # We unpeel that and substitute "version" into "xxx" (giving
+       # "git version") and update the cmd_name event.
+       grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_git_alias_)" actual &&
+
+       # These def_param events could be associated with either of the
+       # above cmd_name events.  It does not matter.
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
+
+       # The "git version" child sees a different cmd_name hierarchy.
+       # Also test the def_param (only for completeness).
+       grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_git_alias_/version)" actual &&
+       grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
+'
+
+test_expect_success 'expect def_params during shell alias expansion' '
+       test_when_finished "rm prop.perf actual" &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       test_config_global "alias.xxx" "!git version" &&
+
+       ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+                       git xxx &&
+
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+
+       # "git xxx" is first mapped to "git-xxx" and the child will fail.
+       grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
+
+       # We unpeel that and substitute "git version" for "git xxx" (as a
+       # shell command.  Another cmd_name event is emitted as we unpeel.
+       grep "d0|main|cmd_name|.*|_run_shell_alias_ (_run_dashed_/_run_shell_alias_)" actual &&
+
+       # These def_param events could be associated with either of the
+       # above cmd_name events.  It does not matter.
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
+
+       # We get the following only because we used a git command for the
+       # shell command. In general, it could have been a shell script and
+       # we would see nothing.
+       #
+       # The child knows the cmd_name hierarchy so it includes it.
+       grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_shell_alias_/version)" actual &&
+       grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
+'
+
+test_expect_success 'expect def_params during nested git alias expansion' '
+       test_when_finished "rm prop.perf actual" &&
+
+       test_config_global "trace2.configParams" "cfg.prop.*" &&
+       test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" &&
+
+       test_config_global "cfg.prop.foo" "red" &&
+
+       test_config_global "alias.xxx" "yyy" &&
+       test_config_global "alias.yyy" "version" &&
+
+       ENV_PROP_FOO=blue \
+               GIT_TRACE2_PERF="$(pwd)/prop.perf" \
+                       git xxx &&
+
+       perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&
+
+       # "git xxx" is first mapped to "git-xxx" and try to spawn "git-xxx"
+       # and the child will fail.
+       grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
+       grep "d0|main|child_start|.*|.* class:dashed argv:\[git-xxx\]" actual &&
+
+       # We unpeel that and substitute "yyy" into "xxx" (giving "git yyy")
+       # and spawn "git-yyy" and the child will fail.
+       grep "d0|main|alias|.*|alias:xxx argv:\[yyy\]" actual &&
+       grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_/_run_dashed_)" actual &&
+       grep "d0|main|child_start|.*|.* class:dashed argv:\[git-yyy\]" actual &&
+
+       # We unpeel that and substitute "version" into "xxx" (giving
+       # "git version") and update the cmd_name event.
+       grep "d0|main|alias|.*|alias:yyy argv:\[version\]" actual &&
+       grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_dashed_/_run_git_alias_)" actual &&
+
+       # These def_param events could be associated with any of the
+       # above cmd_name events.  It does not matter.
+       grep "d0|main|def_param|.*|cfg.prop.foo:red" actual >actual.matches &&
+       grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
+
+       # However, we do not want them repeated each time we unpeel.
+       test_line_count = 1 actual.matches &&
+
+       # The "git version" child sees a different cmd_name hierarchy.
+       # Also test the def_param (only for completeness).
+       grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_dashed_/_run_git_alias_/version)" actual &&
+       grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
+       grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
+'
+
 test_done
index b1eb5c01b899daf9a0075393e3e79d47da069d5d..29cf8a966133a7851722c8cdf828fe3c524a12ec 100755 (executable)
@@ -52,7 +52,7 @@ test_expect_success 'shared=all' '
        test 2 = $(git config core.sharedrepository)
 '
 
-test_expect_failure 'template can set core.bare' '
+test_expect_success 'template cannot set core.bare' '
        test_when_finished "rm -rf subdir" &&
        test_when_finished "rm -rf templates" &&
        test_config core.bare true &&
@@ -60,18 +60,7 @@ test_expect_failure 'template can set core.bare' '
        mkdir -p templates/ &&
        cp .git/config templates/config &&
        git init --template=templates subdir &&
-       test_path_exists subdir/HEAD
-'
-
-test_expect_success 'template can set core.bare but overridden by command line' '
-       test_when_finished "rm -rf subdir" &&
-       test_when_finished "rm -rf templates" &&
-       test_config core.bare true &&
-       umask 0022 &&
-       mkdir -p templates/ &&
-       cp .git/config templates/config &&
-       git init --no-bare --template=templates subdir &&
-       test_path_exists subdir/.git/HEAD
+       test_path_is_missing subdir/HEAD
 '
 
 test_expect_success POSIXPERM 'update-server-info honors core.sharedRepository' '
index f0737593c3fda734060fa9509d1b9561086a7376..b754b9fd74bd17e7a969026a93cd6e70c8771cb8 100755 (executable)
@@ -322,4 +322,15 @@ check_invalid_long_option optionspec-neg --no-positive-only
 check_invalid_long_option optionspec-neg --negative
 check_invalid_long_option optionspec-neg --no-no-negative
 
+test_expect_success 'ambiguous: --no matches both --noble and --no-noble' '
+       cat >spec <<-\EOF &&
+       some-command [options]
+       --
+       noble The feudal switch.
+       EOF
+       test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
+       git rev-parse --parseopt -- <spec 2>err --no &&
+       grep "error: ambiguous option: no (could be --noble or --no-noble)" err
+'
+
 test_done
index e36f4d15f2ddd4f64d6aaf8e087370c3b9f92ce5..33aba87b9a4db314880a640b7ae65d7ba9c76a06 100755 (executable)
@@ -75,13 +75,13 @@ test_expect_success 'git branch HEAD should fail' '
        test_must_fail git branch HEAD
 '
 
-cat >expect <<EOF
-$HEAD refs/heads/d/e/f@{0}: branch: Created from main
-EOF
 test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '
        GIT_COMMITTER_DATE="2005-05-26 23:30" \
        git -c core.logallrefupdates=false branch --create-reflog d/e/f &&
        test_ref_exists refs/heads/d/e/f &&
+       cat >expect <<-EOF &&
+       $HEAD refs/heads/d/e/f@{0}: branch: Created from main
+       EOF
        git reflog show --no-abbrev-commit refs/heads/d/e/f >actual &&
        test_cmp expect actual
 '
@@ -440,10 +440,10 @@ test_expect_success 'git branch --list -v with --abbrev' '
 
 test_expect_success 'git branch --column' '
        COLUMNS=81 git branch --column=column >actual &&
-       cat >expect <<\EOF &&
-  a/b/c   bam     foo     l     * main    n       o/p     r
-  abc     bar     j/k     m/m     mb      o/o     q       topic
-EOF
+       cat >expect <<-\EOF &&
+         a/b/c   bam     foo     l     * main    n       o/p     r
+         abc     bar     j/k     m/m     mb      o/o     q       topic
+       EOF
        test_cmp expect actual
 '
 
@@ -453,25 +453,25 @@ test_expect_success 'git branch --column with an extremely long branch name' '
        test_when_finished "git branch -d $long" &&
        git branch $long &&
        COLUMNS=80 git branch --column=column >actual &&
-       cat >expect <<EOF &&
-  a/b/c
-  abc
-  bam
-  bar
-  foo
-  j/k
-  l
-  m/m
-* main
-  mb
-  n
-  o/o
-  o/p
-  q
-  r
-  topic
-  $long
-EOF
+       cat >expect <<-EOF &&
+         a/b/c
+         abc
+         bam
+         bar
+         foo
+         j/k
+         l
+         m/m
+       * main
+         mb
+         n
+         o/o
+         o/p
+         q
+         r
+         topic
+         $long
+       EOF
        test_cmp expect actual
 '
 
@@ -481,10 +481,10 @@ test_expect_success 'git branch with column.*' '
        COLUMNS=80 git branch >actual &&
        git config --unset column.branch &&
        git config --unset column.ui &&
-       cat >expect <<\EOF &&
-  a/b/c   bam   foo   l   * main   n     o/p   r
-  abc     bar   j/k   m/m   mb     o/o   q     topic
-EOF
+       cat >expect <<-\EOF &&
+         a/b/c   bam   foo   l   * main   n     o/p   r
+         abc     bar   j/k   m/m   mb     o/o   q     topic
+       EOF
        test_cmp expect actual
 '
 
@@ -496,39 +496,36 @@ test_expect_success 'git branch -v with column.ui ignored' '
        git config column.ui column &&
        COLUMNS=80 git branch -v | cut -c -8 | sed "s/ *$//" >actual &&
        git config --unset column.ui &&
-       cat >expect <<\EOF &&
-  a/b/c
-  abc
-  bam
-  bar
-  foo
-  j/k
-  l
-  m/m
-* main
-  mb
-  n
-  o/o
-  o/p
-  q
-  r
-  topic
-EOF
+       cat >expect <<-\EOF &&
+         a/b/c
+         abc
+         bam
+         bar
+         foo
+         j/k
+         l
+         m/m
+       * main
+         mb
+         n
+         o/o
+         o/p
+         q
+         r
+         topic
+       EOF
        test_cmp expect actual
 '
 
-mv .git/config .git/config-saved
-
 test_expect_success DEFAULT_REPO_FORMAT 'git branch -m q q2 without config should succeed' '
+       test_when_finished mv .git/config-saved .git/config &&
+       mv .git/config .git/config-saved &&
        git branch -m q q2 &&
        git branch -m q2 q
 '
 
-mv .git/config-saved .git/config
-
-git config branch.s/s.dummy Hello
-
 test_expect_success 'git branch -m s/s s should work when s/t is deleted' '
+       git config branch.s/s.dummy Hello &&
        git branch --create-reflog s/s &&
        git reflog exists refs/heads/s/s &&
        git branch --create-reflog s/t &&
@@ -1112,14 +1109,14 @@ test_expect_success '--set-upstream-to notices an error to set branch as own ups
        test_cmp expect actual
 "
 
-# Keep this test last, as it changes the current branch
-cat >expect <<EOF
-$HEAD refs/heads/g/h/i@{0}: branch: Created from main
-EOF
 test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
+       test_when_finished git checkout main &&
        GIT_COMMITTER_DATE="2005-05-26 23:30" \
        git checkout -b g/h/i -l main &&
        test_ref_exists refs/heads/g/h/i &&
+       cat >expect <<-EOF &&
+       $HEAD refs/heads/g/h/i@{0}: branch: Created from main
+       EOF
        git reflog show --no-abbrev-commit refs/heads/g/h/i >actual &&
        test_cmp expect actual
 '
@@ -1696,4 +1693,14 @@ test_expect_success '--track overrides branch.autoSetupMerge' '
        test_cmp_config "" --default "" branch.foo5.merge
 '
 
+test_expect_success 'errors if given a bad branch name' '
+       cat <<-\EOF >expect &&
+       fatal: '\''foo..bar'\'' is not a valid branch name
+       hint: See `man git check-ref-format`
+       hint: Disable this message with "git config advice.refSyntax false"
+       EOF
+       test_must_fail git branch foo..bar >actual 2>&1 &&
+       test_cmp expect actual
+'
+
 test_done
index d7382709fc11055be003751d802f953ed56b6eab..f698d0c9ad274137a9aa254c8e89bcece8f4cddd 100755 (executable)
@@ -312,6 +312,38 @@ test_expect_success 'shortlog de-duplicates trailers in a single commit' '
        test_cmp expect actual
 '
 
+# Trailers that have unfolded (single line) and folded (multiline) values which
+# are otherwise identical are treated as the same trailer for de-duplication.
+test_expect_success 'shortlog de-duplicates trailers in a single commit (folded/unfolded values)' '
+       git commit --allow-empty -F - <<-\EOF &&
+       subject one
+
+       this message has two distinct values, plus a repeat (folded)
+
+       Repeated-trailer: Foo foo foo
+       Repeated-trailer: Bar
+       Repeated-trailer: Foo
+         foo foo
+       EOF
+
+       git commit --allow-empty -F - <<-\EOF &&
+       subject two
+
+       similar to the previous, but without the second distinct value
+
+       Repeated-trailer: Foo foo foo
+       Repeated-trailer: Foo
+         foo foo
+       EOF
+
+       cat >expect <<-\EOF &&
+            2  Foo foo foo
+            1  Bar
+       EOF
+       git shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'shortlog can match multiple groups' '
        git commit --allow-empty -F - <<-\EOF &&
        subject one
index a400bcca622f464f13fe4f0a57c4d44ec8dd827b..e93e0d0cc397a323bae99e9f92ee5d02cb026f2b 100755 (executable)
@@ -120,14 +120,14 @@ test_expect_success 'prefers -c config over --template config' '
 
 '
 
-test_expect_failure 'prefers --template config even for core.bare' '
+test_expect_success 'ignore --template config for core.bare' '
 
        template="$TRASH_DIRECTORY/template-with-bare-config" &&
        mkdir "$template" &&
        git config --file "$template/config" core.bare true &&
        git clone "--template=$template" parent clone-bare-config &&
-       test "$(git -C clone-bare-config config --local core.bare)" = "true" &&
-       test_path_is_file clone-bare-config/HEAD
+       test "$(git -C clone-bare-config config --local core.bare)" = "false" &&
+       test_path_is_missing clone-bare-config/HEAD
 '
 
 test_expect_success 'prefers config "clone.defaultRemoteName" over default' '
index 611b3dd3aedb44c9b5d4657f3058c72a60258cb0..1f7201eb60caf9b31fd3db1a83dc834bb145157c 100755 (executable)
@@ -407,6 +407,12 @@ test_expect_success 'clean.requireForce and -f' '
 
 '
 
+test_expect_success 'clean.requireForce and --interactive' '
+       git clean --interactive </dev/null >output 2>error &&
+       test_grep ! "requireForce is true and" error &&
+       test_grep "\*\*\* Commands \*\*\*" output
+'
+
 test_expect_success 'core.excludesfile' '
 
        echo excludes >excludes &&
index d82a3210a1db48288c54ce3c06d430546da70f48..4afe53c66ae57acdadc7177b98fd0aacefdd576c 100755 (executable)
@@ -25,18 +25,18 @@ test_expect_success 'git clean -i (c: clean hotkey)' '
        touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \
        docs/manual.txt obj.o build/lib.so &&
        echo c | git clean -i &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test ! -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_missing src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -46,18 +46,18 @@ test_expect_success 'git clean -i (cl: clean prefix)' '
        touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \
        docs/manual.txt obj.o build/lib.so &&
        echo cl | git clean -i &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test ! -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_missing src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -67,18 +67,18 @@ test_expect_success 'git clean -i (quit)' '
        touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \
        docs/manual.txt obj.o build/lib.so &&
        echo quit | git clean -i &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -88,18 +88,18 @@ test_expect_success 'git clean -i (Ctrl+D)' '
        touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \
        docs/manual.txt obj.o build/lib.so &&
        echo "\04" | git clean -i &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -110,18 +110,18 @@ test_expect_success 'git clean -id (filter all)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines f "*" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -132,18 +132,18 @@ test_expect_success 'git clean -id (filter patterns)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines f "part3.* *.out" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test ! -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test ! -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_missing src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -154,18 +154,18 @@ test_expect_success 'git clean -id (filter patterns 2)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines f "* !*.out" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -176,18 +176,18 @@ test_expect_success 'git clean -id (select - all)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "*" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test ! -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test ! -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_missing src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -198,18 +198,18 @@ test_expect_success 'git clean -id (select - none)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -220,18 +220,18 @@ test_expect_success 'git clean -id (select - number)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s 3 "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -242,18 +242,18 @@ test_expect_success 'git clean -id (select - number 2)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "2 3" 5 "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test ! -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -264,18 +264,18 @@ test_expect_success 'git clean -id (select - number 3)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "3,4 5" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -285,11 +285,11 @@ test_expect_success 'git clean -id (select - filenames)' '
        touch a.out foo.txt bar.txt baz.txt &&
        test_write_lines s "a.out fo ba bar" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test ! -f a.out &&
-       test ! -f foo.txt &&
-       test ! -f bar.txt &&
-       test -f baz.txt &&
+       test_path_is_file Makefile &&
+       test_path_is_missing a.out &&
+       test_path_is_missing foo.txt &&
+       test_path_is_missing bar.txt &&
+       test_path_is_file baz.txt &&
        rm baz.txt
 
 '
@@ -301,18 +301,18 @@ test_expect_success 'git clean -id (select - range)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "1,3-4" 2 "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test ! -f docs/manual.txt &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -323,18 +323,18 @@ test_expect_success 'git clean -id (select - range 2)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "4- 1" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test ! -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_missing src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -345,18 +345,18 @@ test_expect_success 'git clean -id (inverse select)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines s "*" "-5- 1 -2" "" c |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -367,18 +367,18 @@ test_expect_success 'git clean -id (ask)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines a Y y no yes bad "" |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test ! -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -389,18 +389,18 @@ test_expect_success 'git clean -id (ask - Ctrl+D)' '
        docs/manual.txt obj.o build/lib.so &&
        test_write_lines a Y no yes "\04" |
        git clean -id &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -412,18 +412,18 @@ test_expect_success 'git clean -id with prefix and path (filter)' '
        (cd build/ &&
         test_write_lines f docs "*.h" "" c |
         git clean -id ..) &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_file docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -435,18 +435,18 @@ test_expect_success 'git clean -id with prefix and path (select by name)' '
        (cd build/ &&
         test_write_lines s ../docs/ ../src/part3.c ../src/part4.c "" c |
         git clean -id ..) &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test -f a.out &&
-       test ! -f docs/manual.txt &&
-       test ! -f src/part3.c &&
-       test -f src/part3.h &&
-       test ! -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_file a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_missing src/part3.c &&
+       test_path_is_file src/part3.h &&
+       test_path_is_missing src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
@@ -458,18 +458,18 @@ test_expect_success 'git clean -id with prefix and path (ask)' '
        (cd build/ &&
         test_write_lines a Y y no yes bad "" |
         git clean -id ..) &&
-       test -f Makefile &&
-       test -f README &&
-       test -f src/part1.c &&
-       test -f src/part2.c &&
-       test ! -f a.out &&
-       test ! -f docs/manual.txt &&
-       test -f src/part3.c &&
-       test ! -f src/part3.h &&
-       test -f src/part4.c &&
-       test -f src/part4.h &&
-       test -f obj.o &&
-       test -f build/lib.so
+       test_path_is_file Makefile &&
+       test_path_is_file README &&
+       test_path_is_file src/part1.c &&
+       test_path_is_file src/part2.c &&
+       test_path_is_missing a.out &&
+       test_path_is_missing docs/manual.txt &&
+       test_path_is_file src/part3.c &&
+       test_path_is_missing src/part3.h &&
+       test_path_is_file src/part4.c &&
+       test_path_is_file src/part4.h &&
+       test_path_is_file obj.o &&
+       test_path_is_file build/lib.so
 
 '
 
index ec9c6de114fddfdb7127434ae17f3ae419164f6c..3d3e13ccf87215adc1f8dcc8cc674e13d0f4bc91 100755 (executable)
@@ -1935,4 +1935,18 @@ test_expect_success 'suppressing --- does not disable cut-line handling' '
        test_cmp expected actual
 '
 
+test_expect_success 'handling of --- lines in conjunction with cut-lines' '
+       echo "my-trailer: here" >expected &&
+
+       git interpret-trailers --parse >actual <<-\EOF &&
+       subject
+
+       my-trailer: here
+       ---
+       # ------------------------ >8 ------------------------
+       EOF
+
+       test_cmp expected actual
+'
+
 test_done
index 363f9dc0e41b2686aa9892d5e21e153ca54727b9..730f3c7f81090e9d08d02b1ca0e370b05b3dd746 100755 (executable)
@@ -1037,4 +1037,227 @@ test_expect_success 'split-index and FSMonitor work well together' '
        )
 '
 
+# The FSMonitor daemon reports the OBSERVED pathname of modified files
+# and thus contains the OBSERVED spelling on case-insensitive file
+# systems.  The daemon does not (and should not) load the .git/index
+# file and therefore does not know the expected case-spelling.  Since
+# it is possible for the user to create files/subdirectories with the
+# incorrect case, a modified file event for a tracked will not have
+# the EXPECTED case. This can cause `index_name_pos()` to incorrectly
+# report that the file is untracked. This causes the client to fail to
+# mark the file as possibly dirty (keeping the CE_FSMONITOR_VALID bit
+# set) so that `git status` will avoid inspecting it and thus not
+# present in the status output.
+#
+# The setup is a little contrived.
+#
+test_expect_success CASE_INSENSITIVE_FS 'fsmonitor subdir case wrong on disk' '
+       test_when_finished "stop_daemon_delete_repo subdir_case_wrong" &&
+
+       git init subdir_case_wrong &&
+       (
+               cd subdir_case_wrong &&
+               echo x >AAA &&
+               echo x >BBB &&
+
+               mkdir dir1 &&
+               echo x >dir1/file1 &&
+               mkdir dir1/dir2 &&
+               echo x >dir1/dir2/file2 &&
+               mkdir dir1/dir2/dir3 &&
+               echo x >dir1/dir2/dir3/file3 &&
+
+               echo x >yyy &&
+               echo x >zzz &&
+               git add . &&
+               git commit -m "data" &&
+
+               # This will cause "dir1/" and everything under it
+               # to be deleted.
+               git sparse-checkout set --cone --sparse-index &&
+
+               # Create dir2 with the wrong case and then let Git
+               # repopulate dir3 -- it will not correct the spelling
+               # of dir2.
+               mkdir dir1 &&
+               mkdir dir1/DIR2 &&
+               git sparse-checkout add dir1/dir2/dir3
+       ) &&
+
+       start_daemon -C subdir_case_wrong --tf "$PWD/subdir_case_wrong.trace" &&
+
+       # Enable FSMonitor in the client. Run enough commands for
+       # the .git/index to sync up with the daemon with everything
+       # marked clean.
+       git -C subdir_case_wrong config core.fsmonitor true &&
+       git -C subdir_case_wrong update-index --fsmonitor &&
+       git -C subdir_case_wrong status &&
+
+       # Make some files dirty so that FSMonitor gets FSEvents for
+       # each of them.
+       echo xx >>subdir_case_wrong/AAA &&
+       echo xx >>subdir_case_wrong/dir1/DIR2/dir3/file3 &&
+       echo xx >>subdir_case_wrong/zzz &&
+
+       GIT_TRACE_FSMONITOR="$PWD/subdir_case_wrong.log" \
+               git -C subdir_case_wrong --no-optional-locks status --short \
+                       >"$PWD/subdir_case_wrong.out" &&
+
+       # "git status" should have gotten file events for each of
+       # the 3 files.
+       #
+       # "dir2" should be in the observed case on disk.
+       grep "fsmonitor_refresh_callback" \
+               <"$PWD/subdir_case_wrong.log" \
+               >"$PWD/subdir_case_wrong.log1" &&
+
+       grep -q "AAA.*pos 0" "$PWD/subdir_case_wrong.log1" &&
+       grep -q "zzz.*pos 6" "$PWD/subdir_case_wrong.log1" &&
+
+       grep -q "dir1/DIR2/dir3/file3.*pos -3" "$PWD/subdir_case_wrong.log1" &&
+
+       # Verify that we get a mapping event to correct the case.
+       grep -q "MAP:.*dir1/DIR2/dir3/file3.*dir1/dir2/dir3/file3" \
+               "$PWD/subdir_case_wrong.log1" &&
+
+       # The refresh-callbacks should have caused "git status" to clear
+       # the CE_FSMONITOR_VALID bit on each of those files and caused
+       # the worktree scan to visit them and mark them as modified.
+       grep -q " M AAA" "$PWD/subdir_case_wrong.out" &&
+       grep -q " M zzz" "$PWD/subdir_case_wrong.out" &&
+       grep -q " M dir1/dir2/dir3/file3" "$PWD/subdir_case_wrong.out"
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'fsmonitor file case wrong on disk' '
+       test_when_finished "stop_daemon_delete_repo file_case_wrong" &&
+
+       git init file_case_wrong &&
+       (
+               cd file_case_wrong &&
+               echo x >AAA &&
+               echo x >BBB &&
+
+               mkdir dir1 &&
+               mkdir dir1/dir2 &&
+               mkdir dir1/dir2/dir3 &&
+               echo x >dir1/dir2/dir3/FILE-3-B &&
+               echo x >dir1/dir2/dir3/XXXX-3-X &&
+               echo x >dir1/dir2/dir3/file-3-a &&
+               echo x >dir1/dir2/dir3/yyyy-3-y &&
+               mkdir dir1/dir2/dir4 &&
+               echo x >dir1/dir2/dir4/FILE-4-A &&
+               echo x >dir1/dir2/dir4/XXXX-4-X &&
+               echo x >dir1/dir2/dir4/file-4-b &&
+               echo x >dir1/dir2/dir4/yyyy-4-y &&
+
+               echo x >yyy &&
+               echo x >zzz &&
+               git add . &&
+               git commit -m "data"
+       ) &&
+
+       start_daemon -C file_case_wrong --tf "$PWD/file_case_wrong.trace" &&
+
+       # Enable FSMonitor in the client. Run enough commands for
+       # the .git/index to sync up with the daemon with everything
+       # marked clean.
+       git -C file_case_wrong config core.fsmonitor true &&
+       git -C file_case_wrong update-index --fsmonitor &&
+       git -C file_case_wrong status &&
+
+       # Make some files dirty so that FSMonitor gets FSEvents for
+       # each of them.
+       echo xx >>file_case_wrong/AAA &&
+       echo xx >>file_case_wrong/zzz &&
+
+       # Rename some files so that FSMonitor sees a create and delete
+       # FSEvent for each.  (A simple "mv foo FOO" is not portable
+       # between macOS and Windows. It works on both platforms, but makes
+       # the test messy, since (1) one platform updates "ctime" on the
+       # moved file and one does not and (2) it causes a directory event
+       # on one platform and not on the other which causes additional
+       # scanning during "git status" which causes a "H" vs "h" discrepancy
+       # in "git ls-files -f".)  So old-school it and move it out of the
+       # way and copy it to the case-incorrect name so that we get fresh
+       # "ctime" and "mtime" values.
+
+       mv file_case_wrong/dir1/dir2/dir3/file-3-a file_case_wrong/dir1/dir2/dir3/ORIG &&
+       cp file_case_wrong/dir1/dir2/dir3/ORIG     file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
+       rm file_case_wrong/dir1/dir2/dir3/ORIG &&
+       mv file_case_wrong/dir1/dir2/dir4/FILE-4-A file_case_wrong/dir1/dir2/dir4/ORIG &&
+       cp file_case_wrong/dir1/dir2/dir4/ORIG     file_case_wrong/dir1/dir2/dir4/file-4-a &&
+       rm file_case_wrong/dir1/dir2/dir4/ORIG &&
+
+       # Run status enough times to fully sync.
+       #
+       # The first instance should get the create and delete FSEvents
+       # for each pair.  Status should update the index with a new FSM
+       # token (so the next invocation will not see data for these
+       # events).
+
+       GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try1.log" \
+               git -C file_case_wrong status --short \
+                       >"$PWD/file_case_wrong-try1.out" &&
+       grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try1.log" &&
+       grep -q "fsmonitor_refresh_callback.*file-3-a.*pos 4"  "$PWD/file_case_wrong-try1.log" &&
+       grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos 6"  "$PWD/file_case_wrong-try1.log" &&
+       grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try1.log" &&
+
+       # FSM refresh will have invalidated the FSM bit and cause a regular
+       # (real) scan of these tracked files, so they should have "H" status.
+       # (We will not see a "h" status until the next refresh (on the next
+       # command).)
+
+       git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf1.out" &&
+       grep -q "H dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf1.out" &&
+       grep -q "H dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf1.out" &&
+
+
+       # Try the status again. We assume that the above status command
+       # advanced the token so that the next one will not see those events.
+
+       GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try2.log" \
+               git -C file_case_wrong status --short \
+                       >"$PWD/file_case_wrong-try2.out" &&
+       ! grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos" "$PWD/file_case_wrong-try2.log" &&
+       ! grep -q "fsmonitor_refresh_callback.*file-3-a.*pos" "$PWD/file_case_wrong-try2.log" &&
+       ! grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos" "$PWD/file_case_wrong-try2.log" &&
+       ! grep -q "fsmonitor_refresh_callback.*file-4-a.*pos" "$PWD/file_case_wrong-try2.log" &&
+
+       # FSM refresh saw nothing, so it will mark all files as valid,
+       # so they should now have "h" status.
+
+       git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf2.out" &&
+       grep -q "h dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf2.out" &&
+       grep -q "h dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf2.out" &&
+
+
+       # We now have files with clean content, but with case-incorrect
+       # file names.  Modify them to see if status properly reports
+       # them.
+
+       echo xx >>file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
+       echo xx >>file_case_wrong/dir1/dir2/dir4/file-4-a &&
+
+       GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try3.log" \
+               git -C file_case_wrong --no-optional-locks status --short \
+                       >"$PWD/file_case_wrong-try3.out" &&
+
+       # Verify that we get a mapping event to correct the case.
+       grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir3/FILE-3-A.*dir1/dir2/dir3/file-3-a" \
+               "$PWD/file_case_wrong-try3.log" &&
+       grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir4/file-4-a.*dir1/dir2/dir4/FILE-4-A" \
+               "$PWD/file_case_wrong-try3.log" &&
+
+       # FSEvents are in observed case.
+       grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try3.log" &&
+       grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try3.log" &&
+
+       # The refresh-callbacks should have caused "git status" to clear
+       # the CE_FSMONITOR_VALID bit on each of those files and caused
+       # the worktree scan to visit them and mark them as modified.
+       grep -q " M dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-try3.out" &&
+       grep -q " M dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-try3.out"
+'
+
 test_done
index b16c284181b8707ed21c85efa0e0a0fea128faae..569cf2310434358d268028ea8d26361727bf8652 100755 (executable)
@@ -1263,6 +1263,29 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '
        test_cmp expected out
 '
 
+test_expect_success '__git_complete_worktree_paths' '
+       test_when_finished "git worktree remove other_wt" &&
+       git worktree add --orphan other_wt &&
+       run_completion "git worktree remove " &&
+       grep other_wt out
+'
+
+test_expect_success '__git_complete_worktree_paths - not a git repository' '
+       (
+               cd non-repo &&
+               GIT_CEILING_DIRECTORIES="$ROOT" &&
+               export GIT_CEILING_DIRECTORIES &&
+               test_completion "git worktree remove " ""
+       )
+'
+
+test_expect_success '__git_complete_worktree_paths with -C' '
+       test_when_finished "git -C otherrepo worktree remove otherrepo_wt" &&
+       git -C otherrepo worktree add --orphan otherrepo_wt &&
+       run_completion "git -C otherrepo worktree remove " &&
+       grep otherrepo_wt out
+'
+
 test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
        test_completion "git switch " <<-\EOF
        branch-in-other Z
@@ -2804,6 +2827,20 @@ test_expect_success 'git clone --config= - value' '
        EOF
 '
 
+test_expect_success 'git reflog show' '
+       test_when_finished "git checkout - && git branch -d shown" &&
+       git checkout -b shown &&
+       test_completion "git reflog sho" <<-\EOF &&
+       show Z
+       shown Z
+       EOF
+       test_completion "git reflog show sho" "shown " &&
+       test_completion "git reflog shown sho" "shown " &&
+       test_completion "git reflog --unt" "--until=" &&
+       test_completion "git reflog show --unt" "--until=" &&
+       test_completion "git reflog shown --unt" "--until="
+'
+
 test_expect_success 'options with value' '
        test_completion "git merge -X diff-algorithm=" <<-\EOF
 
index b5eaf7fdc1186d4420388c355e794bfdba783f2f..6eaf116346be3ee52d2094715ac979aa059093c5 100644 (file)
@@ -1263,9 +1263,8 @@ test_cmp_bin () {
        cmp "$@"
 }
 
-# Deprecated - do not use this in new code
 test_i18ngrep () {
-       test_grep "$@"
+       BUG "do not use test_i18ngrep---use test_grep instead"
 }
 
 test_grep () {
index ecdebf1afb0d8465717c18409877fe310f980e46..ed88cf84314753443c8c42f0043320957d65d165 100644 (file)
 
 static VOLATILE_LIST_HEAD(tempfile_list);
 
-static void remove_template_directory(struct tempfile *tempfile,
+static int remove_template_directory(struct tempfile *tempfile,
                                      int in_signal_handler)
 {
        if (tempfile->directory) {
                if (in_signal_handler)
-                       rmdir(tempfile->directory);
+                       return rmdir(tempfile->directory);
                else
-                       rmdir_or_warn(tempfile->directory);
+                       return rmdir_or_warn(tempfile->directory);
        }
+
+       return 0;
 }
 
 static void remove_tempfiles(int in_signal_handler)
@@ -353,16 +355,19 @@ int rename_tempfile(struct tempfile **tempfile_p, const char *path)
        return 0;
 }
 
-void delete_tempfile(struct tempfile **tempfile_p)
+int delete_tempfile(struct tempfile **tempfile_p)
 {
        struct tempfile *tempfile = *tempfile_p;
+       int err = 0;
 
        if (!is_tempfile_active(tempfile))
-               return;
+               return 0;
 
-       close_tempfile_gently(tempfile);
-       unlink_or_warn(tempfile->filename.buf);
-       remove_template_directory(tempfile, 0);
+       err |= close_tempfile_gently(tempfile);
+       err |= unlink_or_warn(tempfile->filename.buf);
+       err |= remove_template_directory(tempfile, 0);
        deactivate_tempfile(tempfile);
        *tempfile_p = NULL;
+
+       return err ? -1 : 0;
 }
index d0413af733c81ad895669aab30937435cae0f2af..2d2ae5b657d4a97a7fe7b8e2e84c2062717fdde5 100644 (file)
@@ -269,7 +269,7 @@ int reopen_tempfile(struct tempfile *tempfile);
  * `delete_tempfile()` for a `tempfile` object that has already been
  * deleted or renamed.
  */
-void delete_tempfile(struct tempfile **tempfile_p);
+int delete_tempfile(struct tempfile **tempfile_p);
 
 /*
  * Close the file descriptor and/or file pointer if they are still
index f1e268bd159ecb3d491721e67c34eabd397a66ea..f894532d05331c48607fc3434b8d31937f951a4b 100644 (file)
--- a/trace2.c
+++ b/trace2.c
@@ -433,6 +433,9 @@ void trace2_cmd_name_fl(const char *file, int line, const char *name)
        for_each_wanted_builtin (j, tgt_j)
                if (tgt_j->pfn_command_name_fl)
                        tgt_j->pfn_command_name_fl(file, line, name, hierarchy);
+
+       trace2_cmd_list_config();
+       trace2_cmd_list_env_vars();
 }
 
 void trace2_cmd_mode_fl(const char *file, int line, const char *mode)
@@ -464,17 +467,29 @@ void trace2_cmd_alias_fl(const char *file, int line, const char *alias,
 
 void trace2_cmd_list_config_fl(const char *file, int line)
 {
+       static int emitted = 0;
+
        if (!trace2_enabled)
                return;
 
+       if (emitted)
+               return;
+       emitted = 1;
+
        tr2_cfg_list_config_fl(file, line);
 }
 
 void trace2_cmd_list_env_vars_fl(const char *file, int line)
 {
+       static int emitted = 0;
+
        if (!trace2_enabled)
                return;
 
+       if (emitted)
+               return;
+       emitted = 1;
+
        tr2_list_env_vars_fl(file, line);
 }
 
index ef9df4af558fbb18849279cff2e67507026af106..57b4aa7d5acb515b03b2cf123744d8af34d30166 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -5,7 +5,6 @@
 #include "string-list.h"
 #include "run-command.h"
 #include "commit.h"
-#include "tempfile.h"
 #include "trailer.h"
 #include "list.h"
 /*
@@ -145,12 +144,12 @@ static char last_non_space_char(const char *s)
        return '\0';
 }
 
-static void print_tok_val(FILE *outfile, const char *tok, const char *val)
+static void print_tok_val(struct strbuf *out, const char *tok, const char *val)
 {
        char c;
 
        if (!tok) {
-               fprintf(outfile, "%s\n", val);
+               strbuf_addf(out, "%s\n", val);
                return;
        }
 
@@ -158,21 +157,22 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)
        if (!c)
                return;
        if (strchr(separators, c))
-               fprintf(outfile, "%s%s\n", tok, val);
+               strbuf_addf(out, "%s%s\n", tok, val);
        else
-               fprintf(outfile, "%s%c %s\n", tok, separators[0], val);
+               strbuf_addf(out, "%s%c %s\n", tok, separators[0], val);
 }
 
-static void print_all(FILE *outfile, struct list_head *head,
-                     const struct process_trailer_options *opts)
+void format_trailers(const struct process_trailer_options *opts,
+                    struct list_head *trailers,
+                    struct strbuf *out)
 {
        struct list_head *pos;
        struct trailer_item *item;
-       list_for_each(pos, head) {
+       list_for_each(pos, trailers) {
                item = list_entry(pos, struct trailer_item, list);
                if ((!opts->trim_empty || strlen(item->value) > 0) &&
                    (!opts->only_trailers || item->token))
-                       print_tok_val(outfile, item->token, item->value);
+                       print_tok_val(out, item->token, item->value);
        }
 }
 
@@ -366,8 +366,8 @@ static int find_same_and_apply_arg(struct list_head *head,
        return 0;
 }
 
-static void process_trailers_lists(struct list_head *head,
-                                  struct list_head *arg_head)
+void process_trailers_lists(struct list_head *head,
+                           struct list_head *arg_head)
 {
        struct list_head *pos, *p;
        struct arg_item *arg_tok;
@@ -589,7 +589,7 @@ static int git_trailer_config(const char *conf_key, const char *value,
        return 0;
 }
 
-static void ensure_configured(void)
+void trailer_config_init(void)
 {
        if (configured)
                return;
@@ -719,7 +719,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val,
        list_add_tail(&new_item->list, arg_head);
 }
 
-static void parse_trailers_from_config(struct list_head *config_head)
+void parse_trailers_from_config(struct list_head *config_head)
 {
        struct arg_item *item;
        struct list_head *pos;
@@ -735,8 +735,8 @@ static void parse_trailers_from_config(struct list_head *config_head)
        }
 }
 
-static void parse_trailers_from_command_line_args(struct list_head *arg_head,
-                                                 struct list_head *new_trailer_head)
+void parse_trailers_from_command_line_args(struct list_head *arg_head,
+                                          struct list_head *new_trailer_head)
 {
        struct strbuf tok = STRBUF_INIT;
        struct strbuf val = STRBUF_INIT;
@@ -775,17 +775,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head,
        free(cl_separators);
 }
 
-static void read_input_file(struct strbuf *sb, const char *file)
-{
-       if (file) {
-               if (strbuf_read_file(sb, file, 0) < 0)
-                       die_errno(_("could not read input file '%s'"), file);
-       } else {
-               if (strbuf_read(sb, fileno(stdin), 0) < 0)
-                       die_errno(_("could not read from stdin"));
-       }
-}
-
 static const char *next_line(const char *str)
 {
        const char *nl = strchrnul(str, '\n');
@@ -999,16 +988,16 @@ static void unfold_value(struct strbuf *val)
  * Parse trailers in "str", populating the trailer info and "head"
  * linked list structure.
  */
-static void parse_trailers(struct trailer_info *info,
-                            const char *str,
-                            struct list_head *head,
-                            const struct process_trailer_options *opts)
+void parse_trailers(const struct process_trailer_options *opts,
+                   struct trailer_info *info,
+                   const char *str,
+                   struct list_head *head)
 {
        struct strbuf tok = STRBUF_INIT;
        struct strbuf val = STRBUF_INIT;
        size_t i;
 
-       trailer_info_get(info, str, opts);
+       trailer_info_get(opts, str, info);
 
        for (i = 0; i < info->trailer_nr; i++) {
                int separator_pos;
@@ -1034,99 +1023,18 @@ static void parse_trailers(struct trailer_info *info,
        }
 }
 
-static void free_all(struct list_head *head)
+void free_trailers(struct list_head *trailers)
 {
        struct list_head *pos, *p;
-       list_for_each_safe(pos, p, head) {
+       list_for_each_safe(pos, p, trailers) {
                list_del(pos);
                free_trailer_item(list_entry(pos, struct trailer_item, list));
        }
 }
 
-static struct tempfile *trailers_tempfile;
-
-static FILE *create_in_place_tempfile(const char *file)
-{
-       struct stat st;
-       struct strbuf filename_template = STRBUF_INIT;
-       const char *tail;
-       FILE *outfile;
-
-       if (stat(file, &st))
-               die_errno(_("could not stat %s"), file);
-       if (!S_ISREG(st.st_mode))
-               die(_("file %s is not a regular file"), file);
-       if (!(st.st_mode & S_IWUSR))
-               die(_("file %s is not writable by user"), file);
-
-       /* Create temporary file in the same directory as the original */
-       tail = strrchr(file, '/');
-       if (tail)
-               strbuf_add(&filename_template, file, tail - file + 1);
-       strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX");
-
-       trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);
-       strbuf_release(&filename_template);
-       outfile = fdopen_tempfile(trailers_tempfile, "w");
-       if (!outfile)
-               die_errno(_("could not open temporary file"));
-
-       return outfile;
-}
-
-void process_trailers(const char *file,
-                     const struct process_trailer_options *opts,
-                     struct list_head *new_trailer_head)
-{
-       LIST_HEAD(head);
-       struct strbuf sb = STRBUF_INIT;
-       struct trailer_info info;
-       FILE *outfile = stdout;
-
-       ensure_configured();
-
-       read_input_file(&sb, file);
-
-       if (opts->in_place)
-               outfile = create_in_place_tempfile(file);
-
-       parse_trailers(&info, sb.buf, &head, opts);
-
-       /* Print the lines before the trailers */
-       if (!opts->only_trailers)
-               fwrite(sb.buf, 1, info.trailer_block_start, outfile);
-
-       if (!opts->only_trailers && !info.blank_line_before_trailer)
-               fprintf(outfile, "\n");
-
-
-       if (!opts->only_input) {
-               LIST_HEAD(config_head);
-               LIST_HEAD(arg_head);
-               parse_trailers_from_config(&config_head);
-               parse_trailers_from_command_line_args(&arg_head, new_trailer_head);
-               list_splice(&config_head, &arg_head);
-               process_trailers_lists(&head, &arg_head);
-       }
-
-       print_all(outfile, &head, opts);
-
-       free_all(&head);
-       trailer_info_release(&info);
-
-       /* Print the lines after the trailers as is */
-       if (!opts->only_trailers)
-               fwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);
-
-       if (opts->in_place)
-               if (rename_tempfile(&trailers_tempfile, file))
-                       die_errno(_("could not rename temporary file to %s"), file);
-
-       strbuf_release(&sb);
-}
-
-void trailer_info_get(struct trailer_info *info, const char *str,
-                     const struct process_trailer_options *opts)
+void trailer_info_get(const struct process_trailer_options *opts,
+                     const char *str,
+                     struct trailer_info *info)
 {
        size_t end_of_log_message = 0, trailer_block_start = 0;
        struct strbuf **trailer_lines, **ptr;
@@ -1134,7 +1042,7 @@ void trailer_info_get(struct trailer_info *info, const char *str,
        size_t nr = 0, alloc = 0;
        char **last = NULL;
 
-       ensure_configured();
+       trailer_config_init();
 
        end_of_log_message = find_end_of_log_message(str, opts->no_divider);
        trailer_block_start = find_trailer_block_start(str, end_of_log_message);
@@ -1176,23 +1084,13 @@ void trailer_info_release(struct trailer_info *info)
        free(info->trailers);
 }
 
-static void format_trailer_info(struct strbuf *out,
+static void format_trailer_info(const struct process_trailer_options *opts,
                                const struct trailer_info *info,
-                               const char *msg,
-                               const struct process_trailer_options *opts)
+                               struct strbuf *out)
 {
        size_t origlen = out->len;
        size_t i;
 
-       /* If we want the whole block untouched, we can take the fast path. */
-       if (!opts->only_trailers && !opts->unfold && !opts->filter &&
-           !opts->separator && !opts->key_only && !opts->value_only &&
-           !opts->key_value_separator) {
-               strbuf_add(out, msg + info->trailer_block_start,
-                          info->trailer_block_end - info->trailer_block_start);
-               return;
-       }
-
        for (i = 0; i < info->trailer_nr; i++) {
                char *trailer = info->trailers[i];
                ssize_t separator_pos = find_separator(trailer, separators);
@@ -1237,13 +1135,25 @@ static void format_trailer_info(struct strbuf *out,
 
 }
 
-void format_trailers_from_commit(struct strbuf *out, const char *msg,
-                                const struct process_trailer_options *opts)
+void format_trailers_from_commit(const struct process_trailer_options *opts,
+                                const char *msg,
+                                struct strbuf *out)
 {
+       LIST_HEAD(trailer_objects);
        struct trailer_info info;
 
-       trailer_info_get(&info, msg, opts);
-       format_trailer_info(out, &info, msg, opts);
+       parse_trailers(opts, &info, msg, &trailer_objects);
+
+       /* If we want the whole block untouched, we can take the fast path. */
+       if (!opts->only_trailers && !opts->unfold && !opts->filter &&
+           !opts->separator && !opts->key_only && !opts->value_only &&
+           !opts->key_value_separator) {
+               strbuf_add(out, msg + info.trailer_block_start,
+                          info.trailer_block_end - info.trailer_block_start);
+       } else
+               format_trailer_info(opts, &info, out);
+
+       free_trailers(&trailer_objects);
        trailer_info_release(&info);
 }
 
@@ -1253,7 +1163,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
        strbuf_init(&iter->key, 0);
        strbuf_init(&iter->val, 0);
        opts.no_divider = 1;
-       trailer_info_get(&iter->internal.info, msg, &opts);
+       trailer_info_get(&opts, msg, &iter->internal.info);
        iter->internal.cur = 0;
 }
 
@@ -1270,6 +1180,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter)
                strbuf_reset(&iter->val);
                parse_trailer(&iter->key, &iter->val, NULL,
                              trailer, separator_pos);
+               /* Always unfold values during iteration. */
                unfold_value(&iter->val);
                return 1;
        }
index 1644cd05f60d9f3ceb89ffa66fb5c5b3da9e5cd4..1d106b6dd403b7755e4c81a1a40c1eea8eeae1d6 100644 (file)
--- a/trailer.h
+++ b/trailer.h
@@ -81,15 +81,31 @@ struct process_trailer_options {
 
 #define PROCESS_TRAILER_OPTIONS_INIT {0}
 
-void process_trailers(const char *file,
-                     const struct process_trailer_options *opts,
-                     struct list_head *new_trailer_head);
+void parse_trailers_from_config(struct list_head *config_head);
 
-void trailer_info_get(struct trailer_info *info, const char *str,
-                     const struct process_trailer_options *opts);
+void parse_trailers_from_command_line_args(struct list_head *arg_head,
+                                          struct list_head *new_trailer_head);
+
+void process_trailers_lists(struct list_head *head,
+                           struct list_head *arg_head);
+
+void parse_trailers(const struct process_trailer_options *,
+                   struct trailer_info *,
+                   const char *str,
+                   struct list_head *head);
+
+void trailer_info_get(const struct process_trailer_options *,
+                     const char *str,
+                     struct trailer_info *);
 
 void trailer_info_release(struct trailer_info *info);
 
+void trailer_config_init(void);
+void format_trailers(const struct process_trailer_options *,
+                    struct list_head *trailers,
+                    struct strbuf *out);
+void free_trailers(struct list_head *);
+
 /*
  * Format the trailers from the commit msg "msg" into the strbuf "out".
  * Note two caveats about "opts":
@@ -101,8 +117,9 @@ void trailer_info_release(struct trailer_info *info);
  *     only the trailer block itself, even if the "only_trailers" option is not
  *     set.
  */
-void format_trailers_from_commit(struct strbuf *out, const char *msg,
-                                const struct process_trailer_options *opts);
+void format_trailers_from_commit(const struct process_trailer_options *opts,
+                                const char *msg,
+                                struct strbuf *out);
 
 /*
  * An interface for iterating over the trailers found in a particular commit
index dd6002b393738df81c7ecfc85deb4ed0abe4943c..b660b7942f9f9dbfc76193968d3511047b8afd84 100644 (file)
@@ -1078,7 +1078,7 @@ static int push_refs_with_export(struct transport *transport,
        set_common_push_options(transport, data->name, flags);
        if (flags & TRANSPORT_PUSH_FORCE) {
                if (set_helper_option(transport, "force", "true") != 0)
-                       warning(_("helper %s does not support 'force'"), data->name);
+                       warning(_("helper %s does not support '--force'"), data->name);
        }
 
        helper = get_helper(transport);
index 7108a92b52ce06e5674aadfc5c89d05157a9db93..2db4bb3a1293bb69e081efcf98489a63ca2d812a 100644 (file)
@@ -1093,8 +1093,11 @@ size_t wt_status_locate_end(const char *s, size_t len)
        strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line);
        if (starts_with(s, pattern.buf + 1))
                len = 0;
-       else if ((p = strstr(s, pattern.buf)))
-               len = p - s + 1;
+       else if ((p = strstr(s, pattern.buf))) {
+               size_t newlen = p - s + 1;
+               if (newlen < len)
+                       len = newlen;
+       }
        strbuf_release(&pattern);
        return len;
 }