diff options
388 files changed, 9189 insertions, 11095 deletions
diff --git a/.gitattributes b/.gitattributes index c6a0b35116..32583149c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,6 +13,7 @@ CODE_OF_CONDUCT.md -whitespace /mergetools/* text eol=lf /t/oid-info/* text eol=lf /Documentation/git-merge.adoc conflict-marker-size=32 +/Documentation/git-merge-file.adoc conflict-marker-size=32 /Documentation/gitk.adoc conflict-marker-size=32 /Documentation/user-manual.adoc conflict-marker-size=32 /t/t????-*.sh conflict-marker-size=32 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37541f3d10..83ca8e4182 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -265,7 +265,7 @@ jobs: run: pip install meson ninja - name: Setup shell: pwsh - run: meson setup build -Dperl=disabled -Dcredential_helpers=wincred + run: meson setup build --vsenv -Dperl=disabled -Dcredential_helpers=wincred - name: Compile shell: pwsh run: meson compile -C build @@ -448,21 +448,12 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' env: jobname: sparse - CI_JOB_IMAGE: ubuntu-20.04 - runs-on: ubuntu-20.04 + CI_JOB_IMAGE: ubuntu-22.04 + runs-on: ubuntu-22.04 concurrency: group: sparse-${{ github.ref }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - - name: Download a current `sparse` package - # Ubuntu's `sparse` version is too old for us - uses: git-for-windows/get-azure-pipelines-artifact@v0 - with: - repository: git/git - definitionId: 10 - artifact: sparse-20.04 - - name: Install the current `sparse` package - run: sudo dpkg -i sparse-20.04/sparse_*.deb - uses: actions/checkout@v4 - name: Install other dependencies run: ci/install-dependencies.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2805cdeecb..4798b28374 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -164,7 +164,7 @@ build:msvc-meson: extends: .msvc-meson stage: build script: - - meson setup build -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred + - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred - meson compile -C build artifacts: paths: diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index a0e7041c54..c1046abfb7 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -861,6 +861,9 @@ Markup: _<git-dir>_ _<key-id>_ +Characters are also surrounded by underscores: + _LF_, _CR_, _CR_/_LF_, _NUL_, _EOF_ + Git's Asciidoc processor has been tailored to treat backticked text as complex synopsis. When literal and placeholders are mixed, you can use the backtick notation which will take care of correctly typesetting diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index b787dce3da..3ab82ac3a2 100644 --- a/Documentation/RelNotes/2.50.0.adoc +++ b/Documentation/RelNotes/2.50.0.adoc @@ -15,6 +15,42 @@ UI, Workflows & Features * Incrementally updating multi-pack index files. + * "git reflog" learns "drop" subcommand, that discards the entire + reflog data for a ref. + + * A new userdiff driver for ".ini" format configuration files has + been added. + + * The job to coalesce loose objects into packfiles in "git + maintenance" now has configurable batch size. + + * "git clone" still gave the message about the default branch name; + this message has been turned into an advice message that can be + turned off. + + * "git rev-list" learns machine-parsable output format that delimits + each field with NUL. + + * "git maintenance" learns a new task to expire reflog entries. + + * Auth-related (and unrelated) error handling in send-email has been + made more robust. + + * Updating multiple references have only been possible in all-or-none + fashion with transactions, but it can be more efficient to batch + multiple updates even when some of them are allowed to fail in a + best-effort manner. A new "best effort batches of updates" mode + has been introduced. + + * "git help --build-options" reports SHA-1 and SHA-256 backends used + in the build. + + * "git cat-file --batch" and friends learned to allow "--filter=" to + omit certain objects, just like the transport layer does. + + * "git blame --porcelain" mode now talks about unblamable lines and + lines that are blamed to an ignored commit. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -55,6 +91,30 @@ Performance, Internal Implementation, Development Support etc. * CI update. + * The object layer has been updated to take an explicit repository + instance as a parameter in more code paths. + + * Some warnings from "-Wsign-compare" for builtin/rm.c have been + squelched. + + * A few traditional unit tests have been rewritten to use the clar + framework. + + * Some warnings from "-Wsign-compare" for pathspec.c have been + squelched. + + * "make test" used to have a hard dependency on (basic) Perl; tests + have been rewritten help environment with NO_PERL test the build as + much as possible. + + * Remove remnants of the recursive merge strategy backend, which was + superseded by the ort merge strategy. + + * Optimize the code to dedup references recorded in a bundle file. + + * Update parse-options API to catch mistakes to pass address of an + integral variable of a wrong type/size. + Fixes since v2.49 ----------------- @@ -107,6 +167,63 @@ Fixes since v2.49 which has been corrected. (merge 93bab2d04b fr/vimdiff-layout-fixes later to maint). + * Fix our use of zlib corner cases. + (merge 1cb2f293f5 jk/zlib-inflate-fixes later to maint). + + * Fix lockfile contention in reftable code on Windows. + (merge 0a3dceabf1 ps/mingw-creat-excl-fix later to maint). + + * "git-merge-file" documentation source, which has lines that look + like conflict markers, lacked custom conflict marker size defined, + which has been corrected.. + (merge d3b5832381 pw/custom-conflict-marker-size-for-merge-related-docs later to maint). + + * Squelch false-positive from sparse. + (merge da87b58014 dd/sparse-glibc-workaround later to maint). + + * Adjust to the deprecation of use of Ubuntu 20.04 GitHub Actions CI. + (merge 832d9f6d0b js/ci-github-update-ubuntu later to maint). + + * Work around CI breakage due to fedora base image getting updated. + (merge 8a471a663b js/ci-fedora-gawk later to maint). + + * A ref transaction corner case fix. + (merge b9fadeead7 jt/ref-transaction-abort-fix later to maint). + + * Random build fixes. + (merge 85e1d6819f ps/misc-build-fixes later to maint). + + * "git fetch [<remote>]" with only the configured fetch refspec + should be the only thing to update refs/remotes/<remote>/HEAD, + but the code was overly eager to do so in other cases. + + * Incorrect sorting of refs with bytes with high-bit set on platforms + with signed char led to a BUG, which has been corrected. + + * "make perf" fixes. + (merge 1665f12fa0 pb/perf-test-fixes later to maint). + + * Doc mark-up updates. + (merge 5a5565ec44 ja/doc-reset-mv-rm-markup-updates later to maint). + + * Work around false positive from CodeQL checker. + (merge 0f558141ed js/range-check-codeql-workaround later to maint). + + * "git log --{left,right}-only A...B", when A and B does not share + any common ancestor, now behaves as expected. + (merge e7ef4be7c2 mh/left-right-limited later to maint). + + * Document the convention to disable hooks altogether by setting the + hooksPath configuration variable to /dev/nulll + (merge 1b2eee94f1 ds/doc-disable-hooks later to maint). + + * Make sure outage of third-party sites that supply P4, Git-LFS, and + JGit we use for testing would not prevent our CI jobs from running + at all. + + * Various build tweaks, including CSPRNG selection on some platforms. + (merge cdda67de03 rj/build-tweaks later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 227c4f33a0 ja/doc-block-delimiter-markup-fix later to maint). (merge 2bfd3b3685 ab/decorate-code-cleanup later to maint). @@ -124,3 +241,6 @@ Fixes since v2.49 (merge 133d065dd6 ta/bulk-checkin-signed-compare-false-warning-fix later to maint). (merge d2827dc31e es/meson-build-skip-coccinelle later to maint). (merge ee8edb7156 dk/vimdiff-doc-fix later to maint). + (merge 107d889303 md/t1403-path-is-file later to maint). + (merge abd4192b07 js/comma-semicolon-confusion later to maint). + (merge 27b7264206 ab/environment-clean-header later to maint). diff --git a/Documentation/asciidoc.conf.in b/Documentation/asciidoc.conf.in index f2aef6cb79..9d9139306e 100644 --- a/Documentation/asciidoc.conf.in +++ b/Documentation/asciidoc.conf.in @@ -43,7 +43,7 @@ ifdef::doctype-book[] endif::doctype-book[] [literal-inlinemacro] -{eval:re.sub(r'(<[-a-zA-Z0-9.]+>)', r'<emphasis>\1</emphasis>', re.sub(r'([\[\s|()>]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,\/_^\$]+\.?)+)',r'\1<literal>\2</literal>', re.sub(r'(\.\.\.?)([^\]$.])', r'<literal>\1</literal>\2', macros.passthroughs[int(attrs['passtext'][1:-1])] if attrs['passtext'][1:-1].isnumeric() else attrs['passtext'][1:-1])))} +{eval:re.sub(r'(<[-a-zA-Z0-9.]+>)', r'<emphasis>\1</emphasis>', re.sub(r'([\[\s|()>]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@\\\*\/_^\$]+\.?)+|,)',r'\1<literal>\2</literal>', re.sub(r'(\.\.\.?)([^\]$.])', r'<literal>\1</literal>\2', macros.passthroughs[int(attrs['passtext'][1:-1])] if attrs['passtext'][1:-1].isnumeric() else attrs['passtext'][1:-1])))} endif::backend-docbook[] @@ -75,18 +75,18 @@ git-relative-html-prefix= <a href="{git-relative-html-prefix}{target}.html">{target}{0?({0})}</a> [literal-inlinemacro] -{eval:re.sub(r'(<[-a-zA-Z0-9.]+>)', r'<em>\1</em>', re.sub(r'([\[\s|()>]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,\/_^\$]+\.?)+)',r'\1<code>\2</code>', re.sub(r'(\.\.\.?)([^\]$.])', r'<code>\1</code>\2', macros.passthroughs[int(attrs['passtext'][1:-1])] if attrs['passtext'][1:-1].isnumeric() else attrs['passtext'][1:-1])))} +{eval:re.sub(r'(<[-a-zA-Z0-9.]+>)', r'<em>\1</em>', re.sub(r'([\[\s|()>]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,\\\*\/_^\$]+\.?)+)',r'\1<code>\2</code>', re.sub(r'(\.\.\.?)([^\]$.])', r'<code>\1</code>\2', macros.passthroughs[int(attrs['passtext'][1:-1])] if attrs['passtext'][1:-1].isnumeric() else attrs['passtext'][1:-1])))} endif::backend-xhtml11[] ifdef::backend-docbook[] ifdef::doctype-manpage[] [paradef-default] -synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" endif::doctype-manpage[] endif::backend-docbook[] ifdef::backend-xhtml11[] [paradef-default] -synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" endif::backend-xhtml11[] diff --git a/Documentation/asciidoctor-extensions.rb.in b/Documentation/asciidoctor-extensions.rb.in index 2494f17a51..8b7b161349 100644 --- a/Documentation/asciidoctor-extensions.rb.in +++ b/Documentation/asciidoctor-extensions.rb.in @@ -49,8 +49,8 @@ module Git def process parent, reader, attrs outlines = reader.lines.map do |l| - l.gsub(/(\.\.\.?)([^\]$.])/, '`\1`\2') - .gsub(%r{([\[\] |()>]|^)([-a-zA-Z0-9:+=~@,/_^\$]+)}, '\1{empty}`\2`{empty}') + l.gsub(/(\.\.\.?)([^\]$\. ])/, '{empty}`\1`{empty}\2') + .gsub(%r{([\[\] |()>]|^)([-a-zA-Z0-9:+=~@,/_^\$\\\*]+)}, '\1{empty}`\2`{empty}') .gsub(/(<[-a-zA-Z0-9.]+>)/, '__\\1__') .gsub(']', ']{empty}') end @@ -71,8 +71,9 @@ module Git # unhandled math; pass source to alt and required mathphrase element; dblatex will process alt as LaTeX math %(<inlineequation><alt><![CDATA[#{equation = node.text}]]></alt><mathphrase><![CDATA[#{equation}]]></mathphrase></inlineequation>) elsif type == :monospaced - node.text.gsub(/(\.\.\.?)([^\]$.])/, '<literal>\1</literal>\2') - .gsub(%r{([\[\s|()>.]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,/_^\$]+\.{0,2})+)}, '\1<literal>\2</literal>') + node.text.gsub(/(\.\.\.?)([^\]$\.])/, '<literal>\1</literal>\2') + .gsub(/^\.\.\.?$/, '<literal>\0</literal>') + .gsub(%r{([\[\s|()>.]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@/_^\$\\\*]+\.{0,2})+|,)}, '\1<literal>\2</literal>') .gsub(/(<[-a-zA-Z0-9.]+>)/, '<emphasis>\1</emphasis>') else open, close, supports_phrase = QUOTE_TAGS[type] @@ -100,7 +101,8 @@ module Git def convert_inline_quoted node if node.type == :monospaced node.text.gsub(/(\.\.\.?)([^\]$.])/, '<code>\1</code>\2') - .gsub(%r{([\[\s|()>.]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,/_^\$]+\.{0,2})+)}, '\1<code>\2</code>') + .gsub(/^\.\.\.?$/, '<code>\0</code>') + .gsub(%r{([\[\s|()>.]|^|\]|>)(\.?([-a-zA-Z0-9:+=~@,/_^\$\\\*]+\.{0,2})+)}, '\1<code>\2</code>') .gsub(/(<[-a-zA-Z0-9.]+>)/, '<em>\1</em>') else diff --git a/Documentation/blame-options.adoc b/Documentation/blame-options.adoc index aa77406d4e..19ea187238 100644 --- a/Documentation/blame-options.adoc +++ b/Documentation/blame-options.adoc @@ -125,7 +125,8 @@ take effect. another commit will be marked with a `?` in the blame output. If the `blame.markUnblamableLines` config option is set, then those lines touched by an ignored commit that we could not attribute to another revision are - marked with a '*'. + marked with a '*'. In the porcelain modes, we print 'ignored' and + 'unblamable' on a newline respectively. --ignore-revs-file <file>:: Ignore revisions listed in `file`, which must be in the same format as an diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc index 8f6d8e7754..9fde1ab63a 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc @@ -512,6 +512,11 @@ centrally configure your Git hooks instead of configuring them on a per-repository basis, or as a more flexible and centralized alternative to having an `init.templateDir` where you've changed default hooks. ++ +You can also disable all hooks entirely by setting `core.hooksPath` +to `/dev/null`. This is usually only advisable for expert users and +on a per-command basis using configuration parameters of the form +`git -c core.hooksPath=/dev/null ...`. core.editor:: Commands such as `commit` and `tag` that let you edit diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc index 72a9d6cf81..41536162a7 100644 --- a/Documentation/config/maintenance.adoc +++ b/Documentation/config/maintenance.adoc @@ -61,6 +61,11 @@ maintenance.loose-objects.auto:: loose objects is at least the value of `maintenance.loose-objects.auto`. The default value is 100. +maintenance.loose-objects.batchSize:: + This integer config option controls the maximum number of loose objects + written into a packfile during the `loose-objects` task. The default is + fifty thousand. Use value `0` to indicate no limit. + maintenance.incremental-repack.auto:: This integer config option controls how often the `incremental-repack` task should be run as part of `git maintenance run --auto`. If zero, @@ -69,3 +74,12 @@ maintenance.incremental-repack.auto:: Otherwise, a positive value implies the command should run when the number of pack-files not in the multi-pack-index is at least the value of `maintenance.incremental-repack.auto`. The default value is 10. + +maintenance.reflog-expire.auto:: + This integer config option controls how often the `reflog-expire` task + should be run as part of `git maintenance run --auto`. If zero, then + the `reflog-expire` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + expired reflog entries in the "HEAD" reflog is at least the value of + `maintenance.loose-objects.auto`. The default value is 100. diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc index 25fe219d10..91e46f66f5 100644 --- a/Documentation/config/remote.adoc +++ b/Documentation/config/remote.adoc @@ -108,7 +108,8 @@ the values inherited from a lower priority configuration files (e.g. `$HOME/.gitconfig`). remote.<name>.followRemoteHEAD:: - How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`. + How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD` + when fetching using the configured refspecs of a remote. The default value is "create", which will create `remotes/<name>/HEAD` if it exists on the remote, but not locally; this will not touch an already existing local reference. Setting it to "warn" will print diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc index f75ed44790..e438d28625 100644 --- a/Documentation/git-blame.adoc +++ b/Documentation/git-blame.adoc @@ -135,10 +135,11 @@ header elements later. The porcelain format generally suppresses commit information that has already been seen. For example, two lines that are blamed to the same commit will both be shown, but the details for that commit will be shown -only once. This is more efficient, but may require more state be kept by -the reader. The `--line-porcelain` option can be used to output full -commit information for each line, allowing simpler (but less efficient) -usage like: +only once. Information which is specific to individual lines will not be +grouped together, like revs to be marked 'ignored' or 'unblamable'. This +is more efficient, but may require more state be kept by the reader. The +`--line-porcelain` option can be used to output full commit information +for each line, allowing simpler (but less efficient) usage like: # count the number of lines attributed to each author git blame --line-porcelain file | diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index 30359f5dbd..fc4b92f104 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -81,6 +81,25 @@ OPTIONS end-of-line conversion, etc). In this case, `<object>` has to be of the form `<tree-ish>:<path>`, or `:<path>`. +--filter=<filter-spec>:: +--no-filter:: + Omit objects from the list of printed objects. This can only be used in + combination with one of the batched modes. Excluded objects that have + been explicitly requested via any of the batch modes that read objects + via standard input (`--batch`, `--batch-check`) will be reported as + "filtered". Excluded objects in `--batch-all-objects` mode will not be + printed at all. The '<filter-spec>' may be one of the following: ++ +The form '--filter=blob:none' omits all blobs. ++ +The form '--filter=blob:limit=<n>[kmg]' omits blobs of size at least n +bytes or units. n may be zero. The suffixes k, m, and g can be used to name +units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same as +'blob:limit=1024'. ++ +The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects which +are not of the requested type. + --path=<path>:: For use with `--textconv` or `--filters`, to allow specifying an object name and a path separately, e.g. when it is difficult to figure out @@ -340,6 +359,13 @@ the repository, then `cat-file` will ignore any custom format and print: <object> SP missing LF ------------ +If a name is specified on stdin that is filtered out via `--filter=`, +then `cat-file` will ignore any custom format and print: + +------------ +<object> SP excluded LF +------------ + If a name is specified that might refer to more than one object (an ambiguous short sha), then `cat-file` will ignore any custom format and print: ------------ diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc index 0450d74aff..3a1e2a69b6 100644 --- a/Documentation/git-maintenance.adoc +++ b/Documentation/git-maintenance.adoc @@ -126,13 +126,17 @@ loose-objects:: objects that already exist in a pack-file; concurrent Git processes will examine the pack-file for the object data instead of the loose object. Second, it creates a new pack-file (starting with "loose-") - containing a batch of loose objects. The batch size is limited to 50 - thousand objects to prevent the job from taking too long on a - repository with many loose objects. The `gc` task writes unreachable - objects as loose objects to be cleaned up by a later step only if - they are not re-added to a pack-file; for this reason it is not - advisable to enable both the `loose-objects` and `gc` tasks at the - same time. + containing a batch of loose objects. ++ +The batch size defaults to fifty thousand objects to prevent the job from +taking too long on a repository with many loose objects. Use the +`maintenance.loose-objects.batchSize` config option to adjust this size, +including a value of `0` to remove the limit. ++ +The `gc` task writes unreachable objects as loose objects to be cleaned up +by a later step only if they are not re-added to a pack-file; for this +reason it is not advisable to enable both the `loose-objects` and `gc` +tasks at the same time. incremental-repack:: The `incremental-repack` job repacks the object directory @@ -158,6 +162,10 @@ pack-refs:: need to iterate across many references. See linkgit:git-pack-refs[1] for more information. +reflog-expire:: + The `reflog-expire` task deletes any entries in the reflog older than the + expiry threshold. See linkgit:git-reflog[1] for more information. + OPTIONS ------- --auto:: diff --git a/Documentation/git-mv.adoc b/Documentation/git-mv.adoc index dc1bf61534..f707e998f7 100644 --- a/Documentation/git-mv.adoc +++ b/Documentation/git-mv.adoc @@ -8,19 +8,18 @@ git-mv - Move or rename a file, a directory, or a symlink SYNOPSIS -------- -[verse] -'git mv' [<options>] <source>... <destination> + +[synopsis] +git mv [-v] [-f] [-n] [-k] <source> <destination> +git mv [-v] [-f] [-n] [-k] <source>... <destination-directory> DESCRIPTION ----------- Move or rename a file, directory, or symlink. - git mv [-v] [-f] [-n] [-k] <source> <destination> - git mv [-v] [-f] [-n] [-k] <source> ... <destination-directory> - -In the first form, it renames <source>, which must exist and be either -a file, symlink or directory, to <destination>. -In the second form, the last argument has to be an existing +In the first form, it renames _<source>_, which must exist and be either +a file, symlink or directory, to _<destination>_. +In the second form, _<destination-directory>_ has to be an existing directory; the given sources will be moved into this directory. The index is updated after successful completion, but the change must still be @@ -28,20 +27,20 @@ committed. OPTIONS ------- --f:: ---force:: +`-f`:: +`--force`:: Force renaming or moving of a file even if the <destination> exists. --k:: +`-k`:: Skip move or rename actions which would lead to an error condition. An error happens when a source is neither existing nor controlled by Git, or when it would overwrite an existing file unless `-f` is given. --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Do nothing; only show what would happen --v:: ---verbose:: +`-v`:: +`--verbose`:: Report the names of files as they are moved. SUBMODULES @@ -49,8 +48,8 @@ SUBMODULES Moving a submodule using a gitfile (which means they were cloned with a Git version 1.7.8 or newer) will update the gitfile and core.worktree setting to make the submodule work in the new location. -It also will attempt to update the submodule.<name>.path setting in -the linkgit:gitmodules[5] file and stage that file (unless -n is used). +It also will attempt to update the `submodule.<name>.path` setting in +the linkgit:gitmodules[5] file and stage that file (unless `-n` is used). BUGS ---- diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc index a929c52982..b55c060569 100644 --- a/Documentation/git-reflog.adoc +++ b/Documentation/git-reflog.adoc @@ -16,6 +16,7 @@ SYNOPSIS [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] 'git reflog delete' [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <ref>@{<specifier>}... +'git reflog drop' [--all [--single-worktree] | <refs>...] 'git reflog exists' <ref> DESCRIPTION @@ -48,10 +49,14 @@ and not reachable from the current tip, are removed from the reflog. This is typically not used directly by end users -- instead, see linkgit:git-gc[1]. -The "delete" subcommand deletes single entries from the reflog. Its -argument must be an _exact_ entry (e.g. "`git reflog delete -master@{2}`"). This subcommand is also typically not used directly by -end users. +The "delete" subcommand deletes single entries from the reflog, but +not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git +reflog delete master@{2}`"). This subcommand is also typically not used +directly by end users. + +The "drop" subcommand completely removes the reflog for the specified +references. This is in contrast to "expire" and "delete", both of which +can be used to delete reflog entries, but not the reflog itself. The "exists" subcommand checks whether a ref has a reflog. It exits with zero status if the reflog exists, and non-zero status if it does @@ -132,6 +137,16 @@ Options for `delete` `--dry-run`, and `--verbose`, with the same meanings as when they are used with `expire`. +Options for `drop` +~~~~~~~~~~~~~~~~~~~~ + +--all:: + Drop the reflogs of all references from all worktrees. + +--single-worktree:: + By default when `--all` is specified, reflogs from all working + trees are dropped. This option limits the processing to reflogs + from the current working tree only. GIT --- diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc index 79ad5643ee..53ab88c545 100644 --- a/Documentation/git-reset.adoc +++ b/Documentation/git-reset.adoc @@ -7,23 +7,23 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- -[verse] -'git reset' [-q] [<tree-ish>] [--] <pathspec>... -'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] -'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...] -'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>] +[synopsis] +git reset [-q] [<tree-ish>] [--] <pathspec>... +git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] +git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...] +git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION ----------- -In the first three forms, copy entries from `<tree-ish>` to the index. -In the last form, set the current branch head (`HEAD`) to `<commit>`, +In the first three forms, copy entries from _<tree-ish>_ to the index. +In the last form, set the current branch head (`HEAD`) to _<commit>_, optionally modifying index and working tree to match. -The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms. +The _<tree-ish>_/_<commit>_ defaults to `HEAD` in all forms. -'git reset' [-q] [<tree-ish>] [--] <pathspec>...:: -'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]:: +`git reset [-q] [<tree-ish>] [--] <pathspec>...`:: +`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`:: These forms reset the index entries for all paths that match the - `<pathspec>` to their state at `<tree-ish>`. (It does not affect + _<pathspec>_ to their state at _<tree-ish>_. (It does not affect the working tree or the current branch.) + This means that `git reset <pathspec>` is the opposite of `git add @@ -37,30 +37,30 @@ and specifying a commit with `--source`, you can copy the contents of a path out of a commit to the index and to the working tree in one go. -'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]:: +`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`:: Interactively select hunks in the difference between the index - and `<tree-ish>` (defaults to `HEAD`). The chosen hunks are applied + and _<tree-ish>_ (defaults to `HEAD`). The chosen hunks are applied in reverse to the index. + This means that `git reset -p` is the opposite of `git add -p`, i.e. -you can use it to selectively reset hunks. See the ``Interactive Mode'' +you can use it to selectively reset hunks. See the "Interactive Mode" section of linkgit:git-add[1] to learn how to operate the `--patch` mode. -'git reset' [<mode>] [<commit>]:: - This form resets the current branch head to `<commit>` and - possibly updates the index (resetting it to the tree of `<commit>`) and - the working tree depending on `<mode>`. Before the operation, `ORIG_HEAD` - is set to the tip of the current branch. If `<mode>` is omitted, - defaults to `--mixed`. The `<mode>` must be one of the following: +`git reset [<mode>] [<commit>]`:: + This form resets the current branch head to _<commit>_ and + possibly updates the index (resetting it to the tree of _<commit>_) and + the working tree depending on _<mode>_. Before the operation, `ORIG_HEAD` + is set to the tip of the current branch. If _<mode>_ is omitted, + defaults to `--mixed`. The _<mode>_ must be one of the following: + -- ---soft:: +`--soft`:: Does not touch the index file or the working tree at all (but - resets the head to `<commit>`, just like all modes do). This leaves + resets the head to _<commit>_, just like all modes do). This leaves all your changed files "Changes to be committed", as `git status` would put it. ---mixed:: +`--mixed`:: Resets the index but not the working tree (i.e., the changed files are preserved but not marked for commit) and reports what has not been updated. This is the default action. @@ -68,33 +68,33 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. If `-N` is specified, removed paths are marked as intent-to-add (see linkgit:git-add[1]). ---hard:: +`--hard`:: Resets the index and working tree. Any changes to tracked files in the - working tree since `<commit>` are discarded. Any untracked files or + working tree since _<commit>_ are discarded. Any untracked files or directories in the way of writing any tracked files are simply deleted. ---merge:: +`--merge`:: Resets the index and updates the files in the working tree that are - different between `<commit>` and `HEAD`, but keeps those which are + different between _<commit>_ and `HEAD`, but keeps those which are different between the index and working tree (i.e. which have changes which have not been added). - If a file that is different between `<commit>` and the index has + If a file that is different between _<commit>_ and the index has unstaged changes, reset is aborted. + In other words, `--merge` does something like a `git read-tree -u -m <commit>`, but carries forward unmerged index entries. ---keep:: +`--keep`:: Resets index entries and updates files in the working tree that are - different between `<commit>` and `HEAD`. - If a file that is different between `<commit>` and `HEAD` has local + different between _<commit>_ and `HEAD`. + If a file that is different between _<commit>_ and `HEAD` has local changes, reset is aborted. ---[no-]recurse-submodules:: - When the working tree is updated, using --recurse-submodules will +`--[no-]recurse-submodules`:: + When the working tree is updated, using `--recurse-submodules` will also recursively reset the working tree of all active submodules according to the commit recorded in the superproject, also setting - the submodules' HEAD to be detached at that commit. + the submodules' `HEAD` to be detached at that commit. -- See "Reset, restore and revert" in linkgit:git[1] for the differences @@ -104,31 +104,31 @@ between the three commands. OPTIONS ------- --q:: ---quiet:: +`-q`:: +`--quiet`:: Be quiet, only report errors. ---refresh:: ---no-refresh:: +`--refresh`:: +`--no-refresh`:: Refresh the index after a mixed reset. Enabled by default. ---pathspec-from-file=<file>:: - Pathspec is passed in `<file>` instead of commandline args. If - `<file>` is exactly `-` then standard input is used. Pathspec - elements are separated by LF or CR/LF. Pathspec elements can be +`--pathspec-from-file=<file>`:: + Pathspec is passed in _<file>_ instead of commandline args. If + _<file>_ is exactly `-` then standard input is used. Pathspec + elements are separated by _LF_ or _CR_/_LF_. Pathspec elements can be quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). See also `--pathspec-file-nul` and global `--literal-pathspecs`. ---pathspec-file-nul:: +`--pathspec-file-nul`:: Only meaningful with `--pathspec-from-file`. Pathspec elements are - separated with NUL character and all other characters are taken + separated with _NUL_ character and all other characters are taken literally (including newlines and quotes). -\--:: +`--`:: Do not interpret any more arguments as options. -<pathspec>...:: +`<pathspec>...`:: Limits the paths affected by the operation. + For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. @@ -348,7 +348,7 @@ $ git commit ... <8> ------------ + <1> First, reset the history back one commit so that we remove the original - commit, but leave the working tree with all the changes. The -N ensures + commit, but leave the working tree with all the changes. The `-N` ensures that any new files added with `HEAD` are still marked so that `git add -p` will find them. <2> Next, we interactively select diff hunks to add using the `git add -p` @@ -458,7 +458,7 @@ working index HEAD target working index HEAD --keep B C C .... -`reset --merge` is meant to be used when resetting out of a conflicted +`git reset --merge` is meant to be used when resetting out of a conflicted merge. Any mergy operation guarantees that the working tree file that is involved in the merge does not have a local change with respect to the index before it starts, and that it writes the result out to the working tree. So if @@ -467,7 +467,7 @@ between the index and the working tree, then it means that we are not resetting out from a state that a mergy operation left after failing with a conflict. That is why we disallow `--merge` option in this case. -`reset --keep` is meant to be used when removing some of the last +`git reset --keep` is meant to be used when removing some of the last commits in the current branch while keeping changes in the working tree. If there could be conflicts between the changes in the commit we want to remove and the changes in the working tree we want to keep, diff --git a/Documentation/git-rm.adoc b/Documentation/git-rm.adoc index 363a26934f..b5ead86796 100644 --- a/Documentation/git-rm.adoc +++ b/Documentation/git-rm.adoc @@ -7,10 +7,10 @@ git-rm - Remove files from the working tree and from the index SYNOPSIS -------- -[verse] -'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] - [--quiet] [--pathspec-from-file=<file> [--pathspec-file-nul]] - [--] [<pathspec>...] +[synopsis] +git rm [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] + [--quiet] [--pathspec-from-file=<file> [--pathspec-file-nul]] + [--] [<pathspec>...] DESCRIPTION ----------- @@ -30,7 +30,7 @@ sparse-checkouts are in use (see linkgit:git-sparse-checkout[1]), OPTIONS ------- -<pathspec>...:: +`<pathspec>...`:: Files to remove. A leading directory name (e.g. `dir` to remove `dir/file1` and `dir/file2`) can be given to remove all files in the directory, and recursively all sub-directories, but this @@ -43,57 +43,57 @@ directories `d` and `d2`, there is a difference between using `git rm 'd*'` and `git rm 'd/*'`, as the former will also remove all of directory `d2`. + -For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. +For more details, see the _<pathspec>_ entry in linkgit:gitglossary[7]. --f:: ---force:: +`-f`:: +`--force`:: Override the up-to-date check. --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Don't actually remove any file(s). Instead, just show if they exist in the index and would otherwise be removed by the command. --r:: +`-r`:: Allow recursive removal when a leading directory name is given. -\--:: +`--`:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken for command-line options). ---cached:: +`--cached`:: Use this option to unstage and remove paths only from the index. Working tree files, whether modified or not, will be left alone. ---ignore-unmatch:: +`--ignore-unmatch`:: Exit with a zero status even if no files matched. ---sparse:: +`--sparse`:: Allow updating index entries outside of the sparse-checkout cone. Normally, `git rm` refuses to update index entries whose paths do not fit within the sparse-checkout cone. See linkgit:git-sparse-checkout[1] for more. --q:: ---quiet:: +`-q`:: +`--quiet`:: `git rm` normally outputs one line (in the form of an `rm` command) for each file removed. This option suppresses that output. ---pathspec-from-file=<file>:: - Pathspec is passed in `<file>` instead of commandline args. If - `<file>` is exactly `-` then standard input is used. Pathspec - elements are separated by LF or CR/LF. Pathspec elements can be +`--pathspec-from-file=<file>`:: + Pathspec is passed in _<file>_ instead of args. If + _<file>_ is exactly `-` then standard input is used. Pathspec + elements are separated by _LF_ or _CR_/_LF_. Pathspec elements can be quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). See also `--pathspec-file-nul` and global `--literal-pathspecs`. ---pathspec-file-nul:: +`--pathspec-file-nul`:: Only meaningful with `--pathspec-from-file`. Pathspec elements are - separated with NUL character and all other characters are taken + separated with _NUL_ character and all other characters are taken literally (including newlines and quotes). @@ -153,15 +153,15 @@ SUBMODULES ---------- Only submodules using a gitfile (which means they were cloned with a Git version 1.7.8 or newer) will be removed from the work -tree, as their repository lives inside the .git directory of the +tree, as their repository lives inside the `.git` directory of the superproject. If a submodule (or one of those nested inside it) -still uses a .git directory, `git rm` will move the submodules +still uses a `.git` directory, `git rm` moves the submodules git directory into the superprojects git directory to protect -the submodule's history. If it exists the submodule.<name> section +the submodule's history. If it exists the `submodule.<name>` section in the linkgit:gitmodules[5] file will also be removed and that file -will be staged (unless --cached or -n are used). +will be staged (unless `--cached` or `-n` are used). -A submodule is considered up to date when the HEAD is the same as +A submodule is considered up to date when the `HEAD` is the same as recorded in the index, no tracked files are modified and no untracked files that aren't ignored are present in the submodule's work tree. Ignored files are deemed expendable and won't stop a submodule's work diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc index 9e6935d38d..9310ce9768 100644 --- a/Documentation/git-update-ref.adoc +++ b/Documentation/git-update-ref.adoc @@ -7,8 +7,10 @@ git-update-ref - Update the object name stored in a ref safely SYNOPSIS -------- -[verse] -'git update-ref' [-m <reason>] [--no-deref] (-d <ref> [<old-oid>] | [--create-reflog] <ref> <new-oid> [<old-oid>] | --stdin [-z]) +[synopsis] +git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>] +git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>] +git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates] DESCRIPTION ----------- @@ -57,6 +59,14 @@ performs all modifications together. Specify commands of the form: With `--create-reflog`, update-ref will create a reflog for each ref even if one would not ordinarily be created. +With `--batch-updates`, update-ref executes the updates in a batch but allows +individual updates to fail due to invalid or incorrect user input, applying only +the successful updates. However, system-related errors—such as I/O failures or +memory issues—will result in a full failure of all batched updates. Any failed +updates will be reported in the following format: + + rejected SP (<old-oid> | <old-target>) SP (<new-oid> | <new-target>) SP <rejection-reason> LF + Quote fields containing whitespace as if they were strings in C source code; i.e., surrounded by double-quotes and with backslash escapes. Use 40 "0" characters or the empty string to specify a zero value. To diff --git a/Documentation/git-version.adoc b/Documentation/git-version.adoc index 80fa7754a6..9462043a14 100644 --- a/Documentation/git-version.adoc +++ b/Documentation/git-version.adoc @@ -22,6 +22,14 @@ OPTIONS --build-options:: Include additional information about how git was built for diagnostic purposes. ++ +The libraries used to implement the SHA-1 and SHA-256 algorithms are displayed +in the form `SHA-1: <option>` and `SHA-256: <option>`. Note that the SHA-1 +options `SHA1_APPLE`, `SHA1_OPENSSL`, and `SHA1_BLK` do not use a collision +detection algorithm and thus may be vulnerable to known SHA-1 collision +attacks. When a faster SHA-1 implementation without collision detection is used +for only non-cryptographic purposes, the algorithm is displayed in the form +`non-collision-detecting-SHA-1: <option>`. GIT --- diff --git a/Documentation/howto/recover-corrupted-object-harder.adoc b/Documentation/howto/recover-corrupted-object-harder.adoc index 5efb4fe81f..86a1ba75cf 100644 --- a/Documentation/howto/recover-corrupted-object-harder.adoc +++ b/Documentation/howto/recover-corrupted-object-harder.adoc @@ -125,7 +125,7 @@ static int try_zlib(unsigned char *buf, int len) { /* make this absurdly large so we don't have to loop */ static unsigned char out[1024*1024]; - z_stream z; + struct z_stream_s z; int ret; memset(&z, 0, sizeof(z)); @@ -278,7 +278,7 @@ int main(int argc, char **argv) static unsigned char buf[25 * 1024 * 1024]; static unsigned char out[25 * 1024 * 1024]; int len; - z_stream z; + struct z_stream_s z; int ret; len = read(0, buf, sizeof(buf)); diff --git a/Documentation/merge-strategies.adoc b/Documentation/merge-strategies.adoc index 59f5ae36cc..9e034f447e 100644 --- a/Documentation/merge-strategies.adoc +++ b/Documentation/merge-strategies.adoc @@ -87,44 +87,33 @@ no-renames;; configuration variable. See also linkgit:git-diff[1] `--no-renames`. -subtree[=<path>];; - This option is a more advanced form of 'subtree' strategy, where - the strategy makes a guess on how two trees must be shifted to - match with each other when merging. Instead, the specified path - is prefixed (or stripped from the beginning) to make the shape of - two trees to match. - -recursive:: - This can only resolve two heads using a 3-way merge - algorithm. When there is more than one common - ancestor that can be used for 3-way merge, it creates a - merged tree of the common ancestors and uses that as - the reference tree for the 3-way merge. This has been - reported to result in fewer merge conflicts without - causing mismerges by tests done on actual merge commits - taken from Linux 2.6 kernel development history. - Additionally this can detect and handle merges involving - renames. It does not make use of detected copies. This was - the default strategy for resolving two heads from Git v0.99.9k - until v2.33.0. -+ -For a path that is a submodule, the same caution as 'ort' applies to this -strategy. -+ -The 'recursive' strategy takes the same options as 'ort'. However, -there are two additional options that 'ort' ignores (not documented -above) that are potentially useful with the 'recursive' strategy: +histogram;; + Deprecated synonym for `diff-algorithm=histogram`. patience;; Deprecated synonym for `diff-algorithm=patience`. -diff-algorithm=[patience|minimal|histogram|myers];; +diff-algorithm=[histogram|minimal|myers|patience];; Use a different diff algorithm while merging, which can help avoid mismerges that occur due to unimportant matching lines (such as braces from distinct functions). See also linkgit:git-diff[1] `--diff-algorithm`. Note that `ort` - specifically uses `diff-algorithm=histogram`, while `recursive` - defaults to the `diff.algorithm` config setting. + defaults to `diff-algorithm=histogram`, while regular diffs + currently default to the `diff.algorithm` config setting. + +subtree[=<path>];; + This option is a more advanced form of 'subtree' strategy, where + the strategy makes a guess on how two trees must be shifted to + match with each other when merging. Instead, the specified path + is prefixed (or stripped from the beginning) to make the shape of + two trees to match. + +recursive:: + This is now a synonym for `ort`. It was an alternative + implementation until v2.49.0, but was redirected to mean `ort` + in v2.50.0. The previous recursive strategy was the default + strategy for resolving two heads from Git v0.99.9k until + v2.33.0. resolve:: This can only resolve two heads (i.e. the current branch @@ -145,7 +134,7 @@ ours:: ignoring all changes from all other branches. It is meant to be used to supersede old development history of side branches. Note that this is different from the -Xours option to - the 'recursive' merge strategy. + the 'ort' merge strategy. subtree:: This is a modified `ort` strategy. When merging trees A and diff --git a/Documentation/meson.build b/Documentation/meson.build index b731c76e9e..1433acfd31 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -215,9 +215,9 @@ endif docs_backend = get_option('docs_backend') if docs_backend == 'auto' - if find_program('asciidoc', dirs: program_path, required: false).found() + if find_program('asciidoc', dirs: program_path, native: true, required: false).found() docs_backend = 'asciidoc' - elif find_program('asciidoctor', dirs: program_path, required: false).found() + elif find_program('asciidoctor', dirs: program_path, native: true, required: false).found() docs_backend = 'asciidoctor' else error('Neither asciidoc nor asciidoctor were found.') @@ -225,7 +225,7 @@ if docs_backend == 'auto' endif if docs_backend == 'asciidoc' - asciidoc = find_program('asciidoc', dirs: program_path) + asciidoc = find_program('asciidoc', dirs: program_path, native: true) asciidoc_html = 'xhtml11' asciidoc_docbook = 'docbook' xmlto_extra = [ ] @@ -250,11 +250,21 @@ if docs_backend == 'asciidoc' '--attribute=build_dir=' + meson.current_build_dir(), ] + pager_opt = get_option('default_pager') + if pager_opt != '' and pager_opt != 'less' + asciidoc_common_options += '-agit-default-pager=' + pager_opt + endif + + editor_opt = get_option('default_editor') + if editor_opt != '' and editor_opt != 'vi' + asciidoc_common_options += '-agit-default-editor=' + editor_opt + endif + documentation_deps = [ asciidoc_conf, ] elif docs_backend == 'asciidoctor' - asciidoctor = find_program('asciidoctor', dirs: program_path) + asciidoctor = find_program('asciidoctor', dirs: program_path, native: true) asciidoc_html = 'xhtml5' asciidoc_docbook = 'docbook5' xmlto_extra = [ @@ -287,6 +297,16 @@ elif docs_backend == 'asciidoctor' '--require', 'asciidoctor-extensions', ] + pager_opt = get_option('default_pager') + if pager_opt != '' and pager_opt != 'less' + asciidoc_common_options += '-agit-default-pager=' + pager_opt + endif + + editor_opt = get_option('default_editor') + if editor_opt != '' and editor_opt != 'vi' + asciidoc_common_options += '-agit-default-editor=' + editor_opt + endif + documentation_deps = [ asciidoctor_extensions, ] @@ -296,7 +316,7 @@ if get_option('breaking_changes') asciidoc_common_options += ['--attribute', 'with-breaking-changes'] endif -xmlto = find_program('xmlto', dirs: program_path) +xmlto = find_program('xmlto', dirs: program_path, native: true) cmd_lists = [ 'cmds-ancillaryinterrogators.adoc', @@ -417,7 +437,7 @@ if get_option('docs').contains('html') pointing_to: 'git.html', ) - xsltproc = find_program('xsltproc', dirs: program_path) + xsltproc = find_program('xsltproc', dirs: program_path, native: true) user_manual_xml = custom_target( command: asciidoc_common_options + [ diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 1c403c1e40..d38875efda 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -361,6 +361,30 @@ ifdef::git-rev-list[] --progress=<header>:: Show progress reports on stderr as objects are considered. The `<header>` text will be printed with each progress update. + +-z:: + Instead of being newline-delimited, each outputted object and its + accompanying metadata is delimited using NUL bytes. Output is printed + in the following form: ++ +----------------------------------------------------------------------- +<OID> NUL [<token>=<value> NUL]... +----------------------------------------------------------------------- ++ +Additional object metadata, such as object paths or boundary objects, is +printed using the `<token>=<value>` form. Token values are printed as-is +without any encoding/truncation. An OID entry never contains a '=' character +and thus is used to signal the start of a new object record. Examples: ++ +----------------------------------------------------------------------- +<OID> NUL +<OID> NUL path=<path> NUL +<OID> NUL boundary=yes NUL +<OID> NUL missing=yes NUL [<token>=<value> NUL]... +----------------------------------------------------------------------- ++ +This mode is only compatible with the `--objects`, `--boundary`, and +`--missing` output options. endif::git-rev-list[] History Simplification diff --git a/Documentation/technical/api-parse-options.adoc b/Documentation/technical/api-parse-options.adoc index 61fa6ee167..880eb94642 100644 --- a/Documentation/technical/api-parse-options.adoc +++ b/Documentation/technical/api-parse-options.adoc @@ -211,11 +211,13 @@ There are some macros to easily define options: Use of `--no-option` will clear the list of preceding values. `OPT_INTEGER(short, long, &int_var, description)`:: - Introduce an option with integer argument. - The integer is put into `int_var`. + Introduce an option with integer argument. The argument must be a + integer and may include a suffix of 'k', 'm' or 'g' to + scale the provided value by 1024, 1024^2 or 1024^3 respectively. + The scaled value is put into `int_var`. -`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`:: - Introduce an option with a size argument. The argument must be a +`OPT_UNSIGNED(short, long, &unsigned_long_var, description)`:: + Introduce an option with an unsigned integer argument. The argument must be a non-negative integer and may include a suffix of 'k', 'm' or 'g' to scale the provided value by 1024, 1024^2 or 1024^3 respectively. The scaled value is put into `unsigned_long_var`. diff --git a/Documentation/technical/sparse-checkout.adoc b/Documentation/technical/sparse-checkout.adoc index d968659354..dc2e763bbe 100644 --- a/Documentation/technical/sparse-checkout.adoc +++ b/Documentation/technical/sparse-checkout.adoc @@ -356,8 +356,6 @@ understanding these differences can be beneficial. The behavior for these commands somewhat depends upon the merge strategy being used: * `ort` behaves as described above - * `recursive` tries to not vivify files unnecessarily, but does sometimes - vivify files without conflicts. * `octopus` and `resolve` will always vivify any file changed in the merge relative to the first parent, which is rather suboptimal. @@ -340,9 +340,6 @@ include shared.mak # # Define HAVE_SYNC_FILE_RANGE if your platform has sync_file_range. # -# Define NEEDS_LIBRT if your platform requires linking with librt (glibc version -# before 2.17) for clock_gettime and CLOCK_MONOTONIC. -# # Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function. # # Define HAVE_GETDELIM if your system has the getdelim() function. @@ -994,6 +991,7 @@ LIB_OBJS += common-exit.o LIB_OBJS += common-init.o LIB_OBJS += compat/nonblock.o LIB_OBJS += compat/obstack.o +LIB_OBJS += compat/open.o LIB_OBJS += compat/terminal.o LIB_OBJS += compiler-tricks/not-constant.o LIB_OBJS += config.o @@ -1042,6 +1040,7 @@ LIB_OBJS += gpg-interface.o LIB_OBJS += graph.o LIB_OBJS += grep.o LIB_OBJS += hash-lookup.o +LIB_OBJS += hash.o LIB_OBJS += hashmap.o LIB_OBJS += help.o LIB_OBJS += hex.o @@ -1069,7 +1068,6 @@ LIB_OBJS += merge-blobs.o LIB_OBJS += merge-ll.o LIB_OBJS += merge-ort.o LIB_OBJS += merge-ort-wrappers.o -LIB_OBJS += merge-recursive.o LIB_OBJS += merge.o LIB_OBJS += midx.o LIB_OBJS += midx-write.o @@ -1084,6 +1082,7 @@ LIB_OBJS += notes.o LIB_OBJS += object-file-convert.o LIB_OBJS += object-file.o LIB_OBJS += object-name.o +LIB_OBJS += object-store.o LIB_OBJS += object.o LIB_OBJS += oid-array.o LIB_OBJS += oidmap.o @@ -1366,6 +1365,8 @@ CLAR_TEST_SUITES += u-reftable-tree CLAR_TEST_SUITES += u-strbuf CLAR_TEST_SUITES += u-strcmp-offset CLAR_TEST_SUITES += u-strvec +CLAR_TEST_SUITES += u-trailer +CLAR_TEST_SUITES += u-urlmatch-normalization CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES)) CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o @@ -1376,12 +1377,10 @@ UNIT_TEST_PROGRAMS += t-reftable-basics UNIT_TEST_PROGRAMS += t-reftable-block UNIT_TEST_PROGRAMS += t-reftable-merged UNIT_TEST_PROGRAMS += t-reftable-pq -UNIT_TEST_PROGRAMS += t-reftable-reader UNIT_TEST_PROGRAMS += t-reftable-readwrite UNIT_TEST_PROGRAMS += t-reftable-record UNIT_TEST_PROGRAMS += t-reftable-stack -UNIT_TEST_PROGRAMS += t-trailer -UNIT_TEST_PROGRAMS += t-urlmatch-normalization +UNIT_TEST_PROGRAMS += t-reftable-table UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o @@ -1411,7 +1410,7 @@ ARFLAGS = rcs PTHREAD_CFLAGS = # For the 'sparse' target -SPARSE_FLAGS ?= -std=gnu99 +SPARSE_FLAGS ?= -std=gnu99 -D__STDC_NO_VLA__ SP_EXTRA_FLAGS = # For informing GIT-BUILD-OPTIONS of the SANITIZE=leak,address targets @@ -1811,7 +1810,6 @@ ifdef FREAD_READS_DIRECTORIES endif ifdef OPEN_RETURNS_EINTR COMPAT_CFLAGS += -DOPEN_RETURNS_EINTR - COMPAT_OBJS += compat/open.o endif ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD @@ -2173,18 +2171,14 @@ ifdef HAVE_SYNC_FILE_RANGE BASIC_CFLAGS += -DHAVE_SYNC_FILE_RANGE endif -ifdef NEEDS_LIBRT - EXTLIBS += -lrt +ifdef HAVE_SYSINFO + BASIC_CFLAGS += -DHAVE_SYSINFO endif ifdef HAVE_BSD_SYSCTL BASIC_CFLAGS += -DHAVE_BSD_SYSCTL endif -ifdef HAVE_BSD_KERN_PROC_SYSCTL - BASIC_CFLAGS += -DHAVE_BSD_KERN_PROC_SYSCTL -endif - ifdef HAVE_GETDELIM BASIC_CFLAGS += -DHAVE_GETDELIM endif @@ -2215,25 +2209,33 @@ ifneq ($(findstring openssl,$(CSPRNG_METHOD)),) EXTLIBS += -lcrypto -lssl endif -ifneq ($(PROCFS_EXECUTABLE_PATH),) - procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH)) - BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"' -endif - ifndef HAVE_PLATFORM_PROCINFO COMPAT_OBJS += compat/stub/procinfo.o endif -ifdef HAVE_NS_GET_EXECUTABLE_PATH - BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH -endif +ifdef RUNTIME_PREFIX -ifdef HAVE_ZOS_GET_EXECUTABLE_PATH - BASIC_CFLAGS += -DHAVE_ZOS_GET_EXECUTABLE_PATH -endif + ifdef HAVE_BSD_KERN_PROC_SYSCTL + BASIC_CFLAGS += -DHAVE_BSD_KERN_PROC_SYSCTL + endif + + ifneq ($(PROCFS_EXECUTABLE_PATH),) + pep_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH)) + BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(pep_SQ)"' + endif + + ifdef HAVE_NS_GET_EXECUTABLE_PATH + BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH + endif + + ifdef HAVE_ZOS_GET_EXECUTABLE_PATH + BASIC_CFLAGS += -DHAVE_ZOS_GET_EXECUTABLE_PATH + endif + + ifdef HAVE_WPGMPTR + BASIC_CFLAGS += -DHAVE_WPGMPTR + endif -ifdef HAVE_WPGMPTR - BASIC_CFLAGS += -DHAVE_WPGMPTR endif ifdef FILENO_IS_A_MACRO @@ -2735,10 +2737,10 @@ REFTABLE_OBJS += reftable/blocksource.o REFTABLE_OBJS += reftable/iter.o REFTABLE_OBJS += reftable/merged.o REFTABLE_OBJS += reftable/pq.o -REFTABLE_OBJS += reftable/reader.o REFTABLE_OBJS += reftable/record.o REFTABLE_OBJS += reftable/stack.o REFTABLE_OBJS += reftable/system.o +REFTABLE_OBJS += reftable/table.o REFTABLE_OBJS += reftable/tree.o REFTABLE_OBJS += reftable/writer.o @@ -51,6 +51,7 @@ static struct { [ADVICE_AM_WORK_DIR] = { "amWorkDir" }, [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" }, [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" }, + [ADVICE_DEFAULT_BRANCH_NAME] = { "defaultBranchName" }, [ADVICE_DETACHED_HEAD] = { "detachedHead" }, [ADVICE_DIVERGING] = { "diverging" }, [ADVICE_FETCH_SET_HEAD_WARN] = { "fetchRemoteHEADWarn" }, @@ -18,6 +18,7 @@ enum advice_type { ADVICE_AM_WORK_DIR, ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME, ADVICE_COMMIT_BEFORE_MERGE, + ADVICE_DEFAULT_BRANCH_NAME, ADVICE_DETACHED_HEAD, ADVICE_DIVERGING, ADVICE_FETCH_SET_HEAD_WARN, @@ -14,7 +14,7 @@ #include "abspath.h" #include "base85.h" #include "config.h" -#include "object-store-ll.h" +#include "object-store.h" #include "delta.h" #include "diff.h" #include "dir.h" @@ -5123,8 +5123,8 @@ int apply_parse_options(int argc, const char **argv, /* Think twice before adding "--nul" synonym to this */ OPT_SET_INT('z', NULL, &state->line_termination, N_("paths are separated with NUL character"), '\0'), - OPT_INTEGER('C', NULL, &state->p_context, - N_("ensure at least <n> lines of context match")), + OPT_UNSIGNED('C', NULL, &state->p_context, + N_("ensure at least <n> lines of context match")), OPT_CALLBACK(0, "whitespace", state, N_("action"), N_("detect new or modified lines that have whitespace errors"), apply_option_parse_whitespace), diff --git a/archive-tar.c b/archive-tar.c index 0edf13fba7..282b48196f 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -11,7 +11,7 @@ #include "hex.h" #include "tar.h" #include "archive.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strbuf.h" #include "streaming.h" #include "run-command.h" diff --git a/archive-zip.c b/archive-zip.c index 9f32730181..405da6f3d8 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -12,7 +12,7 @@ #include "hex.h" #include "streaming.h" #include "utf8.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strbuf.h" #include "userdiff.h" #include "write-or-die.h" @@ -14,7 +14,7 @@ #include "pretty.h" #include "setup.h" #include "refs.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "tree-walk.h" @@ -216,7 +216,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base, /* Stream it? */ if (S_ISREG(mode) && !args->convert && oid_object_info(args->repo, oid, &size) == OBJ_BLOB && - size > big_file_threshold) + size > repo_settings_get_big_file_threshold(the_repository)) return write_entry(args, oid, path.buf, path.len, mode, NULL, size); buffer = object_file_to_archive(args, path.buf, oid, mode, &type, &size); @@ -312,7 +312,7 @@ int write_archive_entries(struct archiver_args *args, struct object_id fake_oid; int i; - oidcpy(&fake_oid, null_oid()); + oidcpy(&fake_oid, null_oid(the_hash_algo)); if (args->baselen > 0 && args->base[args->baselen - 1] == '/') { size_t len = args->baselen; @@ -650,20 +650,37 @@ static int parse_archive_args(int argc, const char **argv, OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")), OPT_STRING(0, "prefix", &base, N_("prefix"), N_("prepend prefix to each pathname in the archive")), - { OPTION_CALLBACK, 0, "add-file", args, N_("file"), - N_("add untracked file to archive"), 0, add_file_cb, - (intptr_t)&base }, - { OPTION_CALLBACK, 0, "add-virtual-file", args, - N_("path:content"), N_("add untracked file to archive"), 0, - add_file_cb, (intptr_t)&base }, + { + .type = OPTION_CALLBACK, + .long_name = "add-file", + .value = args, + .argh = N_("file"), + .help = N_("add untracked file to archive"), + .callback = add_file_cb, + .defval = (intptr_t) &base, + }, + { + .type = OPTION_CALLBACK, + .long_name = "add-virtual-file", + .value = args, + .argh = N_("path:content"), + .help = N_("add untracked file to archive"), + .callback = add_file_cb, + .defval = (intptr_t) &base, + }, OPT_STRING('o', "output", &output, N_("file"), N_("write the archive to this file")), OPT_BOOL(0, "worktree-attributes", &worktree_attributes, N_("read .gitattributes in working directory")), OPT__VERBOSE(&verbose, N_("report archived files on stderr")), - { OPTION_STRING, 0, "mtime", &mtime_option, N_("time"), - N_("set modification time of archive entries"), - PARSE_OPT_NONEG }, + { + .type = OPTION_STRING, + .long_name = "mtime", + .value = &mtime_option, + .argh = N_("time"), + .help = N_("set modification time of archive entries"), + .flags = PARSE_OPT_NONEG, + }, OPT_NUMBER_CALLBACK(&compression_level, N_("set compression level"), number_callback), OPT_GROUP(""), @@ -22,7 +22,7 @@ #include "read-cache-ll.h" #include "refs.h" #include "revision.h" -#include "object-store-ll.h" +#include "object-store.h" #include "setup.h" #include "thread-utils.h" #include "tree-walk.h" @@ -20,7 +20,7 @@ #include "commit-slab.h" #include "commit-reach.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "dir.h" @@ -3,7 +3,7 @@ #include "git-compat-util.h" #include "refs.h" -#include "object-store-ll.h" +#include "object-store.h" #include "cache-tree.h" #include "mergesort.h" #include "commit.h" @@ -255,7 +255,7 @@ static struct commit *fake_working_tree_commit(struct repository *r, switch (st.st_mode & S_IFMT) { case S_IFREG: if (opt->flags.allow_textconv && - textconv_object(r, read_from, mode, null_oid(), 0, &buf_ptr, &buf_len)) + textconv_object(r, read_from, mode, null_oid(the_hash_algo), 0, &buf_ptr, &buf_len)) strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); @@ -277,7 +277,7 @@ static struct commit *fake_working_tree_commit(struct repository *r, convert_to_git(r->index, path, buf.buf, buf.len, &buf, 0); origin->file.ptr = buf.buf; origin->file.size = buf.len; - pretend_object_file(buf.buf, buf.len, OBJ_BLOB, &origin->blob_oid); + pretend_object_file(the_repository, buf.buf, buf.len, OBJ_BLOB, &origin->blob_oid); /* * Read the current index, replace the path entry with @@ -633,7 +633,7 @@ void create_branch(struct repository *r, 0, &err); if (!transaction || ref_transaction_update(transaction, ref.buf, - &oid, forcing ? NULL : null_oid(), + &oid, forcing ? NULL : null_oid(the_hash_algo), NULL, NULL, flags, msg, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); diff --git a/builtin/add.c b/builtin/add.c index 78dfb26577..747511b68b 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -386,8 +386,7 @@ int cmd_add(int argc, char *ps_matched = NULL; struct lock_file lock_file = LOCK_INIT; - if (repo) - repo_config(repo, add_config, NULL); + repo_config(repo, add_config, NULL); argc = parse_options(argc, argv, prefix, builtin_add_options, builtin_add_usage, PARSE_OPT_KEEP_ARGV0); diff --git a/builtin/am.c b/builtin/am.c index 3b61bd4c33..4afb519830 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2400,11 +2400,16 @@ int cmd_am(int argc, OPT_CMDMODE(0, "quit", &resume_mode, N_("abort the patching operation but keep HEAD where it is"), RESUME_QUIT), - { OPTION_CALLBACK, 0, "show-current-patch", &resume_mode, - "(diff|raw)", - N_("show the patch being applied"), - PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, - parse_opt_show_current_patch, RESUME_SHOW_PATCH_RAW }, + { + .type = OPTION_CALLBACK, + .long_name = "show-current-patch", + .value = &resume_mode, + .argh = "(diff|raw)", + .help = N_("show the patch being applied"), + .flags = PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + .callback = parse_opt_show_current_patch, + .defval = RESUME_SHOW_PATCH_RAW, + }, OPT_CMDMODE(0, "retry", &resume_mode, N_("try to apply current patch again"), RESUME_APPLY), @@ -2417,9 +2422,16 @@ int cmd_am(int argc, OPT_BOOL(0, "ignore-date", &state.ignore_date, N_("use current timestamp for author date")), OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate), - { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), - N_("GPG-sign commits"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &state.sign_commit, + .argh = N_("key-id"), + .help = N_("GPG-sign commits"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, OPT_CALLBACK_F(0, "empty", &state.empty_type, "(stop|drop|keep)", N_("how to handle empty patches"), PARSE_OPT_NONEG, am_option_parse_empty), diff --git a/builtin/backfill.c b/builtin/backfill.c index 33e1ea2f84..fa82ad2f6f 100644 --- a/builtin/backfill.c +++ b/builtin/backfill.c @@ -13,7 +13,7 @@ #include "tree.h" #include "tree-walk.h" #include "object.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "oidset.h" #include "promisor-remote.h" @@ -123,8 +123,8 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit .sparse = 0, }; struct option options[] = { - OPT_INTEGER(0, "min-batch-size", &ctx.min_batch_size, - N_("Minimum number of objects to request at a time")), + OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size, + N_("Minimum number of objects to request at a time")), OPT_BOOL(0, "sparse", &ctx.sparse, N_("Restrict the missing objects to the current sparse-checkout")), OPT_END(), diff --git a/builtin/blame.c b/builtin/blame.c index c470654c7e..944952e30e 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -28,7 +28,7 @@ #include "line-log.h" #include "progress.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pager.h" #include "blame.h" #include "refs.h" @@ -36,17 +36,17 @@ #include "tag.h" #include "write-or-die.h" -static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); -static char annotate_usage[] = N_("git annotate [<options>] [<rev-opts>] [<rev>] [--] <file>"); +static const char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); +static const char annotate_usage[] = N_("git annotate [<options>] [<rev-opts>] [<rev>] [--] <file>"); -static const char *blame_opt_usage[] = { +static const char *const blame_opt_usage[] = { blame_usage, "", N_("<rev-opts> are documented in git-rev-list(1)"), NULL }; -static const char *annotate_opt_usage[] = { +static const char *const annotate_opt_usage[] = { annotate_usage, "", N_("<rev-opts> are documented in git-rev-list(1)"), @@ -351,6 +351,19 @@ static void emit_porcelain_details(struct blame_origin *suspect, int repeat) write_filename_info(suspect); } +/* + * Information which needs to be printed per-line goes here. Any + * information which can be clubbed on a commit/file level, should + * be printed via 'emit_one_suspect_detail()'. + */ +static void emit_porcelain_per_line_details(struct blame_entry *ent) +{ + if (mark_unblamable_lines && ent->unblamable) + puts("unblamable"); + if (mark_ignored_lines && ent->ignored) + puts("ignored"); +} + static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { @@ -367,6 +380,7 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, ent->lno + 1, ent->num_lines); emit_porcelain_details(suspect, repeat); + emit_porcelain_per_line_details(ent); cp = blame_nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { @@ -377,6 +391,7 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, ent->lno + 1 + cnt); if (repeat) emit_porcelain_details(suspect, 1); + emit_porcelain_per_line_details(ent); } putchar('\t'); do { @@ -929,7 +944,7 @@ int cmd_blame(int argc, long anchor; long num_lines = 0; const char *str_usage = cmd_is_annotate ? annotate_usage : blame_usage; - const char **opt_usage = cmd_is_annotate ? annotate_opt_usage : blame_opt_usage; + const char *const *opt_usage = cmd_is_annotate ? annotate_opt_usage : blame_opt_usage; setup_default_color_by_age(); git_config(git_blame_config, &output_option); diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 66d64bfd5a..f78c3f2aed 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -4,13 +4,13 @@ #include "editor.h" #include "gettext.h" #include "parse-options.h" +#include "path.h" #include "strbuf.h" #include "help.h" #include "compat/compiler.h" #include "hook.h" #include "hook-list.h" #include "diagnose.h" -#include "object-file.h" #include "setup.h" #include "version.h" @@ -141,7 +141,7 @@ int cmd_bugreport(int argc, } strbuf_addstr(&report_path, ".txt"); - switch (safe_create_leading_directories(report_path.buf)) { + switch (safe_create_leading_directories(the_repository, report_path.buf)) { case SCLD_OK: case SCLD_EXISTS: break; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index b13561cf73..0e3f10a946 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -15,14 +15,16 @@ #include "gettext.h" #include "hex.h" #include "ident.h" +#include "list-objects-filter-options.h" #include "parse-options.h" #include "userdiff.h" #include "streaming.h" #include "oid-array.h" #include "packfile.h" +#include "pack-bitmap.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "promisor-remote.h" #include "mailmap.h" @@ -35,6 +37,7 @@ enum batch_mode { }; struct batch_options { + struct list_objects_filter_options objects_filter; int enabled; int follow_symlinks; enum batch_mode batch_mode; @@ -455,6 +458,16 @@ static void print_default_format(struct strbuf *scratch, struct expand_data *dat (uintmax_t)data->size, opt->output_delim); } +static void report_object_status(struct batch_options *opt, + const char *obj_name, + const struct object_id *oid, + const char *status) +{ + printf("%s %s%c", obj_name ? obj_name : oid_to_hex(oid), + status, opt->output_delim); + fflush(stdout); +} + /* * If "pack" is non-NULL, then "offset" is the byte offset within the pack from * which the object may be accessed (though note that we may also rely on @@ -470,8 +483,13 @@ static void batch_object_write(const char *obj_name, if (!data->skip_object_info) { int ret; - if (use_mailmap) + if (use_mailmap || + opt->objects_filter.choice == LOFC_BLOB_NONE || + opt->objects_filter.choice == LOFC_BLOB_LIMIT || + opt->objects_filter.choice == LOFC_OBJECT_TYPE) data->info.typep = &data->type; + if (opt->objects_filter.choice == LOFC_BLOB_LIMIT) + data->info.sizep = &data->size; if (pack) ret = packed_object_info(the_repository, pack, offset, @@ -481,12 +499,42 @@ static void batch_object_write(const char *obj_name, &data->oid, &data->info, OBJECT_INFO_LOOKUP_REPLACE); if (ret < 0) { - printf("%s missing%c", - obj_name ? obj_name : oid_to_hex(&data->oid), opt->output_delim); - fflush(stdout); + report_object_status(opt, obj_name, &data->oid, "missing"); return; } + switch (opt->objects_filter.choice) { + case LOFC_DISABLED: + break; + case LOFC_BLOB_NONE: + if (data->type == OBJ_BLOB) { + if (!opt->all_objects) + report_object_status(opt, obj_name, + &data->oid, "excluded"); + return; + } + break; + case LOFC_BLOB_LIMIT: + if (data->type == OBJ_BLOB && + data->size >= opt->objects_filter.blob_limit_value) { + if (!opt->all_objects) + report_object_status(opt, obj_name, + &data->oid, "excluded"); + return; + } + break; + case LOFC_OBJECT_TYPE: + if (data->type != opt->objects_filter.object_type) { + if (!opt->all_objects) + report_object_status(opt, obj_name, + &data->oid, "excluded"); + return; + } + break; + default: + BUG("unsupported objects filter"); + } + if (use_mailmap && (data->type == OBJ_COMMIT || data->type == OBJ_TAG)) { size_t s = data->size; char *buf = NULL; @@ -535,10 +583,10 @@ static void batch_one_object(const char *obj_name, if (result != FOUND) { switch (result) { case MISSING_OBJECT: - printf("%s missing%c", obj_name, opt->output_delim); + report_object_status(opt, obj_name, &data->oid, "missing"); break; case SHORT_NAME_AMBIGUOUS: - printf("%s ambiguous%c", obj_name, opt->output_delim); + report_object_status(opt, obj_name, &data->oid, "ambiguous"); break; case DANGLING_SYMLINK: printf("dangling %"PRIuMAX"%c%s%c", @@ -595,25 +643,18 @@ static int batch_object_cb(const struct object_id *oid, void *vdata) return 0; } -static int collect_loose_object(const struct object_id *oid, - const char *path UNUSED, - void *data) -{ - oid_array_append(data, oid); - return 0; -} - -static int collect_packed_object(const struct object_id *oid, - struct packed_git *pack UNUSED, - uint32_t pos UNUSED, - void *data) +static int collect_object(const struct object_id *oid, + struct packed_git *pack UNUSED, + off_t offset UNUSED, + void *data) { oid_array_append(data, oid); return 0; } static int batch_unordered_object(const struct object_id *oid, - struct packed_git *pack, off_t offset, + struct packed_git *pack, + off_t offset, void *vdata) { struct object_cb_data *data = vdata; @@ -627,23 +668,6 @@ static int batch_unordered_object(const struct object_id *oid, return 0; } -static int batch_unordered_loose(const struct object_id *oid, - const char *path UNUSED, - void *data) -{ - return batch_unordered_object(oid, NULL, 0, data); -} - -static int batch_unordered_packed(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, - void *data) -{ - return batch_unordered_object(oid, pack, - nth_packed_object_offset(pack, pos), - data); -} - typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *, struct strbuf *, struct expand_data *); @@ -776,6 +800,76 @@ static void batch_objects_command(struct batch_options *opt, #define DEFAULT_FORMAT "%(objectname) %(objecttype) %(objectsize)" +typedef int (*for_each_object_fn)(const struct object_id *oid, struct packed_git *pack, + off_t offset, void *data); + +struct for_each_object_payload { + for_each_object_fn callback; + void *payload; +}; + +static int batch_one_object_loose(const struct object_id *oid, + const char *path UNUSED, + void *_payload) +{ + struct for_each_object_payload *payload = _payload; + return payload->callback(oid, NULL, 0, payload->payload); +} + +static int batch_one_object_packed(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *_payload) +{ + struct for_each_object_payload *payload = _payload; + return payload->callback(oid, pack, nth_packed_object_offset(pack, pos), + payload->payload); +} + +static int batch_one_object_bitmapped(const struct object_id *oid, + enum object_type type UNUSED, + int flags UNUSED, + uint32_t hash UNUSED, + struct packed_git *pack, + off_t offset, + void *_payload) +{ + struct for_each_object_payload *payload = _payload; + return payload->callback(oid, pack, offset, payload->payload); +} + +static void batch_each_object(struct batch_options *opt, + for_each_object_fn callback, + unsigned flags, + void *_payload) +{ + struct for_each_object_payload payload = { + .callback = callback, + .payload = _payload, + }; + struct bitmap_index *bitmap = prepare_bitmap_git(the_repository); + + for_each_loose_object(batch_one_object_loose, &payload, 0); + + if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter, + batch_one_object_bitmapped, &payload)) { + struct packed_git *pack; + + for (pack = get_all_packs(the_repository); pack; pack = pack->next) { + if (bitmap_index_contains_pack(bitmap, pack) || + open_pack_index(pack)) + continue; + for_each_object_in_pack(pack, batch_one_object_packed, + &payload, flags); + } + } else { + for_each_packed_object(the_repository, batch_one_object_packed, + &payload, flags); + } + + free_bitmap_index(bitmap); +} + static int batch_objects(struct batch_options *opt) { struct strbuf input = STRBUF_INIT; @@ -812,7 +906,8 @@ static int batch_objects(struct batch_options *opt) struct object_cb_data cb; struct object_info empty = OBJECT_INFO_INIT; - if (!memcmp(&data.info, &empty, sizeof(empty))) + if (!memcmp(&data.info, &empty, sizeof(empty)) && + opt->objects_filter.choice == LOFC_DISABLED) data.skip_object_info = 1; if (repo_has_promisor_remote(the_repository)) @@ -829,18 +924,14 @@ static int batch_objects(struct batch_options *opt) cb.seen = &seen; - for_each_loose_object(batch_unordered_loose, &cb, 0); - for_each_packed_object(the_repository, batch_unordered_packed, - &cb, FOR_EACH_OBJECT_PACK_ORDER); + batch_each_object(opt, batch_unordered_object, + FOR_EACH_OBJECT_PACK_ORDER, &cb); oidset_clear(&seen); } else { struct oid_array sa = OID_ARRAY_INIT; - for_each_loose_object(collect_loose_object, &sa, 0); - for_each_packed_object(the_repository, collect_packed_object, - &sa, 0); - + batch_each_object(opt, collect_object, 0, &sa); oid_array_for_each_unique(&sa, batch_object_cb, &cb); oid_array_clear(&sa); @@ -936,12 +1027,15 @@ int cmd_cat_file(int argc, int opt_cw = 0; int opt_epts = 0; const char *exp_type = NULL, *obj_name = NULL; - struct batch_options batch = {0}; + struct batch_options batch = { + .objects_filter = LIST_OBJECTS_FILTER_INIT, + }; int unknown_type = 0; int input_nul_terminated = 0; int nul_terminated = 0; + int ret; - const char * const usage[] = { + const char * const builtin_catfile_usage[] = { N_("git cat-file <type> <object>"), N_("git cat-file (-e | -p) <object>"), N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"), @@ -1000,6 +1094,7 @@ int cmd_cat_file(int argc, N_("run filters on object's content"), 'w'), OPT_STRING(0, "path", &force_path, N_("blob|tree"), N_("use a <path> for (--textconv | --filters); Not with 'batch'")), + OPT_PARSE_LIST_OBJECTS_FILTER(&batch.objects_filter), OPT_END() }; @@ -1007,13 +1102,27 @@ int cmd_cat_file(int argc, batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, usage, 0); + argc = parse_options(argc, argv, prefix, options, builtin_catfile_usage, 0); opt_cw = (opt == 'c' || opt == 'w'); opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); if (use_mailmap) read_mailmap(&mailmap); + switch (batch.objects_filter.choice) { + case LOFC_DISABLED: + break; + case LOFC_BLOB_NONE: + case LOFC_BLOB_LIMIT: + case LOFC_OBJECT_TYPE: + if (!batch.enabled) + usage(_("objects filter only supported in batch mode")); + break; + default: + usagef(_("objects filter not supported: '%s'"), + list_object_filter_config_name(batch.objects_filter.choice)); + } + /* --batch-all-objects? */ if (opt == 'b') batch.all_objects = 1; @@ -1021,7 +1130,7 @@ int cmd_cat_file(int argc, /* Option compatibility */ if (force_path && !opt_cw) usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"), - usage, options, + builtin_catfile_usage, options, "--path", _("path|tree-ish"), "--filters", "--textconv"); @@ -1029,20 +1138,20 @@ int cmd_cat_file(int argc, if (batch.enabled) ; else if (batch.follow_symlinks) - usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "--follow-symlinks"); + usage_msg_optf(_("'%s' requires a batch mode"), builtin_catfile_usage, + options, "--follow-symlinks"); else if (batch.buffer_output >= 0) - usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "--buffer"); + usage_msg_optf(_("'%s' requires a batch mode"), builtin_catfile_usage, + options, "--buffer"); else if (batch.all_objects) - usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "--batch-all-objects"); + usage_msg_optf(_("'%s' requires a batch mode"), builtin_catfile_usage, + options, "--batch-all-objects"); else if (input_nul_terminated) - usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "-z"); + usage_msg_optf(_("'%s' requires a batch mode"), builtin_catfile_usage, + options, "-z"); else if (nul_terminated) - usage_msg_optf(_("'%s' requires a batch mode"), usage, options, - "-Z"); + usage_msg_optf(_("'%s' requires a batch mode"), builtin_catfile_usage, + options, "-Z"); batch.input_delim = batch.output_delim = '\n'; if (input_nul_terminated) @@ -1063,33 +1172,37 @@ int cmd_cat_file(int argc, batch.transform_mode = opt; else if (opt && opt != 'b') usage_msg_optf(_("'-%c' is incompatible with batch mode"), - usage, options, opt); + builtin_catfile_usage, options, opt); else if (argc) - usage_msg_opt(_("batch modes take no arguments"), usage, - options); + usage_msg_opt(_("batch modes take no arguments"), + builtin_catfile_usage, options); - return batch_objects(&batch); + ret = batch_objects(&batch); + goto out; } if (opt) { if (!argc && opt == 'c') usage_msg_optf(_("<rev> required with '%s'"), - usage, options, "--textconv"); + builtin_catfile_usage, options, + "--textconv"); else if (!argc && opt == 'w') usage_msg_optf(_("<rev> required with '%s'"), - usage, options, "--filters"); + builtin_catfile_usage, options, + "--filters"); else if (!argc && opt_epts) usage_msg_optf(_("<object> required with '-%c'"), - usage, options, opt); + builtin_catfile_usage, options, opt); else if (argc == 1) obj_name = argv[0]; else - usage_msg_opt(_("too many arguments"), usage, options); + usage_msg_opt(_("too many arguments"), builtin_catfile_usage, + options); } else if (!argc) { - usage_with_options(usage, options); + usage_with_options(builtin_catfile_usage, options); } else if (argc != 2) { usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"), - usage, options, argc); + builtin_catfile_usage, options, argc); } else if (argc) { exp_type = argv[0]; obj_name = argv[1]; @@ -1097,5 +1210,10 @@ int cmd_cat_file(int argc, if (unknown_type && opt != 't' && opt != 's') die("git cat-file --allow-unknown-type: use with -s or -t"); - return cat_one_file(opt, exp_type, obj_name, unknown_type); + + ret = cat_one_file(opt, exp_type, obj_name, unknown_type); + +out: + list_objects_filter_release(&batch.objects_filter); + return ret; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 01ea9ff8b2..d185982f3a 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -17,9 +17,10 @@ #include "merge-ll.h" #include "lockfile.h" #include "mem-pool.h" -#include "merge-recursive.h" +#include "merge-ort-wrappers.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "parse-options.h" #include "path.h" #include "preload-index.h" @@ -130,8 +131,8 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm int changed) { return run_hooks_l(the_repository, "post-checkout", - oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), - oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), + oid_to_hex(old_commit ? &old_commit->object.oid : null_oid(the_hash_algo)), + oid_to_hex(new_commit ? &new_commit->object.oid : null_oid(the_hash_algo)), changed ? "1" : "0", NULL); /* "new_commit" can be NULL when checking out from the index before a commit exists. */ @@ -710,7 +711,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.src_index = the_repository->index; opts.dst_index = the_repository->index; init_checkout_metadata(&opts.meta, info->refname, - info->commit ? &info->commit->object.oid : null_oid(), + info->commit ? &info->commit->object.oid : null_oid(the_hash_algo), NULL); if (parse_tree(tree) < 0) return 128; @@ -907,10 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts, o.branch1 = new_branch_info->name; o.branch2 = "local"; o.conflict_style = opts->conflict_style; - ret = merge_trees(&o, - new_tree, - work, - old_tree); + ret = merge_ort_nonrecursive(&o, + new_tree, + work, + old_tree); if (ret < 0) exit(128); ret = reset_tree(new_tree, diff --git a/builtin/clone.c b/builtin/clone.c index 88276e5b7a..b6f378c438 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -25,7 +25,7 @@ #include "refs.h" #include "refspec.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "tree.h" #include "tree-walk.h" #include "unpack-trees.h" @@ -452,7 +452,9 @@ static struct ref *wanted_peer_refs(struct clone_opts *opts, if (head) tail_link_ref(head, &tail); if (option_single_branch) - refs = to_free = guess_remote_head(head, refs, 0); + refs = to_free = + guess_remote_head(head, refs, + REMOTE_GUESS_HEAD_QUIET); } else if (option_single_branch) { local_refs = NULL; tail = &local_refs; @@ -692,7 +694,7 @@ static int checkout(int submodule_progress, int filter_submodules, if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hooks_l(the_repository, "post-checkout", oid_to_hex(null_oid()), + err |= run_hooks_l(the_repository, "post-checkout", oid_to_hex(null_oid(the_hash_algo)), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { @@ -930,9 +932,16 @@ int cmd_clone(int argc, N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, - N_("pathspec"), N_("initialize submodules in the clone"), - PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, + { + .type = OPTION_CALLBACK, + .long_name = "recurse-submodules", + .value = &option_recurse_submodules, + .argh = N_("pathspec"), + .help = N_("initialize submodules in the clone"), + .flags = PARSE_OPT_OPTARG, + .callback = recurse_submodules_cb, + .defval = (intptr_t)".", + }, OPT_ALIAS(0, "recursive", "recurse-submodules"), OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules cloned in parallel")), @@ -1090,7 +1099,7 @@ int cmd_clone(int argc, sigchain_push_common(remove_junk_on_signal); if (!option_bare) { - if (safe_create_leading_directories_const(work_tree) < 0) + if (safe_create_leading_directories_const(the_repository, work_tree) < 0) die_errno(_("could not create leading directories of '%s'"), work_tree); if (dest_exists) @@ -1111,7 +1120,7 @@ int cmd_clone(int argc, junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = git_dir; } - if (safe_create_leading_directories_const(git_dir) < 0) + if (safe_create_leading_directories_const(the_repository, git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); if (0 <= option_verbosity) { @@ -1525,7 +1534,8 @@ int cmd_clone(int argc, } remote_head = find_ref_by_name(refs, "HEAD"); - remote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0); + remote_head_points_at = guess_remote_head(remote_head, mapped_refs, + REMOTE_GUESS_HEAD_QUIET); if (option_branch) { our_head_points_at = find_remote_branch(mapped_refs, option_branch); diff --git a/builtin/column.c b/builtin/column.c index 50314cc255..ce6443d5fa 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -31,7 +31,7 @@ int cmd_column(int argc, struct option options[] = { OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")), OPT_COLUMN(0, "mode", &colopts, N_("layout to use")), - OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), + OPT_UNSIGNED(0, "raw-mode", &colopts, N_("layout to use")), OPT_INTEGER(0, "width", &copts.width, N_("maximum width")), OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")), OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")), diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 8ca75262c5..a783a86e79 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -6,7 +6,7 @@ #include "hex.h" #include "parse-options.h" #include "commit-graph.h" -#include "object-store-ll.h" +#include "object-store.h" #include "progress.h" #include "replace-object.h" #include "strbuf.h" @@ -22,12 +22,12 @@ " [--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress]\n" \ " <split-options>") -static const char * builtin_commit_graph_verify_usage[] = { +static const char * const builtin_commit_graph_verify_usage[] = { BUILTIN_COMMIT_GRAPH_VERIFY_USAGE, NULL }; -static const char * builtin_commit_graph_write_usage[] = { +static const char * const builtin_commit_graph_write_usage[] = { BUILTIN_COMMIT_GRAPH_WRITE_USAGE, NULL }; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 38457600a4..ad6b2c9320 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -9,7 +9,7 @@ #include "gettext.h" #include "hex.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "parse-options.h" @@ -111,8 +111,16 @@ int cmd_commit_tree(int argc, OPT_CALLBACK_F('F', NULL, &buffer, N_("file"), N_("read commit log message from file"), PARSE_OPT_NONEG, parse_file_arg_callback), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), - N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &sign_commit, + .argh = N_("key-id"), + .help = N_("GPG sign commit"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, OPT_END() }; int ret; diff --git a/builtin/commit.c b/builtin/commit.c index 2f45968222..66bd91fd52 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1542,17 +1542,34 @@ struct repository *repo UNUSED) STATUS_FORMAT_LONG), OPT_BOOL('z', "null", &s.null_termination, N_("terminate entries with NUL")), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, - N_("mode"), - N_("show untracked files, optional modes: all, normal, no. (Default: all)"), - PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - { OPTION_STRING, 0, "ignored", &ignored_arg, - N_("mode"), - N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"), - PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" }, - { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"), - N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), - PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + { + .type = OPTION_STRING, + .short_name = 'u', + .long_name = "untracked-files", + .value = &untracked_files_arg, + .argh = N_("mode"), + .help = N_("show untracked files, optional modes: all, normal, no. (Default: all)"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t)"all", + }, + { + .type = OPTION_STRING, + .long_name = "ignored", + .value = &ignored_arg, + .argh = N_("mode"), + .help = N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t)"traditional", + }, + { + .type = OPTION_STRING, + .long_name = "ignore-submodules", + .value = &ignore_submodule_arg, + .argh = N_("when"), + .help = N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t)"all", + }, OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")), OPT_CALLBACK_F('M', "find-renames", &rename_score_arg, @@ -1688,8 +1705,16 @@ int cmd_commit(int argc, OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_CLEANUP(&cleanup_arg), OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), - N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &sign_commit, + .argh = N_("key-id"), + .help = N_("GPG sign commit"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, /* end commit message options */ OPT_GROUP(N_("Commit contents options")), @@ -1714,7 +1739,16 @@ int cmd_commit(int argc, N_("terminate entries with NUL")), OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + { + .type = OPTION_STRING, + .short_name = 'u', + .long_name = "untracked-files", + .value = &untracked_files_arg, + .argh = N_("mode"), + .help = N_("show untracked files, optional modes: all, normal, no. (Default: all)"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t)"all", + }, OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ diff --git a/builtin/config.c b/builtin/config.c index 53a90094e3..f70d635477 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -131,9 +131,16 @@ struct config_display_options { #define TYPE_COLOR 6 #define TYPE_BOOL_OR_STR 7 -#define OPT_CALLBACK_VALUE(s, l, v, h, i) \ - { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ - PARSE_OPT_NONEG, option_parse_type, (i) } +#define OPT_CALLBACK_VALUE(s, l, v, h, i) { \ + .type = OPTION_CALLBACK, \ + .short_name = (s), \ + .long_name = (l), \ + .value = (v), \ + .help = (h), \ + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, \ + .callback = option_parse_type, \ + .defval = (i), \ +} static int option_parse_type(const struct option *opt, const char *arg, int unset) diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 1e89148ed7..0bb5360b2f 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -12,7 +12,7 @@ #include "parse-options.h" #include "quote.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" static unsigned long garbage; static off_t size_garbage; diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index e707618e74..5065ff4660 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -2,8 +2,8 @@ #include "builtin.h" #include "abspath.h" #include "gettext.h" -#include "object-file.h" #include "parse-options.h" +#include "path.h" #ifndef NO_UNIX_SOCKETS @@ -271,7 +271,7 @@ static void init_socket_directory(const char *path) * condition in which somebody can chdir to it, sleep, then try to open * our protected socket. */ - if (safe_create_leading_directories_const(dir) < 0) + if (safe_create_leading_directories_const(the_repository, dir) < 0) die_errno("unable to create directories for '%s'", dir); if (mkdir(dir, 0700) < 0) die_errno("unable to mkdir '%s'", dir); diff --git a/builtin/describe.c b/builtin/describe.c index e2e73f3d75..2d50883b72 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -19,7 +19,7 @@ #include "setup.h" #include "strvec.h" #include "run-command.h" -#include "object-store-ll.h" +#include "object-store.h" #include "list-objects.h" #include "commit-slab.h" #include "wildmatch.h" @@ -518,7 +518,7 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) { struct rev_info revs; struct strvec args = STRVEC_INIT; - struct process_commit_data pcd = { *null_oid(), oid, dst, &revs}; + struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs}; strvec_pushl(&args, "internal: The first arg is not parsed", "--objects", "--in-commit-order", "--reverse", "HEAD", @@ -601,12 +601,24 @@ int cmd_describe(int argc, N_("do not consider tags matching <pattern>")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), - {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), - N_("append <mark> on dirty working tree (default: \"-dirty\")"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, - {OPTION_STRING, 0, "broken", &broken, N_("mark"), - N_("append <mark> on broken working tree (default: \"-broken\")"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "-broken"}, + { + .type = OPTION_STRING, + .long_name = "dirty", + .value = &dirty, + .argh = N_("mark"), + .help = N_("append <mark> on dirty working tree (default: \"-dirty\")"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "-dirty", + }, + { + .type = OPTION_STRING, + .long_name = "broken", + .value = &broken, + .argh = N_("mark"), + .help = N_("append <mark> on broken working tree (default: \"-broken\")"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "-broken", + }, OPT_END(), }; diff --git a/builtin/diagnose.c b/builtin/diagnose.c index 33c39bd598..ec86d66389 100644 --- a/builtin/diagnose.c +++ b/builtin/diagnose.c @@ -3,8 +3,8 @@ #include "builtin.h" #include "abspath.h" #include "gettext.h" -#include "object-file.h" #include "parse-options.h" +#include "path.h" #include "diagnose.h" static const char * const diagnose_usage[] = { @@ -50,7 +50,7 @@ int cmd_diagnose(int argc, strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0); strbuf_addstr(&zip_path, ".zip"); - switch (safe_create_leading_directories(zip_path.buf)) { + switch (safe_create_leading_directories(the_repository, zip_path.buf)) { case SCLD_OK: case SCLD_EXISTS: break; diff --git a/builtin/diff.c b/builtin/diff.c index a4fffee42c..fa963808c3 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -104,7 +104,7 @@ static void builtin_diff_b_f(struct rev_info *revs, stuff_change(&revs->diffopt, blob[0]->mode, canon_mode(st.st_mode), - &blob[0]->item->oid, null_oid(), + &blob[0]->item->oid, null_oid(the_hash_algo), 1, 0, blob[0]->path ? blob[0]->path : path, path); @@ -498,7 +498,8 @@ int cmd_diff(int argc, /* If this is a no-index diff, just run it and exit there. */ if (no_index) - exit(diff_no_index(&rev, no_index == DIFF_NO_INDEX_IMPLICIT, + exit(diff_no_index(&rev, the_repository->hash_algo, + no_index == DIFF_NO_INDEX_IMPLICIT, argc, argv)); diff --git a/builtin/difftool.c b/builtin/difftool.c index 41cd00066c..a3b64ce694 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -22,6 +22,7 @@ #include "gettext.h" #include "hex.h" #include "parse-options.h" +#include "path.h" #include "read-cache-ll.h" #include "repository.h" #include "sparse-index.h" @@ -29,7 +30,7 @@ #include "strbuf.h" #include "lockfile.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "dir.h" #include "entry.h" #include "setup.h" @@ -271,9 +272,9 @@ static void changed_files(struct repository *repo, strbuf_release(&buf); } -static int ensure_leading_directories(char *path) +static int ensure_leading_directories(struct repository *repo, char *path) { - switch (safe_create_leading_directories(path)) { + switch (safe_create_leading_directories(repo, path)) { case SCLD_OK: case SCLD_EXISTS: return 0; @@ -341,11 +342,12 @@ static int checkout_path(unsigned mode, struct object_id *oid, return ret; } -static void write_file_in_directory(struct strbuf *dir, size_t dir_len, - const char *path, const char *content) +static void write_file_in_directory(struct repository *repo, + struct strbuf *dir, size_t dir_len, + const char *path, const char *content) { add_path(dir, dir_len, path); - ensure_leading_directories(dir->buf); + ensure_leading_directories(repo, dir->buf); unlink(dir->buf); write_file(dir->buf, "%s", content); } @@ -356,14 +358,15 @@ static void write_file_in_directory(struct strbuf *dir, size_t dir_len, * as text files, resulting in behavior that is analogous to what "git diff" * displays for symlink and submodule diffs. */ -static void write_standin_files(struct pair_entry *entry, - struct strbuf *ldir, size_t ldir_len, - struct strbuf *rdir, size_t rdir_len) +static void write_standin_files(struct repository *repo, + struct pair_entry *entry, + struct strbuf *ldir, size_t ldir_len, + struct strbuf *rdir, size_t rdir_len) { if (*entry->left) - write_file_in_directory(ldir, ldir_len, entry->path, entry->left); + write_file_in_directory(repo, ldir, ldir_len, entry->path, entry->left); if (*entry->right) - write_file_in_directory(rdir, rdir_len, entry->path, entry->right); + write_file_in_directory(repo, rdir, rdir_len, entry->path, entry->right); } static int run_dir_diff(struct repository *repo, @@ -533,7 +536,7 @@ static int run_dir_diff(struct repository *repo, ADD_CACHE_JUST_APPEND); add_path(&rdir, rdir_len, dst_path); - if (ensure_leading_directories(rdir.buf)) { + if (ensure_leading_directories(repo, rdir.buf)) { ret = error("could not create " "directory for '%s'", dst_path); @@ -576,7 +579,7 @@ static int run_dir_diff(struct repository *repo, */ hashmap_for_each_entry(&submodules, &iter, entry, entry /* member name */) { - write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); + write_standin_files(repo, entry, &ldir, ldir_len, &rdir, rdir_len); } /* @@ -587,7 +590,7 @@ static int run_dir_diff(struct repository *repo, hashmap_for_each_entry(&symlinks2, &iter, entry, entry /* member name */) { - write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); + write_standin_files(repo, entry, &ldir, ldir_len, &rdir, rdir_len); } strbuf_setlen(&ldir, ldir_len); @@ -750,8 +753,7 @@ int cmd_difftool(int argc, }; struct child_process child = CHILD_PROCESS_INIT; - if (repo) - repo_config(repo, difftool_config, &dt_options); + repo_config(repo, difftool_config, &dt_options); dt_options.symlinks = dt_options.has_symlinks; argc = parse_options(argc, argv, prefix, builtin_difftool_options, diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 126980f724..37c01d6c6f 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -14,7 +14,7 @@ #include "refs.h" #include "refspec.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "object.h" #include "tag.h" @@ -30,7 +30,7 @@ #include "remote.h" #include "blob.h" -static const char *fast_export_usage[] = { +static const char *const fast_export_usage[] = { N_("git fast-export [<rev-list-opts>]"), NULL }; @@ -949,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag) p = rewrite_commit((struct commit *)tagged); if (!p) { printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(null_oid())); + name, oid_to_hex(null_oid(the_hash_algo))); free(buf); return; } @@ -963,7 +963,7 @@ static void handle_tag(const char *name, struct tag *tag) if (tagged->type == OBJ_TAG) { printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(null_oid())); + name, oid_to_hex(null_oid(the_hash_algo))); } skip_prefix(name, "refs/tags/", &name); printf("tag %s\n", name); @@ -1103,7 +1103,7 @@ static void handle_tags_and_duplicates(struct string_list *extras) * it. */ printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(null_oid())); + name, oid_to_hex(null_oid(the_hash_algo))); continue; } @@ -1122,7 +1122,7 @@ static void handle_tags_and_duplicates(struct string_list *extras) if (!reference_excluded_commits) { /* delete the ref */ printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(null_oid())); + name, oid_to_hex(null_oid(the_hash_algo))); continue; } /* set ref to commit using oid, not mark */ @@ -1233,7 +1233,7 @@ static void handle_deletes(void) continue; printf("reset %s\nfrom %s\n\n", - refspec->dst, oid_to_hex(null_oid())); + refspec->dst, oid_to_hex(null_oid(the_hash_algo))); } } diff --git a/builtin/fast-import.c b/builtin/fast-import.c index e432e8d5a1..c1e198f4e3 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -24,7 +24,7 @@ #include "packfile.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "mem-pool.h" #include "commit-reach.h" #include "khash.h" @@ -770,7 +770,7 @@ static void start_packfile(void) p->pack_fd = pack_fd; p->do_not_close = 1; p->repo = the_repository; - pack_file = hashfd(pack_fd, p->pack_name); + pack_file = hashfd(the_repository->hash_algo, pack_fd, p->pack_name); pack_data = p; pack_size = write_pack_header(pack_file, 0); @@ -798,7 +798,7 @@ static const char *create_index(void) if (c != last) die("internal consistency error creating the index"); - tmpfile = write_idx_file(the_hash_algo, NULL, idx, object_count, + tmpfile = write_idx_file(the_repository, NULL, idx, object_count, &pack_idx_opts, pack_data->hash); free(idx); return tmpfile; @@ -1720,7 +1720,7 @@ static void dump_marks(void) if (!export_marks_file || (import_marks_file && !import_marks_file_done)) return; - if (safe_create_leading_directories_const(export_marks_file)) { + if (safe_create_leading_directories_const(the_repository, export_marks_file)) { failure |= error_errno("unable to create leading directories of %s", export_marks_file); return; @@ -2021,7 +2021,7 @@ static void parse_and_store_blob( static struct strbuf buf = STRBUF_INIT; uintmax_t len; - if (parse_data(&buf, big_file_threshold, &len)) + if (parse_data(&buf, repo_settings_get_big_file_threshold(the_repository), &len)) store_object(OBJ_BLOB, &buf, last, oidout, mark); else { if (last) { @@ -3425,7 +3425,7 @@ static int parse_one_option(const char *option) unsigned long v; if (!git_parse_ulong(option, &v)) return 0; - big_file_threshold = v; + repo_settings_set_big_file_threshold(the_repository, v); } else if (skip_prefix(option, "depth=", &option)) { option_depth(option); } else if (skip_prefix(option, "active-branches=", &option)) { diff --git a/builtin/fetch.c b/builtin/fetch.c index 9830c09011..5279997c96 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -14,7 +14,7 @@ #include "refs.h" #include "refspec.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oidset.h" #include "oid-array.h" #include "commit.h" @@ -687,7 +687,7 @@ static int s_update_ref(const char *action, switch (ref_transaction_commit(our_transaction, &err)) { case 0: break; - case TRANSACTION_NAME_CONFLICT: + case REF_TRANSACTION_ERROR_NAME_CONFLICT: ret = STORE_REF_ERROR_DF_CONFLICT; goto out; default: @@ -1638,14 +1638,11 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), - fetch_map, 1); + fetch_map, REMOTE_GUESS_HEAD_ALL); for (ref = matches; ref; ref = ref->next) { string_list_append(&heads, strip_refshead(ref->name)); } - if (follow_remote_head == FOLLOW_REMOTE_NEVER) - goto cleanup; - if (!heads.nr) result = 1; else if (heads.nr > 1) @@ -1691,21 +1688,6 @@ cleanup: return result; } -static int uses_remote_tracking(struct transport *transport, struct refspec *rs) -{ - if (!remote_is_configured(transport->remote, 0)) - return 0; - - if (!rs->nr) - rs = &transport->remote->fetch; - - for (int i = 0; i < rs->nr; i++) - if (rs->items[i].dst) - return 1; - - return 0; -} - static int do_fetch(struct transport *transport, struct refspec *rs, const struct fetch_config *config) @@ -1720,6 +1702,7 @@ static int do_fetch(struct transport *transport, TRANSPORT_LS_REFS_OPTIONS_INIT; struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; + int do_set_head = 0; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1740,9 +1723,12 @@ static int do_fetch(struct transport *transport, } else { struct branch *branch = branch_get(NULL); - if (transport->remote->fetch.nr) + if (transport->remote->fetch.nr) { refspec_ref_prefixes(&transport->remote->fetch, &transport_ls_refs_options.ref_prefixes); + if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER) + do_set_head = 1; + } if (branch_has_merge_config(branch) && !strcmp(branch->remote_name, transport->remote->name)) { int i; @@ -1765,8 +1751,7 @@ static int do_fetch(struct transport *transport, strvec_push(&transport_ls_refs_options.ref_prefixes, "refs/tags/"); - if (transport_ls_refs_options.ref_prefixes.nr && - uses_remote_tracking(transport, rs)) + if (do_set_head) strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); @@ -1859,8 +1844,15 @@ static int do_fetch(struct transport *transport, goto cleanup; retcode = ref_transaction_commit(transaction, &err); - if (retcode) + if (retcode) { + /* + * Explicitly handle transaction cleanup to avoid + * aborting an already closed transaction. + */ + ref_transaction_free(transaction); + transaction = NULL; goto cleanup; + } } commit_fetch_head(&fetch_head); @@ -1918,12 +1910,13 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } } - if (set_head(remote_refs, transport->remote)) - ; + if (do_set_head) { /* - * Way too many cases where this can go wrong - * so let's just fail silently for now. + * Way too many cases where this can go wrong so let's just + * ignore errors and fail silently for now. */ + set_head(remote_refs, transport->remote); + } cleanup: if (retcode) { @@ -2359,8 +2352,14 @@ int cmd_fetch(int argc, OPT_SET_INT_F(0, "refetch", &refetch, N_("re-fetch without negotiating common commits"), 1, PARSE_OPT_NONEG), - { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), - N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, + { + .type = OPTION_STRING, + .long_name = "submodule-prefix", + .value = &submodule_prefix, + .argh = N_("dir"), + .help = N_("prepend this to submodule path output"), + .flags = PARSE_OPT_HIDDEN, + }, OPT_CALLBACK_F(0, "recurse-submodules-default", &recurse_submodules_default, N_("on-demand"), N_("default for recursive fetching of submodules " diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 189cd1096a..3b6aac2cf7 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -20,13 +20,26 @@ int cmd_fmt_merge_msg(int argc, char *into_name = NULL; int shortlog_len = -1; struct option options[] = { - { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), - N_("populate log with at most <n> entries from shortlog"), - PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, - { OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"), - N_("alias for --log (deprecated)"), - PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL, - DEFAULT_MERGE_LOG_LEN }, + { + .type = OPTION_INTEGER, + .long_name = "log", + .value = &shortlog_len, + .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("populate log with at most <n> entries from shortlog"), + .flags = PARSE_OPT_OPTARG, + .defval = DEFAULT_MERGE_LOG_LEN, + }, + { + .type = OPTION_INTEGER, + .long_name = "summary", + .value = &shortlog_len, + .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("alias for --log (deprecated)"), + .flags = PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, + .defval = DEFAULT_MERGE_LOG_LEN, + }, OPT_STRING('m', "message", &message, N_("text"), N_("use <text> as start of message")), OPT_STRING(0, "into-name", &into_name, N_("name"), diff --git a/builtin/fsck.c b/builtin/fsck.c index 8fbd1d6334..6cac28356c 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -17,7 +17,7 @@ #include "packfile.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "read-cache-ll.h" #include "replace-object.h" @@ -332,7 +332,7 @@ static void check_unreachable_object(struct object *obj) describe_object(&obj->oid)); FILE *f; - if (safe_create_leading_directories_const(filename)) { + if (safe_create_leading_directories_const(the_repository, filename)) { error(_("could not create lost-found")); free(filename); return; @@ -400,12 +400,12 @@ static void check_connectivity(void) } /* Look up all the requirements, warn about missing objects.. */ - max = get_max_object_index(); + max = get_max_object_index(the_repository); if (verbose) fprintf_ln(stderr, _("Checking connectivity (%d objects)"), max); for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); + struct object *obj = get_indexed_object(the_repository, i); if (obj) check_object(obj); @@ -626,7 +626,7 @@ static int fsck_loose(const struct object_id *oid, const char *path, void *data) void *contents = NULL; int eaten; struct object_info oi = OBJECT_INFO_INIT; - struct object_id real_oid = *null_oid(); + struct object_id real_oid = *null_oid(the_hash_algo); int err = 0; strbuf_reset(&cb_data->obj_type); diff --git a/builtin/gc.c b/builtin/gc.c index 99431fd467..e690453d4f 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -28,11 +28,11 @@ #include "commit.h" #include "commit-graph.h" #include "packfile.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pack.h" #include "pack-objects.h" #include "path.h" +#include "reflog.h" #include "blob.h" #include "tree.h" #include "promisor-remote.h" @@ -53,7 +53,6 @@ static const char * const builtin_gc_usage[] = { static timestamp_t gc_log_expire_time; -static struct strvec reflog = STRVEC_INIT; static struct strvec repack = STRVEC_INIT; static struct strvec prune = STRVEC_INIT; static struct strvec prune_worktrees = STRVEC_INIT; @@ -288,6 +287,58 @@ static int maintenance_task_pack_refs(struct maintenance_run_opts *opts, return run_command(&cmd); } +struct count_reflog_entries_data { + struct expire_reflog_policy_cb policy; + size_t count; + size_t limit; +}; + +static int count_reflog_entries(struct object_id *old_oid, struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + int tz, const char *msg, void *cb_data) +{ + struct count_reflog_entries_data *data = cb_data; + if (should_expire_reflog_ent(old_oid, new_oid, committer, timestamp, tz, msg, &data->policy)) + data->count++; + return data->count >= data->limit; +} + +static int reflog_expire_condition(struct gc_config *cfg UNUSED) +{ + timestamp_t now = time(NULL); + struct count_reflog_entries_data data = { + .policy = { + .opts = REFLOG_EXPIRE_OPTIONS_INIT(now), + }, + }; + int limit = 100; + + git_config_get_int("maintenance.reflog-expire.auto", &limit); + if (!limit) + return 0; + if (limit < 0) + return 1; + data.limit = limit; + + repo_config(the_repository, reflog_expire_config, &data.policy.opts); + + reflog_expire_options_set_refname(&data.policy.opts, "HEAD"); + refs_for_each_reflog_ent(get_main_ref_store(the_repository), "HEAD", + count_reflog_entries, &data); + + reflog_expiry_cleanup(&data.policy); + return data.count >= data.limit; +} + +static int maintenance_task_reflog_expire(struct maintenance_run_opts *opts UNUSED, + struct gc_config *cfg UNUSED) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + cmd.git_cmd = 1; + strvec_pushl(&cmd.args, "reflog", "expire", "--all", NULL); + return run_command(&cmd); +} + static int too_many_loose_objects(struct gc_config *cfg) { /* @@ -373,8 +424,13 @@ static uint64_t total_ram(void) #if defined(HAVE_SYSINFO) struct sysinfo si; - if (!sysinfo(&si)) - return si.totalram; + if (!sysinfo(&si)) { + uint64_t total = si.totalram; + + if (si.mem_unit > 1) + total *= (uint64_t)si.mem_unit; + return total; + } #elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM)) int64_t physical_memory; int mib[2]; @@ -667,15 +723,8 @@ static void gc_before_repack(struct maintenance_run_opts *opts, if (cfg->pack_refs && maintenance_task_pack_refs(opts, cfg)) die(FAILED_RUN, "pack-refs"); - - if (cfg->prune_reflogs) { - struct child_process cmd = CHILD_PROCESS_INIT; - - cmd.git_cmd = 1; - strvec_pushv(&cmd.args, reflog.v); - if (run_command(&cmd)) - die(FAILED_RUN, reflog.v[0]); - } + if (cfg->prune_reflogs && maintenance_task_reflog_expire(opts, cfg)) + die(FAILED_RUN, "reflog"); } int cmd_gc(int argc, @@ -699,12 +748,18 @@ struct repository *repo UNUSED) int ret; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), - { OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"), - N_("prune unreferenced objects"), - PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg }, + { + .type = OPTION_STRING, + .long_name = "prune", + .value = &prune_expire_arg, + .argh = N_("date"), + .help = N_("prune unreferenced objects"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t)prune_expire_arg, + }, OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")), - OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size, - N_("with --cruft, limit the size of new cruft packs")), + OPT_UNSIGNED(0, "max-cruft-size", &cfg.max_cruft_size, + N_("with --cruft, limit the size of new cruft packs")), OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), OPT_BOOL_F(0, "auto", &opts.auto_flag, N_("enable auto-gc mode"), PARSE_OPT_NOCOMPLETE), @@ -723,7 +778,6 @@ struct repository *repo UNUSED) show_usage_with_options_if_asked(argc, argv, builtin_gc_usage, builtin_gc_options); - strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); strvec_pushl(&repack, "repack", "-d", "-l", NULL); strvec_pushl(&prune, "prune", "--expire", NULL); strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); @@ -1029,6 +1083,8 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts) if (opts->quiet) strvec_push(&child.args, "--no-progress"); + else + strvec_push(&child.args, "--progress"); return !!run_command(&child); } @@ -1161,6 +1217,7 @@ static int write_loose_object_to_stdin(const struct object_id *oid, fprintf(d->in, "%s\n", oid_to_hex(oid)); + /* If batch_size is INT_MAX, then this will return 0 always. */ return ++(d->count) > d->batch_size; } @@ -1185,6 +1242,8 @@ static int pack_loose(struct maintenance_run_opts *opts) strvec_push(&pack_proc.args, "pack-objects"); if (opts->quiet) strvec_push(&pack_proc.args, "--quiet"); + else + strvec_push(&pack_proc.args, "--no-quiet"); strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path); pack_proc.in = -1; @@ -1204,6 +1263,15 @@ static int pack_loose(struct maintenance_run_opts *opts) data.count = 0; data.batch_size = 50000; + repo_config_get_int(r, "maintenance.loose-objects.batchSize", + &data.batch_size); + + /* If configured as 0, then remove limit. */ + if (!data.batch_size) + data.batch_size = INT_MAX; + else if (data.batch_size > 0) + data.batch_size--; /* Decrease for equality on limit. */ + for_each_loose_file_in_objdir(r->objects->odb->path, write_loose_object_to_stdin, NULL, @@ -1263,6 +1331,8 @@ static int multi_pack_index_write(struct maintenance_run_opts *opts) if (opts->quiet) strvec_push(&child.args, "--no-progress"); + else + strvec_push(&child.args, "--progress"); if (run_command(&child)) return error(_("failed to write multi-pack-index")); @@ -1279,6 +1349,8 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts) if (opts->quiet) strvec_push(&child.args, "--no-progress"); + else + strvec_push(&child.args, "--progress"); if (run_command(&child)) return error(_("'git multi-pack-index expire' failed")); @@ -1335,6 +1407,8 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts) if (opts->quiet) strvec_push(&child.args, "--no-progress"); + else + strvec_push(&child.args, "--progress"); strvec_pushf(&child.args, "--batch-size=%"PRIuMAX, (uintmax_t)get_auto_pack_size()); @@ -1392,6 +1466,7 @@ enum maintenance_task_label { TASK_GC, TASK_COMMIT_GRAPH, TASK_PACK_REFS, + TASK_REFLOG_EXPIRE, /* Leave as final value */ TASK__COUNT @@ -1428,6 +1503,11 @@ static struct maintenance_task tasks[] = { maintenance_task_pack_refs, pack_refs_condition, }, + [TASK_REFLOG_EXPIRE] = { + "reflog-expire", + maintenance_task_reflog_expire, + reflog_expire_condition, + }, }; static int compare_tasks_by_selection(const void *a_, const void *b_) @@ -2075,7 +2155,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit case SCHEDULE_DAILY: repeat = "<dict>\n" - "<key>Day</key><integer>%d</integer>\n" + "<key>Weekday</key><integer>%d</integer>\n" "<key>Hour</key><integer>0</integer>\n" "<key>Minute</key><integer>%d</integer>\n" "</dict>\n"; @@ -2086,7 +2166,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit case SCHEDULE_WEEKLY: strbuf_addf(&plist, "<dict>\n" - "<key>Day</key><integer>0</integer>\n" + "<key>Weekday</key><integer>0</integer>\n" "<key>Hour</key><integer>0</integer>\n" "<key>Minute</key><integer>%d</integer>\n" "</dict>\n", @@ -2099,7 +2179,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit } strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n"); - if (safe_create_leading_directories(filename)) + if (safe_create_leading_directories(the_repository, filename)) die(_("failed to create directories for '%s'"), filename); if ((long)lock_file_timeout_ms < 0 && @@ -2565,7 +2645,7 @@ static int systemd_timer_write_timer_file(enum schedule_priority schedule, filename = xdg_config_home_systemd(local_timer_name); - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories(the_repository, filename)) { error(_("failed to create directories for '%s'"), filename); goto error; } @@ -2638,7 +2718,7 @@ static int systemd_timer_write_service_template(const char *exec_path) char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service"); filename = xdg_config_home_systemd(local_service_name); - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories(the_repository, filename)) { error(_("failed to create directories for '%s'"), filename); goto error; } diff --git a/builtin/grep.c b/builtin/grep.c index d1427290f7..3ce574a605 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -26,7 +26,7 @@ #include "submodule-config.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "pager.h" #include "path.h" @@ -453,7 +453,7 @@ static int grep_submodule(struct grep_opt *opt, return 0; subrepo = xmalloc(sizeof(*subrepo)); - if (repo_submodule_init(subrepo, superproject, path, null_oid())) { + if (repo_submodule_init(subrepo, superproject, path, null_oid(opt->repo->hash_algo))) { free(subrepo); return 0; } @@ -983,9 +983,9 @@ int cmd_grep(int argc, OPT_CALLBACK('C', "context", &opt, N_("n"), N_("show <n> context lines before and after matches"), context_callback), - OPT_INTEGER('B', "before-context", &opt.pre_context, + OPT_UNSIGNED('B', "before-context", &opt.pre_context, N_("show <n> context lines before matches")), - OPT_INTEGER('A', "after-context", &opt.post_context, + OPT_UNSIGNED('A', "after-context", &opt.post_context, N_("show <n> context lines after matches")), OPT_INTEGER(0, "threads", &num_threads, N_("use <n> worker threads")), @@ -1017,10 +1017,16 @@ int cmd_grep(int argc, OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), OPT_GROUP(""), - { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, - N_("pager"), N_("show matching files in the pager"), - PARSE_OPT_OPTARG | PARSE_OPT_NOCOMPLETE, - NULL, (intptr_t)default_pager }, + { + .type = OPTION_STRING, + .short_name = 'O', + .long_name = "open-files-in-pager", + .value = &show_in_pager, + .argh = N_("pager"), + .help = N_("show matching files in the pager"), + .flags = PARSE_OPT_OPTARG | PARSE_OPT_NOCOMPLETE, + .defval = (intptr_t)default_pager, + }, OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)"), PARSE_OPT_NOCOMPLETE), @@ -1144,7 +1150,7 @@ int cmd_grep(int argc, break; } - object = parse_object_or_die(&oid, arg); + object = parse_object_or_die(the_repository, &oid, arg); if (!seen_dashdash) verify_non_filename(prefix, arg); add_object_array_with_path(object, arg, &list, oc.mode, oc.path); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index a25f0403f4..cd53fa3bde 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -11,7 +11,7 @@ #include "gettext.h" #include "hex.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "blob.h" #include "quote.h" #include "parse-options.h" @@ -19,6 +19,11 @@ #include "strbuf.h" #include "write-or-die.h" +enum { + HASH_OBJECT_CHECK = (1 << 0), + HASH_OBJECT_WRITE = (1 << 1), +}; + /* * This is to create corrupt objects for debugging and as such it * needs to bypass the data conversion performed by, and the type @@ -33,7 +38,7 @@ static int hash_literally(struct object_id *oid, int fd, const char *type, unsig ret = -1; else ret = write_object_file_literally(buf.buf, buf.len, type, oid, - flags); + (flags & HASH_OBJECT_WRITE) ? WRITE_OBJECT_FILE_PERSIST : 0); close(fd); strbuf_release(&buf); return ret; @@ -42,15 +47,21 @@ static int hash_literally(struct object_id *oid, int fd, const char *type, unsig static void hash_fd(int fd, const char *type, const char *path, unsigned flags, int literally) { + unsigned int index_flags = 0; struct stat st; struct object_id oid; + if (flags & HASH_OBJECT_WRITE) + index_flags |= INDEX_WRITE_OBJECT; + if (flags & HASH_OBJECT_CHECK) + index_flags |= INDEX_FORMAT_CHECK; + if (fstat(fd, &st) < 0 || (literally ? hash_literally(&oid, fd, type, flags) : index_fd(the_repository->index, &oid, fd, &st, - type_from_string(type), path, flags))) - die((flags & HASH_WRITE_OBJECT) + type_from_string(type), path, index_flags))) + die((flags & HASH_OBJECT_WRITE) ? "Unable to add %s to database" : "Unable to hash %s", path); printf("%s\n", oid_to_hex(&oid)); @@ -102,13 +113,13 @@ int cmd_hash_object(int argc, int no_filters = 0; int literally = 0; int nongit = 0; - unsigned flags = HASH_FORMAT_CHECK; + unsigned flags = HASH_OBJECT_CHECK; const char *vpath = NULL; char *vpath_free = NULL; const struct option hash_object_options[] = { OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), OPT_BIT('w', NULL, &flags, N_("write the object into the object database"), - HASH_WRITE_OBJECT), + HASH_OBJECT_WRITE), OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")), OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")), OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")), @@ -122,7 +133,7 @@ int cmd_hash_object(int argc, argc = parse_options(argc, argv, prefix, hash_object_options, hash_object_usage, 0); - if (flags & HASH_WRITE_OBJECT) + if (flags & HASH_OBJECT_WRITE) prefix = setup_git_directory(); else prefix = setup_git_directory_gently(&nongit); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 50573ba049..60a8ee05db 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -21,7 +21,7 @@ #include "packfile.h" #include "pack-revindex.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "oidset.h" #include "path.h" @@ -279,14 +279,14 @@ static unsigned check_objects(void) { unsigned i, max, foreign_nr = 0; - max = get_max_object_index(); + max = get_max_object_index(the_repository); if (verbose) progress = start_delayed_progress(the_repository, _("Checking objects"), max); for (i = 0; i < max; i++) { - foreign_nr += check_object(get_indexed_object(i)); + foreign_nr += check_object(get_indexed_object(the_repository, i)); display_progress(progress, i + 1); } @@ -485,7 +485,8 @@ static void *unpack_entry_data(off_t offset, unsigned long size, git_hash_update(&c, hdr, hdrlen); } else oid = NULL; - if (type == OBJ_BLOB && size > big_file_threshold) + if (type == OBJ_BLOB && + size > repo_settings_get_big_file_threshold(the_repository)) buf = fixed_buf; else buf = xmallocz(size); @@ -799,7 +800,8 @@ static int check_collison(struct object_entry *entry) enum object_type type; unsigned long size; - if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB) + if (entry->size <= repo_settings_get_big_file_threshold(the_repository) || + entry->type != OBJ_BLOB) return -1; memset(&data, 0, sizeof(data)); @@ -1382,7 +1384,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha REALLOC_ARRAY(objects, nr_objects + nr_unresolved + 1); memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); - f = hashfd(output_fd, curr_pack); + f = hashfd(the_repository->hash_algo, output_fd, curr_pack); fix_unresolved_deltas(f); strbuf_addf(&msg, Q_("completed with %d local object", "completed with %d local objects", @@ -2089,10 +2091,10 @@ int cmd_index_pack(int argc, ALLOC_ARRAY(idx_objects, nr_objects); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(the_hash_algo, index_name, idx_objects, + curr_index = write_idx_file(the_repository, index_name, idx_objects, nr_objects, &opts, pack_hash); if (rev_index) - curr_rev_index = write_rev_file(the_hash_algo, rev_index_name, + curr_rev_index = write_rev_file(the_repository, rev_index_name, idx_objects, nr_objects, pack_hash, opts.flags); free(idx_objects); diff --git a/builtin/init-db.c b/builtin/init-db.c index 196dccdd77..bb853e69f5 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -8,7 +8,6 @@ #include "abspath.h" #include "environment.h" #include "gettext.h" -#include "object-file.h" #include "parse-options.h" #include "path.h" #include "refs.h" @@ -93,10 +92,15 @@ int cmd_init_db(int argc, N_("directory from which templates will be used")), OPT_SET_INT(0, "bare", &is_bare_repository_cfg, N_("create a bare repository"), 1), - { OPTION_CALLBACK, 0, "shared", &init_shared_repository, - N_("permissions"), - N_("specify that the git repository is to be shared amongst several users"), - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, + { + .type = OPTION_CALLBACK, + .long_name = "shared", + .value = &init_shared_repository, + .argh = N_("permissions"), + .help = N_("specify that the git repository is to be shared amongst several users"), + .flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + .callback = shared_callback + }, OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), @@ -134,7 +138,7 @@ int cmd_init_db(int argc, */ saved = repo_settings_get_shared_repository(the_repository); repo_settings_set_shared_repository(the_repository, 0); - switch (safe_create_leading_directories_const(argv[0])) { + switch (safe_create_leading_directories_const(the_repository, argv[0])) { case SCLD_OK: case SCLD_PERMS: break; diff --git a/builtin/log.c b/builtin/log.c index 04a6ef97bc..b450cd3bde 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -14,9 +14,8 @@ #include "gettext.h" #include "hex.h" #include "refs.h" -#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pager.h" #include "color.h" #include "commit.h" @@ -29,6 +28,7 @@ #include "tag.h" #include "reflog-walk.h" #include "patch-ids.h" +#include "path.h" #include "shortlog.h" #include "remote.h" #include "string-list.h" @@ -2311,7 +2311,7 @@ int cmd_format_patch(int argc, */ saved = repo_settings_get_shared_repository(the_repository); repo_settings_set_shared_repository(the_repository, 0); - switch (safe_create_leading_directories_const(output_directory)) { + switch (safe_create_leading_directories_const(the_repository, output_directory)) { case SCLD_OK: case SCLD_EXISTS: break; @@ -2468,7 +2468,7 @@ int cmd_format_patch(int argc, base = get_base_commit(&cfg, list, nr); if (base) { reset_revision_walk(); - clear_object_flags(UNINTERESTING); + clear_object_flags(the_repository, UNINTERESTING); prepare_bases(&bases, base, list, nr); } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 70a377e9c0..be74f0a03b 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -233,7 +233,8 @@ static void show_submodule(struct repository *superproject, { struct repository subrepo; - if (repo_submodule_init(&subrepo, superproject, path, null_oid())) + if (repo_submodule_init(&subrepo, superproject, path, + null_oid(superproject->hash_algo))) return; if (repo_read_index(&subrepo) < 0) diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 42f34e1236..01a4d4daa1 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -67,9 +67,14 @@ int cmd_ls_remote(int argc, OPT__QUIET(&quiet, N_("do not print remote URL")), OPT_STRING(0, "upload-pack", &uploadpack, N_("exec"), N_("path of git-upload-pack on the remote host")), - { OPTION_STRING, 0, "exec", &uploadpack, N_("exec"), - N_("path of git-upload-pack on the remote host"), - PARSE_OPT_HIDDEN }, + { + .type = OPTION_STRING, + .long_name = "exec", + .value = &uploadpack, + .argh = N_("exec"), + .help = N_("path of git-upload-pack on the remote host"), + .flags = PARSE_OPT_HIDDEN, + }, OPT_BIT('t', "tags", &flags, N_("limit to tags"), REF_TAGS), OPT_BIT('b', "branches", &flags, N_("limit to branches"), REF_BRANCHES), OPT_BIT_F('h', "heads", &flags, diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 8542b5d53e..8aafc30ca4 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -10,7 +10,7 @@ #include "gettext.h" #include "hex.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "tree.h" #include "path.h" #include "quote.h" diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 7e315f374b..2b16b10d2c 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -5,6 +5,7 @@ #include "abspath.h" #include "diff.h" #include "hex.h" +#include "object-file.h" #include "object-name.h" #include "object-store.h" #include "config.h" diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index abfc060e28..03b5100cfa 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -3,7 +3,7 @@ #include "advice.h" #include "gettext.h" #include "hash.h" -#include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "object-name.h" static const char builtin_merge_recursive_usage[] = @@ -89,7 +89,7 @@ int cmd_merge_recursive(int argc, if (o.verbosity >= 3) printf(_("Merging %s with %s\n"), o.branch1, o.branch2); - failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result); + failed = merge_ort_generic(&o, &h1, &h2, bases_count, bases, &result); free(better1); free(better2); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 3ec7127b3a..4aafa73c61 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -10,7 +10,7 @@ #include "commit-reach.h" #include "merge-ort.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "parse-options.h" #include "blob.h" #include "merge-blobs.h" diff --git a/builtin/merge.c b/builtin/merge.c index ba9faf126a..ce90e52fe4 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -39,7 +39,6 @@ #include "rerere.h" #include "help.h" #include "merge.h" -#include "merge-recursive.h" #include "merge-ort-wrappers.h" #include "resolve-undo.h" #include "remote.h" @@ -171,7 +170,7 @@ static struct strategy *get_strategy(const char *name) struct strategy *ret; static struct cmdnames main_cmds = {0}, other_cmds = {0}; static int loaded; - char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + char *default_strategy = NULL; if (!name) return NULL; @@ -250,9 +249,16 @@ static struct option builtin_merge_options[] = { OPT_BOOL(0, "stat", &show_diffstat, N_("show a diffstat at the end of the merge")), OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), - { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), - N_("add (at most <n>) entries from shortlog to merge commit message"), - PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, + { + .type = OPTION_INTEGER, + .long_name = "log", + .value = &shortlog_len, + .precision = sizeof(shortlog_len), + .argh = N_("n"), + .help = N_("add (at most <n>) entries from shortlog to merge commit message"), + .flags = PARSE_OPT_OPTARG, + .defval = DEFAULT_MERGE_LOG_LEN, + }, OPT_BOOL(0, "squash", &squash, N_("create a single commit instead of doing a merge")), OPT_BOOL(0, "commit", &option_commit, @@ -274,9 +280,16 @@ static struct option builtin_merge_options[] = { OPT_CALLBACK('m', "message", &merge_msg, N_("message"), N_("merge commit message (for a non-fast-forward merge)"), option_parse_message), - { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), - N_("read message from file"), PARSE_OPT_NONEG, - NULL, 0, option_read_message }, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .short_name = 'F', + .long_name = "file", + .value = &merge_msg, + .argh = N_("path"), + .help = N_("read message from file"), + .flags = PARSE_OPT_NONEG, + .ll_callback = option_read_message, + }, OPT_STRING(0, "into-name", &into_name, N_("name"), N_("use <name> instead of the real target")), OPT__VERBOSITY(&verbosity), @@ -289,8 +302,16 @@ static struct option builtin_merge_options[] = { OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), - N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &sign_commit, + .argh = N_("key-id"), + .help = N_("GPG sign commit"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")), @@ -750,12 +771,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); - if (!strcmp(strategy, "ort")) - clean = merge_ort_recursive(&o, head, remoteheads->item, - reversed, &result); - else - clean = merge_recursive(&o, head, remoteheads->item, - reversed, &result); + clean = merge_ort_recursive(&o, head, remoteheads->item, + reversed, &result); free_commit_list(reversed); strbuf_release(&o.obuf); @@ -1316,12 +1333,6 @@ int cmd_merge(int argc, if (branch) skip_prefix(branch, "refs/heads/", &branch); - if (!pull_twohead) { - char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); - if (default_strategy && !strcmp(default_strategy, "ort")) - pull_twohead = xstrdup("ort"); - } - init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@ -1522,12 +1533,6 @@ int cmd_merge(int argc, fast_forward = FF_NO; } - if (!use_strategies && !pull_twohead && - remoteheads && !remoteheads->next) { - char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); - if (default_strategy) - append_strategy(get_strategy(default_strategy)); - } if (!use_strategies) { if (!remoteheads) ; /* already up-to-date */ diff --git a/builtin/mktag.c b/builtin/mktag.c index 6e188dce50..7ac11c46d5 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -6,7 +6,7 @@ #include "strbuf.h" #include "replace-object.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "fsck.h" #include "config.h" diff --git a/builtin/mktree.c b/builtin/mktree.c index 3c16faa40e..4b47803467 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -11,7 +11,8 @@ #include "strbuf.h" #include "tree.h" #include "parse-options.h" -#include "object-store-ll.h" +#include "object-file.h" +#include "object-store.h" static struct treeent { unsigned mode; @@ -66,7 +67,7 @@ static void write_tree(struct object_id *oid) strbuf_release(&buf); } -static const char *mktree_usage[] = { +static const char *const mktree_usage[] = { "git mktree [-z] [--missing] [--batch]", NULL }; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 2a938466f5..69a9750732 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -7,7 +7,7 @@ #include "midx.h" #include "strbuf.h" #include "trace2.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "repository.h" @@ -245,7 +245,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, { struct option *options; static struct option builtin_multi_pack_index_repack_options[] = { - OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, + OPT_UNSIGNED(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS), diff --git a/builtin/mv.c b/builtin/mv.c index 55a7d471dc..54b323fff7 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -15,6 +15,7 @@ #include "gettext.h" #include "name-hash.h" #include "object-file.h" +#include "path.h" #include "pathspec.h" #include "lockfile.h" #include "dir.h" @@ -28,7 +29,8 @@ #include "entry.h" static const char * const builtin_mv_usage[] = { - N_("git mv [<options>] <source>... <destination>"), + N_("git mv [-v] [-f] [-n] [-k] <source> <destination>"), + N_("git mv [-v] [-f] [-n] [-k] <source>... <destination-directory>"), NULL }; @@ -555,7 +557,7 @@ remove_entry: */ char *dst_dup = xstrdup(dst); string_list_append(&dirty_paths, dst); - safe_create_leading_directories(dst_dup); + safe_create_leading_directories(the_repository, dst_dup); FREE_AND_NULL(dst_dup); rename(src, dst); } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 65f867d7a4..ff199638de 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -675,9 +675,9 @@ int cmd_name_rev(int argc, } else if (all) { int i, max; - max = get_max_object_index(); + max = get_max_object_index(the_repository); for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); + struct object *obj = get_indexed_object(the_repository, i); if (!obj || obj->type != OBJ_COMMIT) continue; show_name(obj, NULL, diff --git a/builtin/notes.c b/builtin/notes.c index ff61ec5f2d..a3f433ca4c 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -14,8 +14,9 @@ #include "gettext.h" #include "hex.h" #include "notes.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "pretty.h" diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 6b06d159d2..8b33edc2ff 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -32,7 +32,7 @@ #include "list.h" #include "packfile.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "dir.h" #include "midx.h" @@ -183,7 +183,7 @@ static inline void oe_set_delta_size(struct packing_data *pack, #define SET_DELTA_CHILD(obj, val) oe_set_delta_child(&to_pack, obj, val) #define SET_DELTA_SIBLING(obj, val) oe_set_delta_sibling(&to_pack, obj, val) -static const char *pack_usage[] = { +static const char *const pack_usage[] = { N_("git pack-objects --stdout [<options>] [< <ref-list> | < <object-list>]"), N_("git pack-objects [<options>] <base-name> [< <ref-list> | < <object-list>]"), NULL @@ -500,7 +500,8 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent if (!usable_delta) { if (oe_type(entry) == OBJ_BLOB && - oe_size_greater_than(&to_pack, entry, big_file_threshold) && + oe_size_greater_than(&to_pack, entry, + repo_settings_get_big_file_threshold(the_repository)) && (st = open_istream(the_repository, &entry->idx.oid, &type, &size, NULL)) != NULL) buf = NULL; @@ -1312,9 +1313,10 @@ static void write_pack_file(void) char *pack_tmp_name = NULL; if (pack_to_stdout) - f = hashfd_throughput(1, "<stdout>", progress_state); + f = hashfd_throughput(the_repository->hash_algo, 1, + "<stdout>", progress_state); else - f = create_tmp_packfile(&pack_tmp_name); + f = create_tmp_packfile(the_repository, &pack_tmp_name); offset = write_pack_header(f, nr_remaining); @@ -1408,7 +1410,7 @@ static void write_pack_file(void) if (cruft) pack_idx_opts.flags |= WRITE_MTIMES; - stage_tmp_packfiles(the_hash_algo, &tmpname, + stage_tmp_packfiles(the_repository, &tmpname, pack_tmp_name, written_list, nr_written, &to_pack, &pack_idx_opts, hash, @@ -1818,7 +1820,8 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, static int add_object_entry_from_bitmap(const struct object_id *oid, enum object_type type, int flags UNUSED, uint32_t name_hash, - struct packed_git *pack, off_t offset) + struct packed_git *pack, off_t offset, + void *payload UNUSED) { display_progress(progress_state, ++nr_seen); @@ -2536,7 +2539,8 @@ static void get_object_details(void) struct object_entry *entry = sorted_by_offset[i]; check_object(entry, i); if (entry->type_valid && - oe_size_greater_than(&to_pack, entry, big_file_threshold)) + oe_size_greater_than(&to_pack, entry, + repo_settings_get_big_file_threshold(the_repository))) entry->no_try_delta = 1; display_progress(progress_state, i + 1); } @@ -3929,7 +3933,7 @@ static void show_commit(struct commit *commit, void *data UNUSED) index_commit_for_bitmap(commit); if (use_delta_islands) - propagate_island_marks(commit); + propagate_island_marks(the_repository, commit); } static void show_object(struct object *obj, const char *name, @@ -4245,7 +4249,7 @@ static int mark_bitmap_preferred_tip(const char *refname, if (!peel_iterated_oid(the_repository, oid, &peeled)) oid = &peeled; - object = parse_object_or_die(oid, refname); + object = parse_object_or_die(the_repository, oid, refname); if (object->type == OBJ_COMMIT) object->flags |= NEEDS_BITMAP; @@ -4484,16 +4488,16 @@ int cmd_pack_objects(int argc, OPT_CALLBACK_F(0, "index-version", &pack_idx_opts, N_("<version>[,<offset>]"), N_("write the pack index file in the specified idx format version"), PARSE_OPT_NONEG, option_parse_index_version), - OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, - N_("maximum size of each output pack file")), + OPT_UNSIGNED(0, "max-pack-size", &pack_size_limit, + N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, N_("ignore borrowed objects from alternate object store")), OPT_BOOL(0, "incremental", &incremental, N_("ignore packed objects")), OPT_INTEGER(0, "window", &window, N_("limit pack window by objects")), - OPT_MAGNITUDE(0, "window-memory", &window_memory_limit, - N_("limit pack window by memory in addition to object limit")), + OPT_UNSIGNED(0, "window-memory", &window_memory_limit, + N_("limit pack window by memory in addition to object limit")), OPT_INTEGER(0, "depth", &depth, N_("maximum length of delta chain allowed in the resulting pack")), OPT_BOOL(0, "reuse-delta", &reuse_delta, diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 3febe732f8..5d1fc78176 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -13,7 +13,7 @@ #include "hex.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strbuf.h" #define BLKSIZE 512 diff --git a/builtin/prune.c b/builtin/prune.c index 1c357fffd8..e930caa0c0 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -17,7 +17,7 @@ #include "replace-object.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "shallow.h" static const char * const prune_usage[] = { @@ -185,7 +185,7 @@ int cmd_prune(int argc, const char *name = *argv++; if (!repo_get_oid(the_repository, name, &oid)) { - struct object *object = parse_object_or_die(&oid, + struct object *object = parse_object_or_die(the_repository, &oid, name); add_pending_object(&revs, object, ""); } diff --git a/builtin/read-tree.c b/builtin/read-tree.c index d2a807a828..a8f352f7cd 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -135,9 +135,14 @@ int cmd_read_tree(int argc, N_("3-way merge in presence of adds and removes")), OPT_BOOL(0, "reset", &opts.reset, N_("same as -m, but discard unmerged entries")), - { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), - N_("read the tree into the index under <subdirectory>/"), - PARSE_OPT_NONEG }, + { + .type = OPTION_STRING, + .long_name = "prefix", + .value = &opts.prefix, + .argh = N_("<subdirectory>/"), + .help = N_("read the tree into the index under <subdirectory>/"), + .flags = PARSE_OPT_NONEG, + }, OPT_BOOL('u', NULL, &opts.update, N_("update working tree with merge result")), OPT_CALLBACK_F(0, "exclude-per-directory", &opts, diff --git a/builtin/rebase.c b/builtin/rebase.c index d4715ed35d..2e8c4ee678 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -267,7 +267,8 @@ static int init_basic_state(struct replay_opts *opts, const char *head_name, { FILE *interactive; - if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir())) + if (!is_directory(merge_dir()) && + safe_create_dir_in_gitdir(the_repository, merge_dir())) return error_errno(_("could not create temporary %s"), merge_dir()); refs_delete_reflog(get_main_ref_store(the_repository), "REBASE_HEAD"); @@ -925,7 +926,7 @@ static void fill_branch_base(struct rebase_options *options, options->orig_head, &merge_bases) < 0) exit(128); if (!merge_bases || merge_bases->next) - oidcpy(branch_base, null_oid()); + oidcpy(branch_base, null_oid(the_hash_algo)); else oidcpy(branch_base, &merge_bases->item->object.oid); @@ -1122,9 +1123,15 @@ int cmd_rebase(int argc, OPT_BIT('v', "verbose", &options.flags, N_("display a diffstat of what changed upstream"), REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), - {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, - N_("do not show diffstat of what changed upstream"), - PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + { + .type = OPTION_NEGBIT, + .short_name = 'n', + .long_name = "no-stat", + .value = &options.flags, + .help = N_("do not show diffstat of what changed upstream"), + .flags = PARSE_OPT_NOARG, + .defval = REBASE_DIFFSTAT, + }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by trailer to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -1190,9 +1197,16 @@ int cmd_rebase(int argc, OPT_BOOL(0, "update-refs", &options.update_refs, N_("update branches that point to commits " "that are being rebased")), - { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), - N_("GPG-sign commits"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &gpg_sign, + .argh = N_("key-id"), + .help = N_("GPG-sign commits"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, OPT_AUTOSTASH(&options.autostash), OPT_STRING_LIST('x', "exec", &options.exec, N_("exec"), N_("add exec lines after each commit of the " @@ -1575,11 +1589,6 @@ int cmd_rebase(int argc, options.default_backend); } - if (options.type == REBASE_MERGE && - !options.strategy && - getenv("GIT_TEST_MERGE_ALGORITHM")) - options.strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM")); - switch (options.type) { case REBASE_MERGE: options.state_dir = merge_dir(); @@ -1843,7 +1852,7 @@ int cmd_rebase(int argc, strbuf_addf(&msg, "%s (start): checkout %s", options.reflog_action, options.onto_name); ropts.oid = &options.onto->object.oid; - ropts.orig_head = &options.orig_head->object.oid, + ropts.orig_head = &options.orig_head->object.oid; ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK; ropts.head_msg = msg.buf; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 7b28fc9df6..be314879e8 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -31,8 +31,9 @@ #include "tmp-objdir.h" #include "oidset.h" #include "packfile.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "protocol.h" #include "commit-reach.h" @@ -363,7 +364,7 @@ static void write_head_info(void) strvec_clear(&excludes_vector); if (!sent_capabilities) - show_ref("capabilities^{}", null_oid()); + show_ref("capabilities^{}", null_oid(the_hash_algo)); advertise_shallow_grafts(1); diff --git a/builtin/reflog.c b/builtin/reflog.c index 95f264989b..3acaf3e32c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -29,6 +29,9 @@ #define BUILTIN_REFLOG_EXISTS_USAGE \ N_("git reflog exists <ref>") +#define BUILTIN_REFLOG_DROP_USAGE \ + N_("git reflog drop [--all [--single-worktree] | <refs>...]") + static const char *const reflog_show_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, NULL, @@ -54,18 +57,21 @@ static const char *const reflog_exists_usage[] = { NULL, }; +static const char *const reflog_drop_usage[] = { + BUILTIN_REFLOG_DROP_USAGE, + NULL, +}; + static const char *const reflog_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, BUILTIN_REFLOG_LIST_USAGE, BUILTIN_REFLOG_EXPIRE_USAGE, BUILTIN_REFLOG_DELETE_USAGE, + BUILTIN_REFLOG_DROP_USAGE, BUILTIN_REFLOG_EXISTS_USAGE, NULL }; -static timestamp_t default_reflog_expire; -static timestamp_t default_reflog_expire_unreachable; - struct worktree_reflogs { struct worktree *worktree; struct string_list reflogs; @@ -91,131 +97,19 @@ static int collect_reflog(const char *ref, void *cb_data) return 0; } -static struct reflog_expire_cfg { - struct reflog_expire_cfg *next; - timestamp_t expire_total; - timestamp_t expire_unreachable; - char pattern[FLEX_ARRAY]; -} *reflog_expire_cfg, **reflog_expire_cfg_tail; - -static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) -{ - struct reflog_expire_cfg *ent; - - if (!reflog_expire_cfg_tail) - reflog_expire_cfg_tail = &reflog_expire_cfg; - - for (ent = reflog_expire_cfg; ent; ent = ent->next) - if (!xstrncmpz(ent->pattern, pattern, len)) - return ent; - - FLEX_ALLOC_MEM(ent, pattern, pattern, len); - *reflog_expire_cfg_tail = ent; - reflog_expire_cfg_tail = &(ent->next); - return ent; -} - -/* expiry timer slot */ -#define EXPIRE_TOTAL 01 -#define EXPIRE_UNREACH 02 - -static int reflog_expire_config(const char *var, const char *value, - const struct config_context *ctx, void *cb) -{ - const char *pattern, *key; - size_t pattern_len; - timestamp_t expire; - int slot; - struct reflog_expire_cfg *ent; - - if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0) - return git_default_config(var, value, ctx, cb); - - if (!strcmp(key, "reflogexpire")) { - slot = EXPIRE_TOTAL; - if (git_config_expiry_date(&expire, var, value)) - return -1; - } else if (!strcmp(key, "reflogexpireunreachable")) { - slot = EXPIRE_UNREACH; - if (git_config_expiry_date(&expire, var, value)) - return -1; - } else - return git_default_config(var, value, ctx, cb); - - if (!pattern) { - switch (slot) { - case EXPIRE_TOTAL: - default_reflog_expire = expire; - break; - case EXPIRE_UNREACH: - default_reflog_expire_unreachable = expire; - break; - } - return 0; - } - - ent = find_cfg_ent(pattern, pattern_len); - if (!ent) - return -1; - switch (slot) { - case EXPIRE_TOTAL: - ent->expire_total = expire; - break; - case EXPIRE_UNREACH: - ent->expire_unreachable = expire; - break; - } - return 0; -} - -static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref) -{ - struct reflog_expire_cfg *ent; - - if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH)) - return; /* both given explicitly -- nothing to tweak */ - - for (ent = reflog_expire_cfg; ent; ent = ent->next) { - if (!wildmatch(ent->pattern, ref, 0)) { - if (!(cb->explicit_expiry & EXPIRE_TOTAL)) - cb->expire_total = ent->expire_total; - if (!(cb->explicit_expiry & EXPIRE_UNREACH)) - cb->expire_unreachable = ent->expire_unreachable; - return; - } - } - - /* - * If unconfigured, make stash never expire - */ - if (!strcmp(ref, "refs/stash")) { - if (!(cb->explicit_expiry & EXPIRE_TOTAL)) - cb->expire_total = 0; - if (!(cb->explicit_expiry & EXPIRE_UNREACH)) - cb->expire_unreachable = 0; - return; - } - - /* Nothing matched -- use the default value */ - if (!(cb->explicit_expiry & EXPIRE_TOTAL)) - cb->expire_total = default_reflog_expire; - if (!(cb->explicit_expiry & EXPIRE_UNREACH)) - cb->expire_unreachable = default_reflog_expire_unreachable; -} - static int expire_unreachable_callback(const struct option *opt, const char *arg, int unset) { - struct cmd_reflog_expire_cb *cmd = opt->value; + struct reflog_expire_options *opts = opt->value; BUG_ON_OPT_NEG(unset); - if (parse_expiry_date(arg, &cmd->expire_unreachable)) + if (parse_expiry_date(arg, &opts->expire_unreachable)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); - cmd->explicit_expiry |= EXPIRE_UNREACH; + opts->explicit_expiry |= REFLOG_EXPIRE_UNREACH; return 0; } @@ -223,15 +117,15 @@ static int expire_total_callback(const struct option *opt, const char *arg, int unset) { - struct cmd_reflog_expire_cb *cmd = opt->value; + struct reflog_expire_options *opts = opt->value; BUG_ON_OPT_NEG(unset); - if (parse_expiry_date(arg, &cmd->expire_total)) + if (parse_expiry_date(arg, &opts->expire_total)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); - cmd->explicit_expiry |= EXPIRE_TOTAL; + opts->explicit_expiry |= REFLOG_EXPIRE_TOTAL; return 0; } @@ -276,8 +170,8 @@ static int cmd_reflog_list(int argc, const char **argv, const char *prefix, static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - struct cmd_reflog_expire_cb cmd = { 0 }; timestamp_t now = time(NULL); + struct reflog_expire_options opts = REFLOG_EXPIRE_OPTIONS_INIT(now); int i, status, do_all, single_worktree = 0; unsigned int flags = 0; int verbose = 0; @@ -292,15 +186,15 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, N_("update the reference to the value of the top reflog entry"), EXPIRE_REFLOGS_UPDATE_REF), OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")), - OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"), + OPT_CALLBACK_F(0, "expire", &opts, N_("timestamp"), N_("prune entries older than the specified time"), PARSE_OPT_NONEG, expire_total_callback), - OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"), + OPT_CALLBACK_F(0, "expire-unreachable", &opts, N_("timestamp"), N_("prune entries older than <time> that are not reachable from the current tip of the branch"), PARSE_OPT_NONEG, expire_unreachable_callback), - OPT_BOOL(0, "stale-fix", &cmd.stalefix, + OPT_BOOL(0, "stale-fix", &opts.stalefix, N_("prune any reflog entries that point to broken commits")), OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")), OPT_BOOL(0, "single-worktree", &single_worktree, @@ -308,17 +202,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, OPT_END() }; - default_reflog_expire_unreachable = now - 30 * 24 * 3600; - default_reflog_expire = now - 90 * 24 * 3600; - git_config(reflog_expire_config, NULL); + git_config(reflog_expire_config, &opts); save_commit_buffer = 0; do_all = status = 0; - cmd.explicit_expiry = 0; - cmd.expire_total = default_reflog_expire; - cmd.expire_unreachable = default_reflog_expire_unreachable; - argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0); if (verbose) @@ -329,7 +217,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, * even in older repository. We cannot trust what's reachable * from reflog if the repository was pruned with older git. */ - if (cmd.stalefix) { + if (opts.stalefix) { struct rev_info revs; repo_init_revisions(the_repository, &revs, prefix); @@ -363,11 +251,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, for_each_string_list_item(item, &collected.reflogs) { struct expire_reflog_policy_cb cb = { - .cmd = cmd, + .opts = opts, .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), }; - set_reflog_expiry_param(&cb.cmd, item->string); + reflog_expire_options_set_refname(&cb.opts, item->string); status |= refs_reflog_expire(get_main_ref_store(the_repository), item->string, flags, reflog_expiry_prepare, @@ -380,13 +268,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, for (i = 0; i < argc; i++) { char *ref; - struct expire_reflog_policy_cb cb = { .cmd = cmd }; + struct expire_reflog_policy_cb cb = { .opts = opts }; if (!repo_dwim_log(the_repository, argv[i], strlen(argv[i]), NULL, &ref)) { - status |= error(_("%s points nowhere!"), argv[i]); + status |= error(_("reflog could not be found: '%s'"), argv[i]); continue; } - set_reflog_expiry_param(&cb.cmd, ref); + reflog_expire_options_set_refname(&cb.opts, ref); status |= refs_reflog_expire(get_main_ref_store(the_repository), ref, flags, reflog_expiry_prepare, @@ -449,10 +337,64 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix, refname); } +static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + int ret = 0, do_all = 0, single_worktree = 0; + const struct option options[] = { + OPT_BOOL(0, "all", &do_all, N_("drop the reflogs of all references")), + OPT_BOOL(0, "single-worktree", &single_worktree, + N_("drop reflogs from the current worktree only")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, reflog_drop_usage, 0); + + if (argc && do_all) + usage(_("references specified along with --all")); + + if (do_all) { + struct worktree_reflogs collected = { + .reflogs = STRING_LIST_INIT_DUP, + }; + struct string_list_item *item; + struct worktree **worktrees, **p; + + worktrees = get_worktrees(); + for (p = worktrees; *p; p++) { + if (single_worktree && !(*p)->is_current) + continue; + collected.worktree = *p; + refs_for_each_reflog(get_worktree_ref_store(*p), + collect_reflog, &collected); + } + free_worktrees(worktrees); + + for_each_string_list_item(item, &collected.reflogs) + ret |= refs_delete_reflog(get_main_ref_store(repo), + item->string); + string_list_clear(&collected.reflogs, 0); + + return ret; + } + + for (int i = 0; i < argc; i++) { + char *ref; + if (!repo_dwim_log(repo, argv[i], strlen(argv[i]), NULL, &ref)) { + ret |= error(_("reflog could not be found: '%s'"), argv[i]); + continue; + } + + ret |= refs_delete_reflog(get_main_ref_store(repo), ref); + free(ref); + } + + return ret; +} + /* * main "reflog" */ - int cmd_reflog(int argc, const char **argv, const char *prefix, @@ -465,6 +407,7 @@ int cmd_reflog(int argc, OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), + OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), OPT_END() }; diff --git a/builtin/remote.c b/builtin/remote.c index 1b7aad8838..b4baa34e66 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -14,7 +14,7 @@ #include "rebase.h" #include "refs.h" #include "refspec.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strvec.h" #include "commit-reach.h" #include "progress.h" @@ -511,7 +511,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), - fetch_map, 1); + fetch_map, REMOTE_GUESS_HEAD_ALL); for (ref = matches; ref; ref = ref->next) string_list_append(&states->heads, abbrev_branch(ref->name)); diff --git a/builtin/repack.c b/builtin/repack.c index f3330ade7b..59214dbdfd 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -17,7 +17,7 @@ #include "midx.h" #include "packfile.h" #include "prune-packed.h" -#include "object-store-ll.h" +#include "object-store.h" #include "promisor-remote.h" #include "shallow.h" #include "pack.h" @@ -1171,11 +1171,11 @@ int cmd_repack(int argc, PACK_CRUFT), OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"), N_("with --cruft, expire objects older than this")), - OPT_MAGNITUDE(0, "combine-cruft-below-size", - &combine_cruft_below_size, - N_("with --cruft, only repack cruft packs smaller than this")), - OPT_MAGNITUDE(0, "max-cruft-size", &cruft_po_args.max_pack_size, - N_("with --cruft, limit the size of new cruft packs")), + OPT_UNSIGNED(0, "combine-cruft-below-size", + &combine_cruft_below_size, + N_("with --cruft, only repack cruft packs smaller than this")), + OPT_UNSIGNED(0, "max-cruft-size", &cruft_po_args.max_pack_size, + N_("with --cruft, limit the size of new cruft packs")), OPT_BOOL('d', NULL, &delete_redundant, N_("remove redundant packs, and run git-prune-packed")), OPT_BOOL('f', NULL, &po_args.no_reuse_delta, @@ -1205,8 +1205,8 @@ int cmd_repack(int argc, N_("limits the maximum delta depth")), OPT_STRING(0, "threads", &opt_threads, N_("n"), N_("limits the maximum number of threads")), - OPT_MAGNITUDE(0, "max-pack-size", &po_args.max_pack_size, - N_("maximum size of each packfile")), + OPT_UNSIGNED(0, "max-pack-size", &po_args.max_pack_size, + N_("maximum size of each packfile")), OPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, N_("repack objects in packs marked with .keep")), diff --git a/builtin/replace.c b/builtin/replace.c index 15ec0922ce..48c7c6a2d5 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -19,7 +19,7 @@ #include "run-command.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "tag.h" #include "wildmatch.h" @@ -305,7 +305,7 @@ static int import_object(struct object_id *oid, enum object_type type, strbuf_release(&result); } else { struct stat st; - int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; + int flags = INDEX_FORMAT_CHECK | INDEX_WRITE_OBJECT; if (fstat(fd, &st) < 0) { error_errno(_("unable to fstat %s"), filename); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index bb26bee0d4..c4cd4ed5c8 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -14,8 +14,9 @@ #include "object.h" #include "object-name.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pack-bitmap.h" +#include "parse-options.h" #include "log-tree.h" #include "graph.h" #include "bisect.h" @@ -64,6 +65,7 @@ static const char rev_list_usage[] = " --abbrev-commit\n" " --left-right\n" " --count\n" +" -z\n" " special purpose:\n" " --bisect\n" " --bisect-vars\n" @@ -96,6 +98,9 @@ static int arg_show_object_names = 1; #define DEFAULT_OIDSET_SIZE (16*1024) +static char line_term = '\n'; +static char info_term = ' '; + static int show_disk_usage; static off_t total_disk_usage; static int human_readable; @@ -131,24 +136,37 @@ static void print_missing_object(struct missing_objects_map_entry *entry, { struct strbuf sb = STRBUF_INIT; + if (line_term) + printf("?%s", oid_to_hex(&entry->entry.oid)); + else + printf("%s%cmissing=yes", oid_to_hex(&entry->entry.oid), + info_term); + if (!print_missing_info) { - printf("?%s\n", oid_to_hex(&entry->entry.oid)); + putchar(line_term); return; } if (entry->path && *entry->path) { - struct strbuf path = STRBUF_INIT; + strbuf_addf(&sb, "%cpath=", info_term); - strbuf_addstr(&sb, " path="); - quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP); - strbuf_addbuf(&sb, &path); + if (line_term) { + struct strbuf path = STRBUF_INIT; - strbuf_release(&path); + quote_path(entry->path, NULL, &path, QUOTE_PATH_QUOTE_SP); + strbuf_addbuf(&sb, &path); + + strbuf_release(&path); + } else { + strbuf_addstr(&sb, entry->path); + } } if (entry->type) - strbuf_addf(&sb, " type=%s", type_name(entry->type)); + strbuf_addf(&sb, "%ctype=%s", info_term, type_name(entry->type)); + + fwrite(sb.buf, sizeof(char), sb.len, stdout); + putchar(line_term); - printf("?%s%s\n", oid_to_hex(&entry->entry.oid), sb.buf); strbuf_release(&sb); } @@ -235,13 +253,18 @@ static void show_commit(struct commit *commit, void *data) fputs(info->header_prefix, stdout); if (revs->include_header) { - if (!revs->graph) + if (!revs->graph && line_term) fputs(get_revision_mark(revs, commit), stdout); if (revs->abbrev_commit && revs->abbrev) fputs(repo_find_unique_abbrev(the_repository, &commit->object.oid, revs->abbrev), stdout); else fputs(oid_to_hex(&commit->object.oid), stdout); + + if (!line_term) { + if (commit->object.flags & BOUNDARY) + printf("%cboundary=yes", info_term); + } } if (revs->print_parents) { struct commit_list *parents = commit->parents; @@ -263,7 +286,7 @@ static void show_commit(struct commit *commit, void *data) if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); else if (revs->include_header) - putchar('\n'); + putchar(line_term); if (revs->verbose_header) { struct strbuf buf = STRBUF_INIT; @@ -357,10 +380,19 @@ static void show_object(struct object *obj, const char *name, void *cb_data) return; } - if (arg_show_object_names) - show_object_with_name(stdout, obj, name); - else - printf("%s\n", oid_to_hex(&obj->oid)); + printf("%s", oid_to_hex(&obj->oid)); + + if (arg_show_object_names) { + if (line_term) { + putchar(info_term); + for (const char *p = name; *p && *p != '\n'; p++) + putchar(*p); + } else if (*name) { + printf("%cpath=%s", info_term, name); + } + } + + putchar(line_term); } static void show_edge(struct commit *commit) @@ -429,7 +461,8 @@ static int show_object_fast( int exclude UNUSED, uint32_t name_hash UNUSED, struct packed_git *found_pack UNUSED, - off_t found_offset UNUSED) + off_t found_offset UNUSED, + void *payload UNUSED) { fprintf(stdout, "%s\n", oid_to_hex(oid)); return 1; @@ -634,19 +667,18 @@ int cmd_rev_list(int argc, if (!strcmp(arg, "--exclude-promisor-objects")) { fetch_if_missing = 0; revs.exclude_promisor_objects = 1; - break; - } - } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (skip_prefix(arg, "--missing=", &arg)) { - if (revs.exclude_promisor_objects) - die(_("options '%s' and '%s' cannot be used together"), "--exclude-promisor-objects", "--missing"); - if (parse_missing_action_value(arg)) - break; + } else if (skip_prefix(arg, "--missing=", &arg)) { + parse_missing_action_value(arg); + } else if (!strcmp(arg, "-z")) { + line_term = '\0'; + info_term = '\0'; } } + die_for_incompatible_opt2(revs.exclude_promisor_objects, + "--exclude_promisor_objects", + arg_missing_action, "--missing"); + if (arg_missing_action) revs.do_not_die_on_missing_objects = 1; @@ -755,6 +787,20 @@ int cmd_rev_list(int argc, usage(rev_list_usage); } + + /* + * Reject options currently incompatible with -z. For some options, this + * is not an inherent limitation and support may be implemented in the + * future. + */ + if (!line_term) { + if (revs.graph || revs.verbose_header || show_disk_usage || + info.show_timestamp || info.header_prefix || bisect_list || + use_bitmap_index || revs.edge_hint || revs.left_right || + revs.cherry_mark) + die(_("-z option used with unsupported option")); + } + if (revs.commit_format != CMIT_FMT_USERFORMAT) revs.include_header = 1; if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { diff --git a/builtin/revert.c b/builtin/revert.c index aca6c293cd..e07c2217fe 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -132,8 +132,16 @@ static int run_sequencer(int argc, const char **argv, const char *prefix, OPT_STRING(0, "strategy", &strategy, N_("strategy"), N_("merge strategy")), OPT_STRVEC('X', "strategy-option", &opts->xopts, N_("option"), N_("option for merge strategy")), - { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), - N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + { + .type = OPTION_STRING, + .short_name = 'S', + .long_name = "gpg-sign", + .value = &gpg_sign, + .argh = N_("key-id"), + .help = N_("GPG sign commit"), + .flags = PARSE_OPT_OPTARG, + .defval = (intptr_t) "", + }, OPT_END() }; struct option *options = base_options; @@ -252,8 +260,6 @@ static int run_sequencer(int argc, const char **argv, const char *prefix, free(opts->strategy); opts->strategy = xstrdup_or_null(strategy); } - if (!opts->strategy && getenv("GIT_TEST_MERGE_ALGORITHM")) - opts->strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM")); free(options); if (cmd == 'q') { diff --git a/builtin/rm.c b/builtin/rm.c index 12ae086a55..a6565a69cf 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -5,7 +5,6 @@ */ #define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" #include "advice.h" @@ -40,14 +39,12 @@ static struct { } *entry; } list; -static int get_ours_cache_pos(const char *path, int pos) +static int get_ours_cache_pos(const char *path, unsigned int pos) { - int i = -pos - 1; - - while ((i < the_repository->index->cache_nr) && !strcmp(the_repository->index->cache[i]->name, path)) { - if (ce_stage(the_repository->index->cache[i]) == 2) - return i; - i++; + while ((pos < the_repository->index->cache_nr) && !strcmp(the_repository->index->cache[pos]->name, path)) { + if (ce_stage(the_repository->index->cache[pos]) == 2) + return pos; + pos++; } return -1; } @@ -58,7 +55,7 @@ static void print_error_files(struct string_list *files_list, int *errs) { if (files_list->nr) { - int i; + unsigned int i; struct strbuf err_msg = STRBUF_INIT; strbuf_addstr(&err_msg, main_msg); @@ -83,7 +80,7 @@ static void submodules_absorb_gitdir_if_needed(void) pos = index_name_pos(the_repository->index, name, strlen(name)); if (pos < 0) { - pos = get_ours_cache_pos(name, pos); + pos = get_ours_cache_pos(name, -pos - 1); if (pos < 0) continue; } @@ -131,7 +128,7 @@ static int check_local_mod(struct object_id *head, int index_only) * Skip unmerged entries except for populated submodules * that could lose history when removed. */ - pos = get_ours_cache_pos(name, pos); + pos = get_ours_cache_pos(name, -pos - 1); if (pos < 0) continue; @@ -314,7 +311,7 @@ int cmd_rm(int argc, if (pathspec_needs_expanded_index(the_repository->index, &pathspec)) ensure_full_index(the_repository->index); - for (i = 0; i < the_repository->index->cache_nr; i++) { + for (unsigned int i = 0; i < the_repository->index->cache_nr; i++) { const struct cache_entry *ce = the_repository->index->cache[i]; if (!include_sparse && diff --git a/builtin/show-branch.c b/builtin/show-branch.c index fce6b404e9..525b231d87 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -19,7 +19,7 @@ #include "date.h" #include "wildmatch.h" -static const char* show_branch_usage[] = { +static const char*const show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" " [--current] [--color[=<when>] | --no-color] [--sparse]\n" " [--more=<n> | --list | --independent | --merge-base]\n" @@ -667,9 +667,16 @@ int cmd_show_branch(int ac, N_("show remote-tracking branches")), OPT__COLOR(&showbranch_use_color, N_("color '*!+-' corresponding to the branch")), - { OPTION_INTEGER, 0, "more", &extra, N_("n"), - N_("show <n> more commits after the common ancestor"), - PARSE_OPT_OPTARG, NULL, (intptr_t)1 }, + { + .type = OPTION_INTEGER, + .long_name = "more", + .value = &extra, + .precision = sizeof(extra), + .argh = N_("n"), + .help = N_("show <n> more commits after the common ancestor"), + .flags = PARSE_OPT_OPTARG, + .defval = 1, + }, OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1), OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")), OPT_BOOL(0, "current", &with_current_branch, diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 285cd3e433..f81209f23c 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -5,7 +5,7 @@ #include "hex.h" #include "refs/refs-internal.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "object.h" #include "string-list.h" #include "parse-options.h" diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 14dcace5f8..1bf01591b2 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -9,6 +9,7 @@ #include "object-file.h" #include "object-name.h" #include "parse-options.h" +#include "path.h" #include "pathspec.h" #include "strbuf.h" #include "string-list.h" @@ -335,7 +336,7 @@ static int write_patterns_and_update(struct pattern_list *pl) sparse_filename = get_sparse_checkout_filename(); - if (safe_create_leading_directories(sparse_filename)) + if (safe_create_leading_directories(the_repository, sparse_filename)) die(_("failed to create directory for sparse-checkout file")); hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR); @@ -491,7 +492,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix, FILE *fp; /* assume we are in a fresh repo, but update the sparse-checkout file */ - if (safe_create_leading_directories(sparse_filename)) + if (safe_create_leading_directories(the_repository, sparse_filename)) die(_("unable to create leading directories of %s"), sparse_filename); fp = xfopen(sparse_filename, "w"); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c1a8029714..53da2116dd 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -28,7 +28,7 @@ #include "diff.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" @@ -78,7 +78,7 @@ static int get_default_remote_submodule(const char *module_path, char **default_ int ret; if (repo_submodule_init(&subrepo, the_repository, module_path, - null_oid()) < 0) + null_oid(the_hash_algo)) < 0) return die_message(_("could not get a repository handle for submodule '%s'"), module_path); ret = repo_get_default_remote(&subrepo, default_remote); @@ -308,7 +308,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, displaypath = get_submodule_displaypath(path, info->prefix, info->super_prefix); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("No url found for submodule path '%s' in .gitmodules"), @@ -468,7 +468,7 @@ static void init_submodule(const char *path, const char *prefix, displaypath = get_submodule_displaypath(path, prefix, super_prefix); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("No url found for submodule path '%s' in .gitmodules"), @@ -645,14 +645,14 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, if (validate_submodule_path(path) < 0) exit(128); - if (!submodule_from_path(the_repository, null_oid(), path)) + if (!submodule_from_path(the_repository, null_oid(the_hash_algo), path)) die(_("no submodule mapping found in .gitmodules for path '%s'"), path); displaypath = get_submodule_displaypath(path, prefix, super_prefix); if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) { - print_status(flags, 'U', path, null_oid(), displaypath); + print_status(flags, 'U', path, null_oid(the_hash_algo), displaypath); goto cleanup; } @@ -912,7 +912,7 @@ static void generate_submodule_summary(struct summary_cb *info, struct strbuf errmsg = STRBUF_INIT; int total_commits = -1; - if (!info->cached && oideq(&p->oid_dst, null_oid())) { + if (!info->cached && oideq(&p->oid_dst, null_oid(the_hash_algo))) { if (S_ISGITLINK(p->mod_dst)) { struct ref_store *refs = repo_get_submodule_ref_store(the_repository, p->sm_path); @@ -1051,7 +1051,7 @@ static void prepare_submodule_summary(struct summary_cb *info, if (info->for_status && p->status != 'A' && (sub = submodule_from_path(the_repository, - null_oid(), p->sm_path))) { + null_oid(the_hash_algo), p->sm_path))) { char *config_key = NULL; const char *value; int ignore_all = 0; @@ -1259,7 +1259,7 @@ static void sync_submodule(const char *path, const char *prefix, if (validate_submodule_path(path) < 0) exit(128); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (sub && sub->url) { if (starts_with_dot_dot_slash(sub->url) || @@ -1404,7 +1404,7 @@ static void deinit_submodule(const char *path, const char *prefix, if (validate_submodule_path(path) < 0) exit(128); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub || !sub->name) goto cleanup; @@ -1739,7 +1739,7 @@ static int clone_submodule(const struct module_clone_data *clone_data, !is_empty_dir(clone_data_path)) die(_("directory not empty: '%s'"), clone_data_path); - if (safe_create_leading_directories_const(sm_gitdir) < 0) + if (safe_create_leading_directories_const(the_repository, sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); prepare_possible_alternates(clone_data->name, reference); @@ -1800,7 +1800,7 @@ static int clone_submodule(const struct module_clone_data *clone_data, if (clone_data->require_init && !stat(clone_data_path, &st) && !is_empty_dir(clone_data_path)) die(_("directory not empty: '%s'"), clone_data_path); - if (safe_create_leading_directories_const(clone_data_path) < 0) + if (safe_create_leading_directories_const(the_repository, clone_data_path) < 0) die(_("could not create directory '%s'"), clone_data_path); path = xstrfmt("%s/index", sm_gitdir); unlink_or_warn(path); @@ -1929,7 +1929,7 @@ static int determine_submodule_update_strategy(struct repository *r, enum submodule_update_type update, struct submodule_update_strategy *out) { - const struct submodule *sub = submodule_from_path(r, null_oid(), path); + const struct submodule *sub = submodule_from_path(r, null_oid(the_hash_algo), path); char *key; const char *val; int ret; @@ -2089,7 +2089,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, goto cleanup; } - sub = submodule_from_path(the_repository, null_oid(), ce->name); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), ce->name); if (!sub) { next_submodule_warn_missing(suc, out, displaypath); @@ -2485,7 +2485,7 @@ static int remote_submodule_branch(const char *path, const char **branch) char *key; *branch = NULL; - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) return die_message(_("could not initialize submodule at path '%s'"), path); @@ -2531,7 +2531,7 @@ static int ensure_core_worktree(const char *path) const char *cw; struct repository subrepo; - if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) + if (repo_submodule_init(&subrepo, the_repository, path, null_oid(the_hash_algo))) return die_message(_("could not get a repository handle for submodule '%s'"), path); @@ -2644,7 +2644,7 @@ static int update_submodule(struct update_data *update_data) return ret; if (update_data->just_cloned) - oidcpy(&update_data->suboid, null_oid()); + oidcpy(&update_data->suboid, null_oid(the_hash_algo)); else if (repo_resolve_gitlink_ref(the_repository, update_data->sm_path, "HEAD", &update_data->suboid)) return die_message(_("Unable to find current revision in submodule path '%s'"), @@ -2697,8 +2697,8 @@ static int update_submodule(struct update_data *update_data) struct update_data next = *update_data; next.prefix = NULL; - oidcpy(&next.oid, null_oid()); - oidcpy(&next.suboid, null_oid()); + oidcpy(&next.oid, null_oid(the_hash_algo)); + oidcpy(&next.suboid, null_oid(the_hash_algo)); cp.dir = update_data->sm_path; cp.git_cmd = 1; @@ -3057,7 +3057,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix, if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1])) usage_with_options(usage, options); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@ -3113,7 +3113,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix, if (argc != 1 || !(path = argv[0])) usage_with_options(usage, options); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), diff --git a/builtin/tag.c b/builtin/tag.c index d3e0943b73..4742b27d16 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,8 +17,9 @@ #include "gettext.h" #include "hex.h" #include "refs.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "tag.h" #include "parse-options.h" @@ -172,7 +173,7 @@ static int do_sign(struct strbuf *buffer, struct object_id **compat_oid, if (compat) { const struct git_hash_algo *algo = the_repository->hash_algo; - if (convert_object_file(&compat_buf, algo, compat, + if (convert_object_file(the_repository ,&compat_buf, algo, compat, buffer->buf, buffer->len, OBJ_TAG, 1)) goto out; if (sign_buffer(&compat_buf, &compat_sig, keyid)) @@ -479,9 +480,16 @@ int cmd_tag(int argc, int edit_flag = 0; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), - { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), - N_("print <n> lines of each tag message"), - PARSE_OPT_OPTARG, NULL, 1 }, + { + .type = OPTION_INTEGER, + .short_name = 'n', + .value = &filter.lines, + .precision = sizeof(filter.lines), + .argh = N_("n"), + .help = N_("print <n> lines of each tag message"), + .flags = PARSE_OPT_OPTARG, + .defval = 1, + }, OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'), @@ -513,9 +521,14 @@ int cmd_tag(int argc, N_("do not output a newline after empty formatted refs")), OPT_REF_SORT(&sorting_options), { - OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), - N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, - parse_opt_object_name, (intptr_t) "HEAD" + .type = OPTION_CALLBACK, + .long_name = "points-at", + .value = &filter.points_at, + .argh = N_("object"), + .help = N_("print only tags of the object"), + .flags = PARSE_OPT_LASTARG_DEFAULT, + .callback = parse_opt_object_name, + .defval = (intptr_t) "HEAD", }, OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index fb5fcbc40a..e33acfc4ee 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -2,8 +2,9 @@ #include "builtin.h" #include "config.h" #include "hex.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" static char *create_temp_file(struct object_id *oid) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index c5a6dca856..661be789f1 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -8,7 +8,8 @@ #include "gettext.h" #include "git-zlib.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-file.h" +#include "object-store.h" #include "object.h" #include "delta.h" #include "pack.h" @@ -505,7 +506,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, * has not been resolved yet. */ oidclr(&obj_list[nr].oid, the_repository->hash_algo); - add_delta_to_list(nr, null_oid(), base_offset, + add_delta_to_list(nr, null_oid(the_hash_algo), base_offset, delta_data, delta_size); return; } @@ -553,7 +554,8 @@ static void unpack_one(unsigned nr) switch (type) { case OBJ_BLOB: - if (!dry_run && size > big_file_threshold) { + if (!dry_run && + size > repo_settings_get_big_file_threshold(the_repository)) { stream_blob(size, nr); return; } diff --git a/builtin/update-index.c b/builtin/update-index.c index b2f6b1a3fb..538b619ba4 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -304,7 +304,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len ce->ce_mode = ce_mode_from_stat(old, st->st_mode); if (index_path(the_repository->index, &ce->oid, path, st, - info_only ? 0 : HASH_WRITE_OBJECT)) { + info_only ? 0 : INDEX_WRITE_OBJECT)) { discard_cache_entry(ce); return -1; } @@ -964,29 +964,51 @@ int cmd_update_index(int argc, N_("like --refresh, but ignore assume-unchanged setting"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, really_refresh_callback), - {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, - N_("<mode>,<object>,<path>"), - N_("add the specified entry to the index"), - PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ - PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, - NULL, 0, - cacheinfo_callback}, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .long_name = "cacheinfo", + .argh = N_("<mode>,<object>,<path>"), + .help = N_("add the specified entry to the index"), + .flags = PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + .ll_callback = cacheinfo_callback, + }, OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x", N_("override the executable bit of the listed files"), PARSE_OPT_NONEG, chmod_callback), - {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, - N_("mark files as \"not changing\""), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, - {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL, - N_("clear assumed-unchanged bit"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, - {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL, - N_("mark files as \"index-only\""), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, - {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, - N_("clear skip-worktree bit"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + { + .type = OPTION_SET_INT, + .long_name = "assume-unchanged", + .value = &mark_valid_only, + .help = N_("mark files as \"not changing\""), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = MARK_FLAG, + }, + { + .type = OPTION_SET_INT, + .long_name = "no-assume-unchanged", + .value = &mark_valid_only, + .help = N_("clear assumed-unchanged bit"), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = UNMARK_FLAG, + }, + { + .type = OPTION_SET_INT, + .long_name = "skip-worktree", + .value = &mark_skip_worktree_only, + .help = N_("mark files as \"index-only\""), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = MARK_FLAG, + }, + { + .type = OPTION_SET_INT, + .long_name = "no-skip-worktree", + .value = &mark_skip_worktree_only, + .help = N_("clear skip-worktree bit"), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = UNMARK_FLAG, + }, OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, N_("do not touch index-only entries")), OPT_SET_INT(0, "info-only", &info_only, @@ -995,22 +1017,39 @@ int cmd_update_index(int argc, N_("remove named paths even if present in worktree"), 1), OPT_BOOL('z', NULL, &nul_term_line, N_("with --stdin: input lines are terminated by null bytes")), - {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, - N_("read list of paths to be updated from standard input"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, - NULL, 0, stdin_callback}, - {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, - N_("add entries from standard input to the index"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, - NULL, 0, stdin_cacheinfo_callback}, - {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, - N_("repopulate stages #2 and #3 for the listed paths"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, - NULL, 0, unresolve_callback}, - {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, - N_("only update entries that differ from HEAD"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, - NULL, 0, reupdate_callback}, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .long_name = "stdin", + .value = &read_from_stdin, + .help = N_("read list of paths to be updated from standard input"), + .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, + .ll_callback = stdin_callback, + }, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .long_name = "index-info", + .value = &nul_term_line, + .help = N_("add entries from standard input to the index"), + .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, + .ll_callback = stdin_cacheinfo_callback, + }, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .long_name = "unresolve", + .value = &has_errors, + .help = N_("repopulate stages #2 and #3 for the listed paths"), + .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, + .ll_callback = unresolve_callback, + }, + { + .type = OPTION_LOWLEVEL_CALLBACK, + .short_name = 'g', + .long_name = "again", + .value = &has_errors, + .help = N_("only update entries that differ from HEAD"), + .flags = PARSE_OPT_NONEG | PARSE_OPT_NOARG, + .ll_callback = reupdate_callback, + }, OPT_BIT(0, "ignore-missing", &refresh_args.flags, N_("ignore files missing from worktree"), REFRESH_IGNORE_MISSING), @@ -1036,12 +1075,22 @@ int cmd_update_index(int argc, N_("write out the index even if is not flagged as changed"), 1), OPT_BOOL(0, "fsmonitor", &fsmonitor, N_("enable or disable file system monitor")), - {OPTION_SET_INT, 0, "fsmonitor-valid", &mark_fsmonitor_only, NULL, - N_("mark files as fsmonitor valid"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, - {OPTION_SET_INT, 0, "no-fsmonitor-valid", &mark_fsmonitor_only, NULL, - N_("clear fsmonitor valid bit"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + { + .type = OPTION_SET_INT, + .long_name = "fsmonitor-valid", + .value = &mark_fsmonitor_only, + .help = N_("mark files as fsmonitor valid"), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = MARK_FLAG, + }, + { + .type = OPTION_SET_INT, + .long_name = "no-fsmonitor-valid", + .value = &mark_fsmonitor_only, + .help = N_("clear fsmonitor valid bit"), + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = UNMARK_FLAG, + }, OPT_END() }; diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 1d541e13ad..2b1e336ba1 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -5,6 +5,7 @@ #include "config.h" #include "gettext.h" #include "hash.h" +#include "hex.h" #include "refs.h" #include "object-name.h" #include "parse-options.h" @@ -13,7 +14,7 @@ static const char * const git_update_ref_usage[] = { N_("git update-ref [<options>] -d <refname> [<old-oid>]"), N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"), - N_("git update-ref [<options>] --stdin [-z]"), + N_("git update-ref [<options>] --stdin [-z] [--batch-updates]"), NULL }; @@ -503,7 +504,7 @@ static void parse_cmd_symref_verify(struct ref_transaction *transaction, */ old_target = parse_next_refname(&next); if (!old_target) - oidcpy(&old_oid, null_oid()); + oidcpy(&old_oid, null_oid(the_hash_algo)); if (*next != line_termination) die("symref-verify %s: extra input: %s", refname, next); @@ -565,6 +566,49 @@ static void parse_cmd_abort(struct ref_transaction *transaction, report_ok("abort"); } +static void print_rejected_refs(const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, + const char *old_target, + const char *new_target, + enum ref_transaction_error err, + void *cb_data UNUSED) +{ + struct strbuf sb = STRBUF_INIT; + const char *reason = ""; + + switch (err) { + case REF_TRANSACTION_ERROR_NAME_CONFLICT: + reason = "refname conflict"; + break; + case REF_TRANSACTION_ERROR_CREATE_EXISTS: + reason = "reference already exists"; + break; + case REF_TRANSACTION_ERROR_NONEXISTENT_REF: + reason = "reference does not exist"; + break; + case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE: + reason = "incorrect old value provided"; + break; + case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE: + reason = "invalid new value provided"; + break; + case REF_TRANSACTION_ERROR_EXPECTED_SYMREF: + reason = "expected symref but found regular ref"; + break; + default: + reason = "unkown failure"; + } + + strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, + new_oid ? oid_to_hex(new_oid) : new_target, + old_oid ? oid_to_hex(old_oid) : old_target, + reason); + + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); +} + static void parse_cmd_commit(struct ref_transaction *transaction, const char *next, const char *end UNUSED) { @@ -573,6 +617,10 @@ static void parse_cmd_commit(struct ref_transaction *transaction, die("commit: extra input: %s", next); if (ref_transaction_commit(transaction, &error)) die("commit: %s", error.buf); + + ref_transaction_for_each_rejected_update(transaction, + print_rejected_refs, NULL); + report_ok("commit"); ref_transaction_free(transaction); } @@ -609,7 +657,7 @@ static const struct parse_cmd { { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED }, }; -static void update_refs_stdin(void) +static void update_refs_stdin(unsigned int flags) { struct strbuf input = STRBUF_INIT, err = STRBUF_INIT; enum update_refs_state state = UPDATE_REFS_OPEN; @@ -617,7 +665,7 @@ static void update_refs_stdin(void) int i, j; transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), - 0, &err); + flags, &err); if (!transaction) die("%s", err.buf); @@ -685,7 +733,7 @@ static void update_refs_stdin(void) */ state = cmd->state; transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), - 0, &err); + flags, &err); if (!transaction) die("%s", err.buf); @@ -701,6 +749,8 @@ static void update_refs_stdin(void) /* Commit by default if no transaction was requested. */ if (ref_transaction_commit(transaction, &err)) die("%s", err.buf); + ref_transaction_for_each_rejected_update(transaction, + print_rejected_refs, NULL); ref_transaction_free(transaction); break; case UPDATE_REFS_STARTED: @@ -727,6 +777,8 @@ int cmd_update_ref(int argc, struct object_id oid, oldoid; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; int create_reflog = 0; + unsigned int flags = 0; + struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), @@ -735,6 +787,8 @@ int cmd_update_ref(int argc, OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")), + OPT_BIT('0', "batch-updates", &flags, N_("batch reference updates"), + REF_TRANSACTION_ALLOW_FAILURE), OPT_END(), }; @@ -756,8 +810,10 @@ int cmd_update_ref(int argc, usage_with_options(git_update_ref_usage, options); if (end_null) line_termination = '\0'; - update_refs_stdin(); + update_refs_stdin(flags); return 0; + } else if (flags & REF_TRANSACTION_ALLOW_FAILURE) { + die("--batch-updates can only be used with --stdin"); } if (end_null) diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index d7467290a8..ba702d30ef 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -20,8 +20,8 @@ int cmd_update_server_info(int argc, OPT_END() }; - if (repo) - repo_config(repo, git_default_config, NULL); + repo_config(repo, git_default_config, NULL); + argc = parse_options(argc, argv, prefix, options, update_server_info_usage, 0); if (argc > 0) diff --git a/builtin/worktree.c b/builtin/worktree.c index 48448a8355..88a36ea9f8 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -348,7 +348,7 @@ static void copy_sparse_checkout(const char *worktree_git_dir) char *to_file = xstrfmt("%s/info/sparse-checkout", worktree_git_dir); if (file_exists(from_file)) { - if (safe_create_leading_directories(to_file) || + if (safe_create_leading_directories(the_repository, to_file) || copy_file(to_file, from_file, 0666)) error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"), from_file, to_file); @@ -367,7 +367,7 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir) struct config_set cs = { { 0 } }; int bare; - if (safe_create_leading_directories(to_file) || + if (safe_create_leading_directories(the_repository, to_file) || copy_file(to_file, from_file, 0666)) { error(_("failed to copy worktree config from '%s' to '%s'"), from_file, to_file); @@ -466,7 +466,7 @@ static int add_worktree(const char *path, const char *refname, name = sb_name.buf; repo_git_path_replace(the_repository, &sb_repo, "worktrees/%s", name); len = sb_repo.len; - if (safe_create_leading_directories_const(sb_repo.buf)) + if (safe_create_leading_directories_const(the_repository, sb_repo.buf)) die_errno(_("could not create leading directories of '%s'"), sb_repo.buf); @@ -498,7 +498,7 @@ static int add_worktree(const char *path, const char *refname, write_file(sb.buf, _("initializing")); strbuf_addf(&sb_git, "%s/.git", path); - if (safe_create_leading_directories_const(sb_git.buf)) + if (safe_create_leading_directories_const(the_repository, sb_git.buf)) die_errno(_("could not create leading directories of '%s'"), sb_git.buf); junk_work_tree = xstrdup(path); @@ -578,7 +578,7 @@ done: strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); strvec_pushl(&opt.args, - oid_to_hex(null_oid()), + oid_to_hex(null_oid(the_hash_algo)), oid_to_hex(&commit->object.oid), "1", NULL); diff --git a/builtin/write-tree.c b/builtin/write-tree.c index 43f233e69b..5a8dc377ec 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -31,10 +31,14 @@ int cmd_write_tree(int argc, WRITE_TREE_MISSING_OK), OPT_STRING(0, "prefix", &tree_prefix, N_("<prefix>/"), N_("write tree object for a subdirectory <prefix>")), - { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL, - N_("only useful for debugging"), - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL, - WRITE_TREE_IGNORE_CACHE_TREE }, + { + .type = OPTION_BIT, + .long_name = "ignore-cache-tree", + .value = &flags, + .help = N_("only useful for debugging"), + .flags = PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, + .defval = WRITE_TREE_IGNORE_CACHE_TREE, + }, OPT_END() }; diff --git a/bulk-checkin.c b/bulk-checkin.c index a5a3395188..c31c31b18d 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -17,7 +17,7 @@ #include "tmp-objdir.h" #include "packfile.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" static int odb_transaction_nesting; @@ -43,7 +43,7 @@ static void finish_tmp_packfile(struct strbuf *basename, { char *idx_tmp_name = NULL; - stage_tmp_packfiles(the_hash_algo, basename, pack_tmp_name, + stage_tmp_packfiles(the_repository, basename, pack_tmp_name, written_list, nr_written, NULL, pack_idx_opts, hash, &idx_tmp_name); rename_tmp_packfile_idx(basename, &idx_tmp_name); @@ -167,7 +167,7 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state, unsigned char obuf[16384]; unsigned hdrlen; int status = Z_OK; - int write_object = (flags & HASH_WRITE_OBJECT); + int write_object = (flags & INDEX_WRITE_OBJECT); off_t offset = 0; git_deflate_init(&s, pack_compression_level); @@ -237,10 +237,10 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state, static void prepare_to_stream(struct bulk_checkin_packfile *state, unsigned flags) { - if (!(flags & HASH_WRITE_OBJECT) || state->f) + if (!(flags & INDEX_WRITE_OBJECT) || state->f) return; - state->f = create_tmp_packfile(&state->pack_tmp_name); + state->f = create_tmp_packfile(the_repository, &state->pack_tmp_name); reset_pack_idx_option(&state->pack_idx_opts); /* Pretend we are going to write only one object */ @@ -271,7 +271,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, git_hash_update(&ctx, obuf, header_len); /* Note: idx is non-NULL when we are writing */ - if ((flags & HASH_WRITE_OBJECT) != 0) { + if ((flags & INDEX_WRITE_OBJECT) != 0) { CALLOC_ARRAY(idx, 1); prepare_to_stream(state, flags); diff --git a/bulk-checkin.h b/bulk-checkin.h index aa7286a7b3..7246ea58dc 100644 --- a/bulk-checkin.h +++ b/bulk-checkin.h @@ -9,6 +9,21 @@ void prepare_loose_object_bulk_checkin(void); void fsync_loose_object_bulk_checkin(int fd, const char *filename); +/* + * This creates one packfile per large blob unless bulk-checkin + * machinery is "plugged". + * + * This also bypasses the usual "convert-to-git" dance, and that is on + * purpose. We could write a streaming version of the converting + * functions and insert that before feeding the data to fast-import + * (or equivalent in-core API described above). However, that is + * somewhat complicated, as we do not know the size of the filter + * result, which we need to know beforehand when writing a git object. + * Since the primary motivation for trying to stream from the working + * tree file and to avoid mmaping it in core is to deal with large + * binary blobs, they generally do not want to get any conversion, and + * callers should avoid this code path when filters are requested. + */ int index_blob_bulk_checkin(struct object_id *oid, int fd, size_t size, const char *path, unsigned flags); diff --git a/bundle-uri.c b/bundle-uri.c index 744257c49c..96d2ba726d 100644 --- a/bundle-uri.c +++ b/bundle-uri.c @@ -14,7 +14,7 @@ #include "fetch-pack.h" #include "remote.h" #include "trace2.h" -#include "object-store-ll.h" +#include "object-store.h" static struct { enum bundle_list_heuristic heuristic; @@ -7,7 +7,7 @@ #include "environment.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "repository.h" #include "object.h" #include "commit.h" @@ -384,6 +384,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) { int i; int ref_count = 0; + struct strset objects = STRSET_INIT; for (i = 0; i < revs->pending.nr; i++) { struct object_array_entry *e = revs->pending.objects + i; @@ -401,6 +402,9 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; + if (strset_contains(&objects, display_ref)) + goto skip_write_ref; + if (e->item->type == OBJ_TAG && !is_tag_in_date_range(e->item, revs)) { e->item->flags |= UNINTERESTING; @@ -423,6 +427,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) } ref_count++; + strset_add(&objects, display_ref); write_or_die(bundle_fd, oid_to_hex(&e->item->oid), the_hash_algo->hexsz); write_or_die(bundle_fd, " ", 1); write_or_die(bundle_fd, display_ref, strlen(display_ref)); @@ -431,6 +436,8 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) free(ref); } + strset_clear(&objects); + /* end header */ write_or_die(bundle_fd, "\n", 1); return ref_count; @@ -566,7 +573,6 @@ int create_bundle(struct repository *r, const char *path, */ revs.blob_objects = revs.tree_objects = 0; traverse_commit_list(&revs, write_bundle_prerequisites, NULL, &bpi); - object_array_remove_duplicates(&revs_copy.pending); /* write bundle refs */ ref_count = write_bundle_refs(bundle_fd, &revs_copy); diff --git a/cache-tree.c b/cache-tree.c index bcbcad3d61..c0e1e9ee1d 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -10,7 +10,7 @@ #include "cache-tree.h" #include "bulk-checkin.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "read-cache-ll.h" #include "replace-object.h" #include "repository.h" @@ -452,7 +452,7 @@ static int update_one(struct cache_tree *it, OBJ_TREE, &it->oid); } else if (write_object_file_flags(buffer.buf, buffer.len, OBJ_TREE, &it->oid, NULL, flags & WRITE_TREE_SILENT - ? HASH_SILENT : 0)) { + ? WRITE_OBJECT_FILE_SILENT : 0)) { strbuf_release(&buffer); return -1; } diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 0df74610d0..da19dada2c 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -31,7 +31,7 @@ alpine-*) ;; fedora-*|almalinux-*) dnf -yq update >/dev/null && - dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null + dnf -yq install shadow-utils sudo make gcc findutils diffutils perl python3 gawk gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null ;; ubuntu-*|i386/ubuntu-*|debian-*) # Required so that apt doesn't wait for user input on certain packages. @@ -66,16 +66,29 @@ ubuntu-*|i386/ubuntu-*|debian-*) mkdir --parents "$CUSTOM_PATH" wget --quiet --directory-prefix="$CUSTOM_PATH" \ - "$P4WHENCE/bin.linux26x86_64/p4d" "$P4WHENCE/bin.linux26x86_64/p4" - chmod a+x "$CUSTOM_PATH/p4d" "$CUSTOM_PATH/p4" - - wget --quiet "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" + "$P4WHENCE/bin.linux26x86_64/p4d" \ + "$P4WHENCE/bin.linux26x86_64/p4" && + chmod a+x "$CUSTOM_PATH/p4d" "$CUSTOM_PATH/p4" || { + rm -f "$CUSTOM_PATH/p4" + rm -f "$CUSTOM_PATH/p4d" + echo >&2 "P4 download (optional) failed" + } + + wget --quiet \ + "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" && tar -xzf "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" \ - -C "$CUSTOM_PATH" --strip-components=1 "git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs" - rm "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" - - wget --quiet "$JGITWHENCE" --output-document="$CUSTOM_PATH/jgit" - chmod a+x "$CUSTOM_PATH/jgit" + -C "$CUSTOM_PATH" --strip-components=1 \ + "git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs" && + rm "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" || { + rm -f "$CUSTOM_PATH/git-lfs" + echo >&2 "LFS download (optional) failed" + } + + wget --quiet "$JGITWHENCE" --output-document="$CUSTOM_PATH/jgit" && + chmod a+x "$CUSTOM_PATH/jgit" || { + rm -f "$CUSTOM_PATH/jgit" + echo >&2 "JGit download (optional) failed" + } ;; esac ;; @@ -119,7 +132,7 @@ StaticAnalysis) sparse) sudo apt-get -q update -q sudo apt-get -q -y install libssl-dev libcurl4-openssl-dev \ - libexpat-dev gettext zlib1g-dev + libexpat-dev gettext zlib1g-dev sparse ;; Documentation) sudo apt-get -q update diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 1c69846723..f99b7db2ee 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -20,7 +20,6 @@ linux-breaking-changes) linux-TEST-vars) export OPENSSL_SHA1_UNSAFE=YesPlease export GIT_TEST_SPLIT_INDEX=yes - export GIT_TEST_MERGE_ALGORITHM=recursive export GIT_TEST_FULL_IN_PACK_ARRAY=true export GIT_TEST_OE_SIZE=10 export GIT_TEST_OE_DELTA_SIZE=5 diff --git a/combine-diff.c b/combine-diff.c index 9527f3160d..dfae9f7995 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -2,7 +2,7 @@ #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "convert.h" #include "diff.h" @@ -1066,7 +1066,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, &result_size, NULL, NULL); } else if (textconv) { struct diff_filespec *df = alloc_filespec(elem->path); - fill_filespec(df, null_oid(), 0, st.st_mode); + fill_filespec(df, null_oid(the_hash_algo), 0, st.st_mode); result_size = fill_textconv(opt->repo, textconv, df, &result); free_filespec(df); } else if (0 <= (fd = open(elem->path, O_RDONLY))) { diff --git a/commit-graph.c b/commit-graph.c index 1021ccb983..6394752b0b 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -13,8 +13,7 @@ #include "refs.h" #include "hash-lookup.h" #include "commit-graph.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "path.h" #include "alloc.h" @@ -2065,7 +2064,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) ctx->graph_name = get_commit_graph_filename(ctx->odb); } - if (safe_create_leading_directories(ctx->graph_name)) { + if (safe_create_leading_directories(the_repository, ctx->graph_name)) { error(_("unable to create leading directories of %s"), ctx->graph_name); return -1; @@ -2090,11 +2089,13 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) return -1; } - f = hashfd(get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer)); + f = hashfd(the_repository->hash_algo, + get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer)); } else { hold_lock_file_for_update_mode(&lk, ctx->graph_name, LOCK_DIE_ON_ERROR, 0444); - f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk)); + f = hashfd(the_repository->hash_algo, + get_lock_file_fd(&lk), get_lock_file_path(&lk)); } cf = init_chunkfile(f); @@ -2716,7 +2717,8 @@ static void graph_report(const char *fmt, ...) static int commit_graph_checksum_valid(struct commit_graph *g) { - return hashfile_checksum_valid(g->data, g->data_len); + return hashfile_checksum_valid(the_repository->hash_algo, + g->data, g->data_len); } static int verify_one_commit_graph(struct repository *r, diff --git a/commit-graph.h b/commit-graph.h index 6781940195..13f662827d 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -1,7 +1,7 @@ #ifndef COMMIT_GRAPH_H #define COMMIT_GRAPH_H -#include "object-store-ll.h" +#include "object-store.h" #include "oidset.h" #define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH" @@ -9,7 +9,7 @@ #include "hex.h" #include "repository.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "utf8.h" #include "diff.h" #include "revision.h" @@ -29,6 +29,7 @@ #include "tree.h" #include "hook.h" #include "parse.h" +#include "object-file.h" #include "object-file-convert.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -1378,7 +1379,7 @@ static int convert_commit_extra_headers(const struct commit_extra_header *orig, struct commit_extra_header *new; CALLOC_ARRAY(new, 1); if (!strcmp(orig->key, "mergetag")) { - if (convert_object_file(&out, algo, compat, + if (convert_object_file(the_repository, &out, algo, compat, orig->value, orig->len, OBJ_TAG, 1)) { free(new); diff --git a/compat/mingw-posix.h b/compat/mingw-posix.h index 8dddfa818d..88e0cf9292 100644 --- a/compat/mingw-posix.h +++ b/compat/mingw-posix.h @@ -201,8 +201,12 @@ int uname(struct utsname *buf); * replacements of existing functions */ -int mingw_unlink(const char *pathname); -#define unlink mingw_unlink +int mingw_unlink(const char *pathname, int handle_in_use_error); +#ifdef MINGW_DONT_HANDLE_IN_USE_ERROR +# define unlink(path) mingw_unlink(path, 0) +#else +# define unlink(path) mingw_unlink(path, 1) +#endif int mingw_rmdir(const char *path); #define rmdir mingw_rmdir diff --git a/compat/mingw.c b/compat/mingw.c index 305a999f1f..8a9972a1ca 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -21,6 +21,9 @@ #include "gettext.h" #define SECURITY_WIN32 #include <sspi.h> +#include <winternl.h> + +#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056) #define HCAST(type, handle) ((type)(intptr_t)handle) @@ -302,7 +305,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf) return wbuf; } -int mingw_unlink(const char *pathname) +int mingw_unlink(const char *pathname, int handle_in_use_error) { int ret, tries = 0; wchar_t wpathname[MAX_PATH]; @@ -317,6 +320,9 @@ int mingw_unlink(const char *pathname) while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) break; + if (!handle_in_use_error) + return ret; + /* * We assume that some other process had the source or * destination file open at the wrong moment and retry. @@ -621,6 +627,8 @@ int mingw_open (const char *filename, int oflags, ...) wchar_t wfilename[MAX_PATH]; open_fn_t open_fn; + DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, RtlGetLastNtStatus, void); + va_start(args, oflags); mode = va_arg(args, int); va_end(args); @@ -644,6 +652,21 @@ int mingw_open (const char *filename, int oflags, ...) fd = open_fn(wfilename, oflags, mode); + /* + * Internally, `_wopen()` uses the `CreateFile()` API with CREATE_NEW, + * which may error out with ERROR_ACCESS_DENIED and an NtStatus of + * STATUS_DELETE_PENDING when the file is scheduled for deletion via + * `DeleteFileW()`. The file essentially exists, so we map errno to + * EEXIST instead of EACCESS so that callers don't have to special-case + * this. + * + * This fixes issues for example with the lockfile interface when one + * process has a lock that it is about to commit or release while + * another process wants to acquire it. + */ + if (fd < 0 && create && GetLastError() == ERROR_ACCESS_DENIED && + INIT_PROC_ADDR(RtlGetLastNtStatus) && RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + errno = EEXIST; if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) { DWORD attrs = GetFileAttributesW(wfilename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) diff --git a/compat/open.c b/compat/open.c index eb3754a23b..37ae2b1aeb 100644 --- a/compat/open.c +++ b/compat/open.c @@ -1,5 +1,6 @@ #include "git-compat-util.h" +#ifdef OPEN_RETURNS_EINTR #undef open int git_open_with_retry(const char *path, int flags, ...) { @@ -23,3 +24,31 @@ int git_open_with_retry(const char *path, int flags, ...) return ret; } +#endif + +int git_open_cloexec(const char *name, int flags) +{ + int fd; + static int o_cloexec = O_CLOEXEC; + + fd = open(name, flags | o_cloexec); + if ((o_cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { + /* Try again w/o O_CLOEXEC: the kernel might not support it */ + o_cloexec &= ~O_CLOEXEC; + fd = open(name, flags | o_cloexec); + } + +#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) + { + static int fd_cloexec = FD_CLOEXEC; + + if (!o_cloexec && 0 <= fd && fd_cloexec) { + /* Opened w/o O_CLOEXEC? try with fcntl(2) to add it */ + int flags = fcntl(fd, F_GETFD); + if (fcntl(fd, F_SETFD, flags | fd_cloexec)) + fd_cloexec = 0; + } + } +#endif + return fd; +} diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c index ec5cc5d2dd..4a4f849629 100644 --- a/compat/regex/regex_internal.c +++ b/compat/regex/regex_internal.c @@ -1232,7 +1232,10 @@ re_node_set_merge (re_node_set *dest, const re_node_set *src) is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) { if (dest->elems[id] == src->elems[is]) - is--, id--; + { + is--; + id--; + } else if (dest->elems[id] < src->elems[is]) dest->elems[--sbase] = src->elems[is--]; else /* if (dest->elems[id] > src->elems[is]) */ diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c index 2eeec82f40..c08f1bbe1f 100644 --- a/compat/regex/regexec.c +++ b/compat/regex/regexec.c @@ -2210,7 +2210,7 @@ sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, /* mctx->bkref_ents may have changed, reload the pointer. */ entry = mctx->bkref_ents + enabled_idx; } - while (enabled_idx++, entry++->more); + while ((void)enabled_idx++, entry++->more); } err = REG_NOERROR; free_return: diff --git a/compat/zlib-compat.h b/compat/zlib-compat.h index 0c60e3af33..ac08276622 100644 --- a/compat/zlib-compat.h +++ b/compat/zlib-compat.h @@ -4,8 +4,8 @@ #ifdef HAVE_ZLIB_NG # include <zlib-ng.h> -# define z_stream zng_stream -#define gz_header_s zng_gz_header_s +# define z_stream_s zng_stream_s +# define gz_header_s zng_gz_header_s # define crc32(crc, buf, len) zng_crc32(crc, buf, len) @@ -31,7 +31,7 @@ #include "hashmap.h" #include "string-list.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pager.h" #include "path.h" #include "utf8.h" @@ -1490,11 +1490,6 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.bigfilethreshold")) { - big_file_threshold = git_config_ulong(var, value, ctx->kvi); - return 0; - } - if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { auto_crlf = AUTO_CRLF_INPUT; diff --git a/config.mak.dev b/config.mak.dev index 95b7bc46ae..e86b6e1b34 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -41,6 +41,10 @@ DEVELOPER_CFLAGS += -Wwrite-strings DEVELOPER_CFLAGS += -fno-common DEVELOPER_CFLAGS += -Wunreachable-code +ifneq ($(filter clang9,$(COMPILER_FEATURES)),) +DEVELOPER_CFLAGS += -Wcomma +endif + ifneq ($(filter clang4,$(COMPILER_FEATURES)),) DEVELOPER_CFLAGS += -Wtautological-constant-out-of-range-compare endif diff --git a/config.mak.uname b/config.mak.uname index b12d4e168a..db22a8fb31 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -48,18 +48,18 @@ ifeq ($(uname_S),OSF1) endif ifeq ($(uname_S),Linux) HAVE_ALLOCA_H = YesPlease + # override in config.mak if you have glibc >= 2.38 NO_STRLCPY = YesPlease + CSPRNG_METHOD = getrandom HAVE_PATHS_H = YesPlease LIBC_CONTAINS_LIBINTL = YesPlease HAVE_DEV_TTY = YesPlease HAVE_CLOCK_GETTIME = YesPlease HAVE_CLOCK_MONOTONIC = YesPlease - # -lrt is needed for clock_gettime on glibc <= 2.16 - NEEDS_LIBRT = YesPlease HAVE_SYNC_FILE_RANGE = YesPlease HAVE_GETDELIM = YesPlease FREAD_READS_DIRECTORIES = UnfortunatelyYes - BASIC_CFLAGS += -DHAVE_SYSINFO + HAVE_SYSINFO = YesPlease PROCFS_EXECUTABLE_PATH = /proc/self/exe HAVE_PLATFORM_PROCINFO = YesPlease COMPAT_OBJS += compat/linux/procinfo.o @@ -246,9 +246,16 @@ ifeq ($(uname_O),Cygwin) # Try commenting this out if you suspect MMAP is more efficient NO_MMAP = YesPlease else - NO_REGEX = UnfortunatelyYes + ifeq ($(shell expr "$(uname_R)" : '1\.7\.'),4) + NO_REGEX = UnfortunatelyYes + endif endif HAVE_DEV_TTY = YesPlease + HAVE_GETDELIM = YesPlease + HAVE_CLOCK_GETTIME = YesPlease + HAVE_CLOCK_MONOTONIC = YesPlease + HAVE_SYSINFO = YesPlease + CSPRNG_METHOD = arc4random HAVE_ALLOCA_H = YesPlease NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes diff --git a/configure.ac b/configure.ac index 5923edc44a..d7e0503f1e 100644 --- a/configure.ac +++ b/configure.ac @@ -1066,6 +1066,14 @@ AC_CHECK_LIB([iconv], [locale_charset], [AC_CHECK_LIB([charset], [locale_charset], [CHARSET_LIB=-lcharset])]) GIT_CONF_SUBST([CHARSET_LIB]) + +# +# Define HAVE_SYSINFO=YesPlease if sysinfo is available. +GIT_CHECK_FUNC(sysinfo, + [HAVE_SYSINFO=YesPlease], + [HAVE_SYSINFO=]) +GIT_CONF_SUBST([HAVE_SYSINFO]) + # # Define HAVE_CLOCK_GETTIME=YesPlease if clock_gettime is available. GIT_CHECK_FUNC(clock_gettime, diff --git a/connected.c b/connected.c index 3099da84f3..4415388beb 100644 --- a/connected.c +++ b/connected.c @@ -3,7 +3,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "run-command.h" #include "sigchain.h" #include "connected.h" diff --git a/contrib/credential/netrc/meson.build b/contrib/credential/netrc/meson.build index a990dbb86d..3d74547c8a 100644 --- a/contrib/credential/netrc/meson.build +++ b/contrib/credential/netrc/meson.build @@ -7,14 +7,16 @@ credential_netrc = custom_target( install_dir: get_option('libexecdir') / 'git-core', ) -credential_netrc_testenv = test_environment -credential_netrc_testenv.set('CREDENTIAL_NETRC_PATH', credential_netrc.full_path()) +if get_option('tests') + credential_netrc_testenv = test_environment + credential_netrc_testenv.set('CREDENTIAL_NETRC_PATH', credential_netrc.full_path()) -test('t-git-credential-netrc', - shell, - args: [ meson.current_source_dir() / 't-git-credential-netrc.sh' ], - workdir: meson.current_source_dir(), - env: credential_netrc_testenv, - depends: test_dependencies + bin_wrappers + [credential_netrc], - timeout: 0, -) + test('t-git-credential-netrc', + shell, + args: [ meson.current_source_dir() / 't-git-credential-netrc.sh' ], + workdir: meson.current_source_dir(), + env: credential_netrc_testenv, + depends: test_dependencies + bin_wrappers + [credential_netrc], + timeout: 0, + ) +endif diff --git a/contrib/subtree/meson.build b/contrib/subtree/meson.build index 9c72b23625..63714166a6 100644 --- a/contrib/subtree/meson.build +++ b/contrib/subtree/meson.build @@ -12,16 +12,18 @@ git_subtree = custom_target( install_dir: get_option('libexecdir') / 'git-core', ) -subtree_test_environment = test_environment -subtree_test_environment.prepend('PATH', meson.current_build_dir()) +if get_option('tests') + subtree_test_environment = test_environment + subtree_test_environment.prepend('PATH', meson.current_build_dir()) -test('t7900-subtree', shell, - args: [ 't7900-subtree.sh' ], - env: subtree_test_environment, - workdir: meson.current_source_dir() / 't', - depends: test_dependencies + bin_wrappers + [ git_subtree ], - timeout: 0, -) + test('t7900-subtree', shell, + args: [ 't7900-subtree.sh' ], + env: subtree_test_environment, + workdir: meson.current_source_dir() / 't', + depends: test_dependencies + bin_wrappers + [ git_subtree ], + timeout: 0, + ) +endif if get_option('docs').contains('man') subtree_xml = custom_target( @@ -8,7 +8,7 @@ #include "copy.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "attr.h" #include "run-command.h" #include "quote.h" diff --git a/csum-file.c b/csum-file.c index b58c183a4f..6e21e3cac8 100644 --- a/csum-file.c +++ b/csum-file.c @@ -8,8 +8,6 @@ * able to verify hasn't been messed with afterwards. */ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "csum-file.h" #include "git-zlib.h" @@ -148,21 +146,23 @@ void hashwrite(struct hashfile *f, const void *buf, unsigned int count) } } -struct hashfile *hashfd_check(const char *name) +struct hashfile *hashfd_check(const struct git_hash_algo *algop, + const char *name) { int sink, check; struct hashfile *f; sink = xopen("/dev/null", O_WRONLY); check = xopen(name, O_RDONLY); - f = hashfd(sink, name); + f = hashfd(algop, sink, name); f->check_fd = check; f->check_buffer = xmalloc(f->buffer_len); return f; } -static struct hashfile *hashfd_internal(int fd, const char *name, +static struct hashfile *hashfd_internal(const struct git_hash_algo *algop, + int fd, const char *name, struct progress *tp, size_t buffer_len) { @@ -176,7 +176,7 @@ static struct hashfile *hashfd_internal(int fd, const char *name, f->do_crc = 0; f->skip_hash = 0; - f->algop = unsafe_hash_algo(the_hash_algo); + f->algop = unsafe_hash_algo(algop); f->algop->init_fn(&f->ctx); f->buffer_len = buffer_len; @@ -186,17 +186,19 @@ static struct hashfile *hashfd_internal(int fd, const char *name, return f; } -struct hashfile *hashfd(int fd, const char *name) +struct hashfile *hashfd(const struct git_hash_algo *algop, + int fd, const char *name) { /* * Since we are not going to use a progress meter to * measure the rate of data passing through this hashfile, * use a larger buffer size to reduce fsync() calls. */ - return hashfd_internal(fd, name, NULL, 128 * 1024); + return hashfd_internal(algop, fd, name, NULL, 128 * 1024); } -struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp) +struct hashfile *hashfd_throughput(const struct git_hash_algo *algop, + int fd, const char *name, struct progress *tp) { /* * Since we are expecting to report progress of the @@ -204,7 +206,7 @@ struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp * size so the progress indicators arrive at a more * frequent rate. */ - return hashfd_internal(fd, name, tp, 8 * 1024); + return hashfd_internal(algop, fd, name, tp, 8 * 1024); } void hashfile_checkpoint_init(struct hashfile *f, @@ -246,13 +248,15 @@ uint32_t crc32_end(struct hashfile *f) return f->crc32; } -int hashfile_checksum_valid(const unsigned char *data, size_t total_len) +int hashfile_checksum_valid(const struct git_hash_algo *algop, + const unsigned char *data, size_t total_len) { unsigned char got[GIT_MAX_RAWSZ]; struct git_hash_ctx ctx; - const struct git_hash_algo *algop = unsafe_hash_algo(the_hash_algo); size_t data_len = total_len - algop->rawsz; + algop = unsafe_hash_algo(algop); + if (total_len < algop->rawsz) return 0; /* say "too short"? */ diff --git a/csum-file.h b/csum-file.h index ffccbf0996..07ae11024a 100644 --- a/csum-file.h +++ b/csum-file.h @@ -45,9 +45,12 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *); #define CSUM_FSYNC 2 #define CSUM_HASH_IN_STREAM 4 -struct hashfile *hashfd(int fd, const char *name); -struct hashfile *hashfd_check(const char *name); -struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp); +struct hashfile *hashfd(const struct git_hash_algo *algop, + int fd, const char *name); +struct hashfile *hashfd_check(const struct git_hash_algo *algop, + const char *name); +struct hashfile *hashfd_throughput(const struct git_hash_algo *algop, + int fd, const char *name, struct progress *tp); /* * Free the hashfile without flushing its contents to disk. This only @@ -66,7 +69,8 @@ void crc32_begin(struct hashfile *); uint32_t crc32_end(struct hashfile *); /* Verify checksum validity while reading. Returns non-zero on success. */ -int hashfile_checksum_valid(const unsigned char *data, size_t len); +int hashfile_checksum_valid(const struct git_hash_algo *algop, + const unsigned char *data, size_t len); /* * Returns the total number of bytes fed to the hashfile so far (including ones diff --git a/delta-islands.c b/delta-islands.c index 3aec43fada..36c94799d6 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -267,8 +266,7 @@ void resolve_tree_islands(struct repository *r, QSORT(todo, nr, tree_depth_compare); if (progress) - progress_state = start_progress(the_repository, - _("Propagating island marks"), nr); + progress_state = start_progress(r, _("Propagating island marks"), nr); for (i = 0; i < nr; i++) { struct object_entry *ent = todo[i].entry; @@ -490,9 +488,9 @@ void load_delta_islands(struct repository *r, int progress) island_marks = kh_init_oid_map(); - git_config(island_config_callback, &ild); + repo_config(r, island_config_callback, &ild); ild.remote_islands = kh_init_str(); - refs_for_each_ref(get_main_ref_store(the_repository), + refs_for_each_ref(get_main_ref_store(r), find_island_for_ref, &ild); free_config_regexes(&ild); deduplicate_islands(ild.remote_islands, r); @@ -502,7 +500,7 @@ void load_delta_islands(struct repository *r, int progress) fprintf(stderr, _("Marked %d islands, done.\n"), island_counter); } -void propagate_island_marks(struct commit *commit) +void propagate_island_marks(struct repository *r, struct commit *commit) { khiter_t pos = kh_get_oid_map(island_marks, commit->object.oid); @@ -510,8 +508,8 @@ void propagate_island_marks(struct commit *commit) struct commit_list *p; struct island_bitmap *root_marks = kh_value(island_marks, pos); - repo_parse_commit(the_repository, commit); - set_island_marks(&repo_get_commit_tree(the_repository, commit)->object, + repo_parse_commit(r, commit); + set_island_marks(&repo_get_commit_tree(r, commit)->object, root_marks); for (p = commit->parents; p; p = p->next) set_island_marks(&p->item->object, root_marks); diff --git a/delta-islands.h b/delta-islands.h index 8d1591ae28..6107660306 100644 --- a/delta-islands.h +++ b/delta-islands.h @@ -12,7 +12,7 @@ void resolve_tree_islands(struct repository *r, int progress, struct packing_data *to_pack); void load_delta_islands(struct repository *r, int progress); -void propagate_island_marks(struct commit *commit); +void propagate_island_marks(struct repository *r, struct commit *commit); int compute_pack_layers(struct packing_data *to_pack); void free_island_marks(void); diff --git a/detect-compiler b/detect-compiler index a87650b71b..124ebdd4c9 100755 --- a/detect-compiler +++ b/detect-compiler @@ -9,7 +9,7 @@ CC="$*" # # FreeBSD clang version 3.4.1 (tags/RELEASE...) get_version_line() { - LANG=C LC_ALL=C $CC -v 2>&1 | grep ' version ' + LANG=C LC_ALL=C $CC -v 2>&1 | sed -n '/ version /{p;q;}' } get_family() { diff --git a/diagnose.c b/diagnose.c index bd485effea..b1be74be98 100644 --- a/diagnose.c +++ b/diagnose.c @@ -7,7 +7,7 @@ #include "gettext.h" #include "hex.h" #include "strvec.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "parse-options.h" #include "repository.h" diff --git a/diff-delta.c b/diff-delta.c index a4faf73829..71d37368d6 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -438,19 +438,31 @@ create_delta(const struct delta_index *index, op = out + outpos++; i = 0x80; - if (moff & 0x000000ff) - out[outpos++] = moff >> 0, i |= 0x01; - if (moff & 0x0000ff00) - out[outpos++] = moff >> 8, i |= 0x02; - if (moff & 0x00ff0000) - out[outpos++] = moff >> 16, i |= 0x04; - if (moff & 0xff000000) - out[outpos++] = moff >> 24, i |= 0x08; - - if (msize & 0x00ff) - out[outpos++] = msize >> 0, i |= 0x10; - if (msize & 0xff00) - out[outpos++] = msize >> 8, i |= 0x20; + if (moff & 0x000000ff) { + out[outpos++] = moff >> 0; + i |= 0x01; + } + if (moff & 0x0000ff00) { + out[outpos++] = moff >> 8; + i |= 0x02; + } + if (moff & 0x00ff0000) { + out[outpos++] = moff >> 16; + i |= 0x04; + } + if (moff & 0xff000000) { + out[outpos++] = moff >> 24; + i |= 0x08; + } + + if (msize & 0x00ff) { + out[outpos++] = msize >> 0; + i |= 0x10; + } + if (msize & 0xff00) { + out[outpos++] = msize >> 8; + i |= 0x20; + } *op = i; diff --git a/diff-lib.c b/diff-lib.c index 353b473ed5..244468dd1a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -172,7 +172,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) * these from (stage - 2). */ dpath = combine_diff_path_new(ce->name, ce_namelen(ce), - wt_mode, null_oid(), 2); + wt_mode, null_oid(the_hash_algo), 2); while (i < entries) { struct cache_entry *nce = istate->cache[i]; @@ -257,7 +257,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) ce_intent_to_add(ce)) { newmode = ce_mode_from_stat(ce, st.st_mode); diff_addremove(&revs->diffopt, '+', newmode, - null_oid(), 0, ce->name, 0); + null_oid(the_hash_algo), 0, ce->name, 0); continue; } @@ -274,7 +274,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) } oldmode = ce->ce_mode; old_oid = &ce->oid; - new_oid = changed ? null_oid() : &ce->oid; + new_oid = changed ? null_oid(the_hash_algo) : &ce->oid; diff_change(&revs->diffopt, oldmode, newmode, old_oid, new_oid, !is_null_oid(old_oid), @@ -330,7 +330,7 @@ static int get_stat_data(const struct cache_entry *ce, 0, dirty_submodule); if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); - oid = null_oid(); + oid = null_oid(the_hash_algo); } } @@ -402,7 +402,7 @@ static int show_modified(struct rev_info *revs, p = combine_diff_path_new(new_entry->name, ce_namelen(new_entry), - mode, null_oid(), 2); + mode, null_oid(the_hash_algo), 2); p->parent[0].status = DIFF_STATUS_MODIFIED; p->parent[0].mode = new_entry->ce_mode; oidcpy(&p->parent[0].oid, &new_entry->oid); diff --git a/diff-no-index.c b/diff-no-index.c index 6f277892d3..9739b2b268 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -113,7 +113,8 @@ static void populate_from_stdin(struct diff_filespec *s) populate_common(s, &buf); } -static struct diff_filespec *noindex_filespec(const char *name, int mode, +static struct diff_filespec *noindex_filespec(const struct git_hash_algo *algop, + const char *name, int mode, enum special special) { struct diff_filespec *s; @@ -121,7 +122,7 @@ static struct diff_filespec *noindex_filespec(const char *name, int mode, if (!name) name = "/dev/null"; s = alloc_filespec(name); - fill_filespec(s, null_oid(), 0, mode); + fill_filespec(s, null_oid(algop), 0, mode); if (special == SPECIAL_STDIN) populate_from_stdin(s); else if (special == SPECIAL_PIPE) @@ -129,7 +130,7 @@ static struct diff_filespec *noindex_filespec(const char *name, int mode, return s; } -static int queue_diff(struct diff_options *o, +static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop, const char *name1, const char *name2, int recursing) { int mode1 = 0, mode2 = 0; @@ -145,14 +146,14 @@ static int queue_diff(struct diff_options *o, if (S_ISDIR(mode1)) { /* 2 is file that is created */ - d1 = noindex_filespec(NULL, 0, SPECIAL_NONE); - d2 = noindex_filespec(name2, mode2, special2); + d1 = noindex_filespec(algop, NULL, 0, SPECIAL_NONE); + d2 = noindex_filespec(algop, name2, mode2, special2); name2 = NULL; mode2 = 0; } else { /* 1 is file that is deleted */ - d1 = noindex_filespec(name1, mode1, special1); - d2 = noindex_filespec(NULL, 0, SPECIAL_NONE); + d1 = noindex_filespec(algop, name1, mode1, special1); + d2 = noindex_filespec(algop, NULL, 0, SPECIAL_NONE); name1 = NULL; mode1 = 0; } @@ -217,7 +218,7 @@ static int queue_diff(struct diff_options *o, n2 = buffer2.buf; } - ret = queue_diff(o, n1, n2, 1); + ret = queue_diff(o, algop, n1, n2, 1); } string_list_clear(&p1, 0); string_list_clear(&p2, 0); @@ -234,8 +235,8 @@ static int queue_diff(struct diff_options *o, SWAP(special1, special2); } - d1 = noindex_filespec(name1, mode1, special1); - d2 = noindex_filespec(name2, mode2, special2); + d1 = noindex_filespec(algop, name1, mode1, special1); + d2 = noindex_filespec(algop, name2, mode2, special2); diff_queue(&diff_queued_diff, d1, d2); return 0; } @@ -297,9 +298,8 @@ static const char * const diff_no_index_usage[] = { NULL }; -int diff_no_index(struct rev_info *revs, - int implicit_no_index, - int argc, const char **argv) +int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop, + int implicit_no_index, int argc, const char **argv) { int i, no_index; int ret = 1; @@ -354,7 +354,7 @@ int diff_no_index(struct rev_info *revs, setup_diff_pager(&revs->diffopt); revs->diffopt.flags.exit_with_status = 1; - if (queue_diff(&revs->diffopt, paths[0], paths[1], 0)) + if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0)) goto out; diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); @@ -23,7 +23,7 @@ #include "color.h" #include "run-command.h" #include "utf8.h" -#include "object-store-ll.h" +#include "object-store.h" #include "userdiff.h" #include "submodule.h" #include "hashmap.h" @@ -4193,7 +4193,8 @@ int diff_populate_filespec(struct repository *r, * is probably fine. */ if (check_binary && - s->size > big_file_threshold && s->is_binary == -1) { + s->size > repo_settings_get_big_file_threshold(the_repository) && + s->is_binary == -1) { s->is_binary = 1; return 0; } @@ -4243,7 +4244,8 @@ object_read: if (size_only || check_binary) { if (size_only) return 0; - if (s->size > big_file_threshold && s->is_binary == -1) { + if (s->size > repo_settings_get_big_file_threshold(the_repository) && + s->is_binary == -1) { s->is_binary = 1; return 0; } @@ -4344,7 +4346,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r, die_errno("readlink(%s)", one->path); prep_temp_blob(r->index, one->path, temp, sb.buf, sb.len, (one->oid_valid ? - &one->oid : null_oid()), + &one->oid : null_oid(the_hash_algo)), (one->oid_valid ? one->mode : S_IFLNK)); strbuf_release(&sb); @@ -4353,7 +4355,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r, /* we can borrow from the file in the work tree */ temp->name = one->path; if (!one->oid_valid) - oid_to_hex_r(temp->hex, null_oid()); + oid_to_hex_r(temp->hex, null_oid(the_hash_algo)); else oid_to_hex_r(temp->hex, &one->oid); /* Even though we may sometimes borrow the @@ -5892,10 +5894,15 @@ struct option *add_diff_options(const struct option *opts, OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"), N_("select files by diff type"), PARSE_OPT_NONEG, diff_opt_diff_filter), - { OPTION_CALLBACK, 0, "output", options, N_("<file>"), - N_("output to a specific file"), - PARSE_OPT_NONEG, NULL, 0, diff_opt_output }, - + { + .type = OPTION_CALLBACK, + .long_name = "output", + .value = options, + .argh = N_("<file>"), + .help = N_("output to a specific file"), + .flags = PARSE_OPT_NONEG, + .ll_callback = diff_opt_output, + }, OPT_END() }; @@ -6647,8 +6654,8 @@ static void create_filepairs_for_header_only_notifications(struct diff_options * one = alloc_filespec(e->key); two = alloc_filespec(e->key); - fill_filespec(one, null_oid(), 0, 0); - fill_filespec(two, null_oid(), 0, 0); + fill_filespec(one, null_oid(the_hash_algo), 0, 0); + fill_filespec(two, null_oid(the_hash_algo), 0, 0); p = diff_queue(q, one, two); p->status = DIFF_STATUS_MODIFIED; } @@ -689,7 +689,7 @@ void flush_one_hunk(struct object_id *result, struct git_hash_ctx *ctx); int diff_result_code(struct rev_info *); -int diff_no_index(struct rev_info *, +int diff_no_index(struct rev_info *, const struct git_hash_algo *algop, int implicit_no_index, int, const char **); int index_differs_from(struct repository *r, const char *def, diff --git a/diffcore-rename.c b/diffcore-rename.c index 8077283fc7..179731462b 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -8,7 +8,7 @@ #include "git-compat-util.h" #include "diff.h" #include "diffcore.h" -#include "object-store-ll.h" +#include "object-store.h" #include "hashmap.h" #include "mem-pool.h" #include "oid-array.h" @@ -17,8 +17,7 @@ #include "environment.h" #include "gettext.h" #include "name-hash.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "refs.h" #include "repository.h" @@ -4035,7 +4034,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree, */ i++; - sub = submodule_from_path(&subrepo, null_oid(), ce->name); + sub = submodule_from_path(&subrepo, null_oid(the_hash_algo), ce->name); if (!sub || !is_submodule_active(&subrepo, ce->name)) /* .gitmodules broken or inactive sub */ continue; @@ -4063,12 +4062,12 @@ void connect_work_tree_and_git_dir(const char *work_tree_, /* Prepare .git file */ strbuf_addf(&gitfile_sb, "%s/.git", work_tree_); - if (safe_create_leading_directories_const(gitfile_sb.buf)) + if (safe_create_leading_directories_const(the_repository, gitfile_sb.buf)) die(_("could not create directories for %s"), gitfile_sb.buf); /* Prepare config file */ strbuf_addf(&cfg_sb, "%s/config", git_dir_); - if (safe_create_leading_directories_const(cfg_sb.buf)) + if (safe_create_leading_directories_const(the_repository, cfg_sb.buf)) die(_("could not create directories for %s"), cfg_sb.buf); git_dir = real_pathdup(git_dir_, 1); @@ -1,7 +1,7 @@ #define USE_THE_REPOSITORY_VARIABLE #include "git-compat-util.h" -#include "object-store-ll.h" +#include "object-store.h" #include "dir.h" #include "environment.h" #include "gettext.h" diff --git a/environment.c b/environment.c index 9e4c7781be..970a407753 100644 --- a/environment.c +++ b/environment.c @@ -49,7 +49,6 @@ int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT; -unsigned long big_file_threshold = 512 * 1024 * 1024; char *editor_program; char *askpass_program; char *excludes_file; @@ -107,7 +106,7 @@ int auto_comment_line_char; /* Parallel index stat data preload? */ int core_preload_index = 1; -/* This is set by setup_git_dir_gently() and/or git_default_config() */ +/* This is set by setup_git_directory_gently() and/or git_default_config() */ char *git_work_tree_cfg; /* diff --git a/environment.h b/environment.h index 45e690f203..3d98461a06 100644 --- a/environment.h +++ b/environment.h @@ -152,9 +152,6 @@ extern char *apply_default_ignorewhitespace; extern char *git_attributes_file; extern int zlib_compression_level; extern int pack_compression_level; -extern size_t packed_git_window_size; -extern size_t packed_git_limit; -extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; extern int max_allowed_tree_depth; diff --git a/fetch-pack.c b/fetch-pack.c index 1ed5e11dd5..210dc30d50 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -24,7 +24,7 @@ #include "oid-array.h" #include "oidset.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "connected.h" #include "fetch-negotiator.h" diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 5b63c3b088..501b5acdd4 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -6,7 +6,7 @@ #include "environment.h" #include "refs.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "diff.h" #include "diff-merges.h" #include "hex.h" @@ -4,7 +4,7 @@ #include "date.h" #include "dir.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "repository.h" #include "object.h" diff --git a/git-compat-util.h b/git-compat-util.h index afa040086f..36b9577c8d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -93,12 +93,19 @@ DISABLE_WARNING(-Wsign-compare) # define BARF_UNLESS_COPYABLE(dst, src) \ BUILD_ASSERT_OR_ZERO(__builtin_types_compatible_p(__typeof__(*(dst)), \ __typeof__(*(src)))) + +# define BARF_UNLESS_SIGNED(var) BUILD_ASSERT_OR_ZERO(((__typeof__(var)) -1) < 0) +# define BARF_UNLESS_UNSIGNED(var) BUILD_ASSERT_OR_ZERO(((__typeof__(var)) -1) > 0) #else # define BARF_UNLESS_AN_ARRAY(arr) 0 # define BARF_UNLESS_COPYABLE(dst, src) \ BUILD_ASSERT_OR_ZERO(0 ? ((*(dst) = *(src)), 0) : \ sizeof(*(dst)) == sizeof(*(src))) + +# define BARF_UNLESS_SIGNED(var) 0 +# define BARF_UNLESS_UNSIGNED(var) 0 #endif + /* * ARRAY_SIZE - get the number of elements in a visible array * @x: the array whose size you want. @@ -598,6 +605,9 @@ static inline bool strip_suffix(const char *str, const char *suffix, #define DEFAULT_PACKED_GIT_LIMIT \ ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? (32 * 1024L * 1024L) : 256)) +int git_open_cloexec(const char *name, int flags); +#define git_open(name) git_open_cloexec(name, O_RDONLY) + static inline size_t st_add(size_t a, size_t b) { if (unsigned_add_overflows(a, b)) diff --git a/git-send-email.perl b/git-send-email.perl index 798d59b84f..1f613fa979 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1419,7 +1419,7 @@ sub smtp_auth_maybe { die "invalid smtp auth: '${smtp_auth}'"; } - # TODO: Authentication may fail not because credentials were + # Authentication may fail not because credentials were # invalid but due to other reasons, in which we should not # reject credentials. $auth = Git::credential({ @@ -1431,24 +1431,61 @@ sub smtp_auth_maybe { 'password' => $smtp_authpass }, sub { my $cred = shift; + my $result; + my $error; + + # catch all SMTP auth error in a unified eval block + eval { + if ($smtp_auth) { + my $sasl = Authen::SASL->new( + mechanism => $smtp_auth, + callback => { + user => $cred->{'username'}, + pass => $cred->{'password'}, + authname => $cred->{'username'}, + } + ); + $result = $smtp->auth($sasl); + } else { + $result = $smtp->auth($cred->{'username'}, $cred->{'password'}); + } + 1; # ensure true value is returned if no exception is thrown + } or do { + $error = $@ || 'Unknown error'; + }; + + return ($error + ? handle_smtp_error($error) + : ($result ? 1 : 0)); + }); - if ($smtp_auth) { - my $sasl = Authen::SASL->new( - mechanism => $smtp_auth, - callback => { - user => $cred->{'username'}, - pass => $cred->{'password'}, - authname => $cred->{'username'}, - } - ); + return $auth; +} - return !!$smtp->auth($sasl); +sub handle_smtp_error { + my ($error) = @_; + + # Parse SMTP status code from error message in: + # https://www.rfc-editor.org/rfc/rfc5321.html + if ($error =~ /\b(\d{3})\b/) { + my $status_code = $1; + if ($status_code =~ /^4/) { + # 4yz: Transient Negative Completion reply + warn "SMTP transient error (status code $status_code): $error"; + return 1; + } elsif ($status_code =~ /^5/) { + # 5yz: Permanent Negative Completion reply + warn "SMTP permanent error (status code $status_code): $error"; + return 0; } + # If no recognized status code is found, treat as transient error + warn "SMTP unknown error: $error. Treating as transient failure."; + return 1; + } - return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); - }); - - return $auth; + # If no status code is found, treat as transient error + warn "SMTP generic error: $error"; + return 1; } sub ssl_verify_params { diff --git a/git-zlib.c b/git-zlib.c index 651dd9e07c..df9604910e 100644 --- a/git-zlib.c +++ b/git-zlib.c @@ -45,7 +45,7 @@ static void zlib_pre_call(git_zstream *s) s->z.avail_out = zlib_buf_cap(s->avail_out); } -static void zlib_post_call(git_zstream *s) +static void zlib_post_call(git_zstream *s, int status) { unsigned long bytes_consumed; unsigned long bytes_produced; @@ -54,7 +54,12 @@ static void zlib_post_call(git_zstream *s) bytes_produced = s->z.next_out - s->next_out; if (s->z.total_out != s->total_out + bytes_produced) BUG("total_out mismatch"); - if (s->z.total_in != s->total_in + bytes_consumed) + /* + * zlib does not update total_in when it returns Z_NEED_DICT, + * causing a mismatch here. Skip the sanity check in that case. + */ + if (status != Z_NEED_DICT && + s->z.total_in != s->total_in + bytes_consumed) BUG("total_in mismatch"); s->total_out = s->z.total_out; @@ -72,7 +77,7 @@ void git_inflate_init(git_zstream *strm) zlib_pre_call(strm); status = inflateInit(&strm->z); - zlib_post_call(strm); + zlib_post_call(strm, status); if (status == Z_OK) return; die("inflateInit: %s (%s)", zerr_to_string(status), @@ -90,7 +95,7 @@ void git_inflate_init_gzip_only(git_zstream *strm) zlib_pre_call(strm); status = inflateInit2(&strm->z, windowBits); - zlib_post_call(strm); + zlib_post_call(strm, status); if (status == Z_OK) return; die("inflateInit2: %s (%s)", zerr_to_string(status), @@ -103,7 +108,7 @@ void git_inflate_end(git_zstream *strm) zlib_pre_call(strm); status = inflateEnd(&strm->z); - zlib_post_call(strm); + zlib_post_call(strm, status); if (status == Z_OK) return; error("inflateEnd: %s (%s)", zerr_to_string(status), @@ -122,7 +127,7 @@ int git_inflate(git_zstream *strm, int flush) ? 0 : flush); if (status == Z_MEM_ERROR) die("inflate: out of memory"); - zlib_post_call(strm); + zlib_post_call(strm, status); /* * Let zlib work another round, while we can still @@ -160,7 +165,7 @@ void git_deflate_init(git_zstream *strm, int level) memset(strm, 0, sizeof(*strm)); zlib_pre_call(strm); status = deflateInit(&strm->z, level); - zlib_post_call(strm); + zlib_post_call(strm, status); if (status == Z_OK) return; die("deflateInit: %s (%s)", zerr_to_string(status), @@ -176,7 +181,7 @@ static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) status = deflateInit2(&strm->z, level, Z_DEFLATED, windowBits, 8, Z_DEFAULT_STRATEGY); - zlib_post_call(strm); + zlib_post_call(strm, status); if (status == Z_OK) return; die("deflateInit2: %s (%s)", zerr_to_string(status), @@ -207,7 +212,7 @@ int git_deflate_abort(git_zstream *strm) zlib_pre_call(strm); status = deflateEnd(&strm->z); - zlib_post_call(strm); + zlib_post_call(strm, status); return status; } @@ -227,7 +232,7 @@ int git_deflate_end_gently(git_zstream *strm) zlib_pre_call(strm); status = deflateEnd(&strm->z); - zlib_post_call(strm); + zlib_post_call(strm, status); return status; } @@ -244,7 +249,7 @@ int git_deflate(git_zstream *strm, int flush) ? 0 : flush); if (status == Z_MEM_ERROR) die("deflate: out of memory"); - zlib_post_call(strm); + zlib_post_call(strm, status); /* * Let zlib work another round, while we can still diff --git a/git-zlib.h b/git-zlib.h index 1e8d9aabcb..0e66fefa8c 100644 --- a/git-zlib.h +++ b/git-zlib.h @@ -4,7 +4,7 @@ #include "compat/zlib-compat.h" typedef struct git_zstream { - z_stream z; + struct z_stream_s z; unsigned long avail_in; unsigned long avail_out; unsigned long total_in; diff --git a/gitweb/Makefile b/gitweb/Makefile index d5748e9359..26a683d442 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -118,7 +118,7 @@ $(MAK_DIR_GITWEB)gitweb.cgi: $(MAK_DIR_GITWEB)gitweb.perl $(MAK_DIR_GITWEB)static/gitweb.js: $(MAK_DIR_GITWEB)generate-gitweb-js.sh $(MAK_DIR_GITWEB)static/gitweb.js: $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_JSLIB_FILES)) $(QUIET_GEN)$(RM) $@ $@+ && \ - $(MAK_DIR_GITWEB)generate-gitweb-js.sh $@+ $^ && \ + $(MAK_DIR_GITWEB)generate-gitweb-js.sh $@+ $(filter %.js,$^) && \ mv $@+ $@ ### Installation rules diff --git a/gitweb/meson.build b/gitweb/meson.build index 89b403dc9d..88a54b4dc9 100644 --- a/gitweb/meson.build +++ b/gitweb/meson.build @@ -1,5 +1,5 @@ gitweb_config = configuration_data() -gitweb_config.set_quoted('PERL_PATH', perl.full_path()) +gitweb_config.set_quoted('PERL_PATH', target_perl.full_path()) gitweb_config.set_quoted('CSSMIN', '') gitweb_config.set_quoted('JSMIN', '') gitweb_config.set_quoted('GIT_BINDIR', get_option('prefix') / get_option('bindir')) @@ -5,7 +5,7 @@ #include "gettext.h" #include "grep.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pretty.h" #include "userdiff.h" #include "xdiff-interface.h" @@ -1517,7 +1517,7 @@ static int fill_textconv_grep(struct repository *r, fill_filespec(df, gs->identifier, 1, 0100644); break; case GREP_SOURCE_FILE: - fill_filespec(df, null_oid(), 0, 0100644); + fill_filespec(df, null_oid(r->hash_algo), 0, 0100644); break; default: BUG("attempt to textconv something without a path?"); diff --git a/hash.c b/hash.c new file mode 100644 index 0000000000..4a04ecb50e --- /dev/null +++ b/hash.c @@ -0,0 +1,277 @@ +#include "git-compat-util.h" +#include "hash.h" +#include "hex.h" + +static const struct object_id empty_tree_oid = { + .hash = { + 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, + 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 + }, + .algo = GIT_HASH_SHA1, +}; +static const struct object_id empty_blob_oid = { + .hash = { + 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, + 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 + }, + .algo = GIT_HASH_SHA1, +}; +static const struct object_id null_oid_sha1 = { + .hash = {0}, + .algo = GIT_HASH_SHA1, +}; +static const struct object_id empty_tree_oid_sha256 = { + .hash = { + 0x6e, 0xf1, 0x9b, 0x41, 0x22, 0x5c, 0x53, 0x69, 0xf1, 0xc1, + 0x04, 0xd4, 0x5d, 0x8d, 0x85, 0xef, 0xa9, 0xb0, 0x57, 0xb5, + 0x3b, 0x14, 0xb4, 0xb9, 0xb9, 0x39, 0xdd, 0x74, 0xde, 0xcc, + 0x53, 0x21 + }, + .algo = GIT_HASH_SHA256, +}; +static const struct object_id empty_blob_oid_sha256 = { + .hash = { + 0x47, 0x3a, 0x0f, 0x4c, 0x3b, 0xe8, 0xa9, 0x36, 0x81, 0xa2, + 0x67, 0xe3, 0xb1, 0xe9, 0xa7, 0xdc, 0xda, 0x11, 0x85, 0x43, + 0x6f, 0xe1, 0x41, 0xf7, 0x74, 0x91, 0x20, 0xa3, 0x03, 0x72, + 0x18, 0x13 + }, + .algo = GIT_HASH_SHA256, +}; +static const struct object_id null_oid_sha256 = { + .hash = {0}, + .algo = GIT_HASH_SHA256, +}; + +static void git_hash_sha1_init(struct git_hash_ctx *ctx) +{ + ctx->algop = &hash_algos[GIT_HASH_SHA1]; + git_SHA1_Init(&ctx->state.sha1); +} + +static void git_hash_sha1_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) +{ + dst->algop = src->algop; + git_SHA1_Clone(&dst->state.sha1, &src->state.sha1); +} + +static void git_hash_sha1_update(struct git_hash_ctx *ctx, const void *data, size_t len) +{ + git_SHA1_Update(&ctx->state.sha1, data, len); +} + +static void git_hash_sha1_final(unsigned char *hash, struct git_hash_ctx *ctx) +{ + git_SHA1_Final(hash, &ctx->state.sha1); +} + +static void git_hash_sha1_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) +{ + git_SHA1_Final(oid->hash, &ctx->state.sha1); + memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ); + oid->algo = GIT_HASH_SHA1; +} + +static void git_hash_sha1_init_unsafe(struct git_hash_ctx *ctx) +{ + ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA1]); + git_SHA1_Init_unsafe(&ctx->state.sha1_unsafe); +} + +static void git_hash_sha1_clone_unsafe(struct git_hash_ctx *dst, const struct git_hash_ctx *src) +{ + dst->algop = src->algop; + git_SHA1_Clone_unsafe(&dst->state.sha1_unsafe, &src->state.sha1_unsafe); +} + +static void git_hash_sha1_update_unsafe(struct git_hash_ctx *ctx, const void *data, + size_t len) +{ + git_SHA1_Update_unsafe(&ctx->state.sha1_unsafe, data, len); +} + +static void git_hash_sha1_final_unsafe(unsigned char *hash, struct git_hash_ctx *ctx) +{ + git_SHA1_Final_unsafe(hash, &ctx->state.sha1_unsafe); +} + +static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, struct git_hash_ctx *ctx) +{ + git_SHA1_Final_unsafe(oid->hash, &ctx->state.sha1_unsafe); + memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ); + oid->algo = GIT_HASH_SHA1; +} + +static void git_hash_sha256_init(struct git_hash_ctx *ctx) +{ + ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA256]); + git_SHA256_Init(&ctx->state.sha256); +} + +static void git_hash_sha256_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) +{ + dst->algop = src->algop; + git_SHA256_Clone(&dst->state.sha256, &src->state.sha256); +} + +static void git_hash_sha256_update(struct git_hash_ctx *ctx, const void *data, size_t len) +{ + git_SHA256_Update(&ctx->state.sha256, data, len); +} + +static void git_hash_sha256_final(unsigned char *hash, struct git_hash_ctx *ctx) +{ + git_SHA256_Final(hash, &ctx->state.sha256); +} + +static void git_hash_sha256_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) +{ + git_SHA256_Final(oid->hash, &ctx->state.sha256); + /* + * This currently does nothing, so the compiler should optimize it out, + * but keep it in case we extend the hash size again. + */ + memset(oid->hash + GIT_SHA256_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA256_RAWSZ); + oid->algo = GIT_HASH_SHA256; +} + +static void git_hash_unknown_init(struct git_hash_ctx *ctx UNUSED) +{ + BUG("trying to init unknown hash"); +} + +static void git_hash_unknown_clone(struct git_hash_ctx *dst UNUSED, + const struct git_hash_ctx *src UNUSED) +{ + BUG("trying to clone unknown hash"); +} + +static void git_hash_unknown_update(struct git_hash_ctx *ctx UNUSED, + const void *data UNUSED, + size_t len UNUSED) +{ + BUG("trying to update unknown hash"); +} + +static void git_hash_unknown_final(unsigned char *hash UNUSED, + struct git_hash_ctx *ctx UNUSED) +{ + BUG("trying to finalize unknown hash"); +} + +static void git_hash_unknown_final_oid(struct object_id *oid UNUSED, + struct git_hash_ctx *ctx UNUSED) +{ + BUG("trying to finalize unknown hash"); +} + +static const struct git_hash_algo sha1_unsafe_algo = { + .name = "sha1", + .format_id = GIT_SHA1_FORMAT_ID, + .rawsz = GIT_SHA1_RAWSZ, + .hexsz = GIT_SHA1_HEXSZ, + .blksz = GIT_SHA1_BLKSZ, + .init_fn = git_hash_sha1_init_unsafe, + .clone_fn = git_hash_sha1_clone_unsafe, + .update_fn = git_hash_sha1_update_unsafe, + .final_fn = git_hash_sha1_final_unsafe, + .final_oid_fn = git_hash_sha1_final_oid_unsafe, + .empty_tree = &empty_tree_oid, + .empty_blob = &empty_blob_oid, + .null_oid = &null_oid_sha1, +}; + +const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { + { + .name = NULL, + .format_id = 0x00000000, + .rawsz = 0, + .hexsz = 0, + .blksz = 0, + .init_fn = git_hash_unknown_init, + .clone_fn = git_hash_unknown_clone, + .update_fn = git_hash_unknown_update, + .final_fn = git_hash_unknown_final, + .final_oid_fn = git_hash_unknown_final_oid, + .empty_tree = NULL, + .empty_blob = NULL, + .null_oid = NULL, + }, + { + .name = "sha1", + .format_id = GIT_SHA1_FORMAT_ID, + .rawsz = GIT_SHA1_RAWSZ, + .hexsz = GIT_SHA1_HEXSZ, + .blksz = GIT_SHA1_BLKSZ, + .init_fn = git_hash_sha1_init, + .clone_fn = git_hash_sha1_clone, + .update_fn = git_hash_sha1_update, + .final_fn = git_hash_sha1_final, + .final_oid_fn = git_hash_sha1_final_oid, + .unsafe = &sha1_unsafe_algo, + .empty_tree = &empty_tree_oid, + .empty_blob = &empty_blob_oid, + .null_oid = &null_oid_sha1, + }, + { + .name = "sha256", + .format_id = GIT_SHA256_FORMAT_ID, + .rawsz = GIT_SHA256_RAWSZ, + .hexsz = GIT_SHA256_HEXSZ, + .blksz = GIT_SHA256_BLKSZ, + .init_fn = git_hash_sha256_init, + .clone_fn = git_hash_sha256_clone, + .update_fn = git_hash_sha256_update, + .final_fn = git_hash_sha256_final, + .final_oid_fn = git_hash_sha256_final_oid, + .empty_tree = &empty_tree_oid_sha256, + .empty_blob = &empty_blob_oid_sha256, + .null_oid = &null_oid_sha256, + } +}; + +const struct object_id *null_oid(const struct git_hash_algo *algop) +{ + return algop->null_oid; +} + +const char *empty_tree_oid_hex(const struct git_hash_algo *algop) +{ + static char buf[GIT_MAX_HEXSZ + 1]; + return oid_to_hex_r(buf, algop->empty_tree); +} + +int hash_algo_by_name(const char *name) +{ + if (!name) + return GIT_HASH_UNKNOWN; + for (size_t i = 1; i < GIT_HASH_NALGOS; i++) + if (!strcmp(name, hash_algos[i].name)) + return i; + return GIT_HASH_UNKNOWN; +} + +int hash_algo_by_id(uint32_t format_id) +{ + for (size_t i = 1; i < GIT_HASH_NALGOS; i++) + if (format_id == hash_algos[i].format_id) + return i; + return GIT_HASH_UNKNOWN; +} + +int hash_algo_by_length(size_t len) +{ + for (size_t i = 1; i < GIT_HASH_NALGOS; i++) + if (len == hash_algos[i].rawsz) + return i; + return GIT_HASH_UNKNOWN; +} + +const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop) +{ + /* If we have a faster "unsafe" implementation, use that. */ + if (algop->unsafe) + return algop->unsafe; + /* Otherwise use the default one. */ + return algop; +} @@ -2,26 +2,32 @@ #define HASH_H #if defined(SHA1_APPLE) +#define SHA1_BACKEND "SHA1_APPLE (No collision detection)" #include <CommonCrypto/CommonDigest.h> #elif defined(SHA1_OPENSSL) +# define SHA1_BACKEND "SHA1_OPENSSL (No collision detection)" # include <openssl/sha.h> # if defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL >= 3 # define SHA1_NEEDS_CLONE_HELPER # include "sha1/openssl.h" # endif #elif defined(SHA1_DC) +#define SHA1_BACKEND "SHA1_DC" #include "sha1dc_git.h" #else /* SHA1_BLK */ +#define SHA1_BACKEND "SHA1_BLK (No collision detection)" #include "block-sha1/sha1.h" #endif #if defined(SHA1_APPLE_UNSAFE) +# define SHA1_UNSAFE_BACKEND "SHA1_APPLE_UNSAFE" # include <CommonCrypto/CommonDigest.h> # define platform_SHA_CTX_unsafe CC_SHA1_CTX # define platform_SHA1_Init_unsafe CC_SHA1_Init # define platform_SHA1_Update_unsafe CC_SHA1_Update # define platform_SHA1_Final_unsafe CC_SHA1_Final #elif defined(SHA1_OPENSSL_UNSAFE) +# define SHA1_UNSAFE_BACKEND "SHA1_OPENSSL_UNSAFE" # include <openssl/sha.h> # if defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL >= 3 # define SHA1_NEEDS_CLONE_HELPER_UNSAFE @@ -38,6 +44,7 @@ # define platform_SHA1_Final_unsafe SHA1_Final # endif #elif defined(SHA1_BLK_UNSAFE) +# define SHA1_UNSAFE_BACKEND "SHA1_BLK_UNSAFE" # include "block-sha1/sha1.h" # define platform_SHA_CTX_unsafe blk_SHA_CTX # define platform_SHA1_Init_unsafe blk_SHA1_Init @@ -46,17 +53,21 @@ #endif #if defined(SHA256_NETTLE) +#define SHA256_BACKEND "SHA256_NETTLE" #include "sha256/nettle.h" #elif defined(SHA256_GCRYPT) +#define SHA256_BACKEND "SHA256_GCRYPT" #define SHA256_NEEDS_CLONE_HELPER #include "sha256/gcrypt.h" #elif defined(SHA256_OPENSSL) +# define SHA256_BACKEND "SHA256_OPENSSL" # include <openssl/sha.h> # if defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL >= 3 # define SHA256_NEEDS_CLONE_HELPER # include "sha256/openssl.h" # endif #else +#define SHA256_BACKEND "SHA256_BLK" #include "sha256/block/sha256.h" #endif @@ -326,7 +337,7 @@ int hash_algo_by_name(const char *name); /* Identical, except based on the format ID. */ int hash_algo_by_id(uint32_t format_id); /* Identical, except based on the length. */ -int hash_algo_by_length(int len); +int hash_algo_by_length(size_t len); /* Identical, except for a pointer to struct git_hash_algo. */ static inline int hash_algo_by_ptr(const struct git_hash_algo *p) { @@ -341,7 +352,7 @@ static inline int hash_algo_by_ptr(const struct git_hash_algo *p) const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop); -const struct object_id *null_oid(void); +const struct object_id *null_oid(const struct git_hash_algo *algop); static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2, const struct git_hash_algo *algop) { @@ -9,6 +9,7 @@ #include "run-command.h" #include "levenshtein.h" #include "gettext.h" +#include "hash.h" #include "help.h" #include "command-list.h" #include "string-list.h" @@ -803,6 +804,12 @@ void get_version_info(struct strbuf *buf, int show_build_options) #elif defined ZLIB_VERSION strbuf_addf(buf, "zlib: %s\n", ZLIB_VERSION); #endif + strbuf_addf(buf, "SHA-1: %s\n", SHA1_BACKEND); +#if defined SHA1_UNSAFE_BACKEND + strbuf_addf(buf, "non-collision-detecting-SHA-1: %s\n", + SHA1_UNSAFE_BACKEND); +#endif + strbuf_addf(buf, "SHA-256: %s\n", SHA256_BACKEND); } } diff --git a/http-backend.c b/http-backend.c index 50b2858fad..0c575aa88a 100644 --- a/http-backend.c +++ b/http-backend.c @@ -18,7 +18,7 @@ #include "url.h" #include "strvec.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "protocol.h" #include "date.h" #include "write-or-die.h" diff --git a/http-push.c b/http-push.c index 1b030d96f4..32e37565f4 100644 --- a/http-push.c +++ b/http-push.c @@ -19,7 +19,8 @@ #include "tree-walk.h" #include "url.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-file.h" +#include "object-store.h" #include "commit-reach.h" #ifdef EXPAT_NEEDS_XMLPARSE_H diff --git a/http-walker.c b/http-walker.c index 7918ddc096..882cae19c2 100644 --- a/http-walker.c +++ b/http-walker.c @@ -9,7 +9,7 @@ #include "list.h" #include "transport.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" struct alt_base { char *base; @@ -19,7 +19,7 @@ #include "packfile.h" #include "string-list.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "tempfile.h" static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); @@ -197,10 +197,13 @@ kwsincr (kwset_t kws, char const *text, size_t len) while (link && label != link->label) { links[depth] = link; - if (label < link->label) - dirs[depth++] = L, link = link->llink; - else - dirs[depth++] = R, link = link->rlink; + if (label < link->label) { + dirs[depth++] = L; + link = link->llink; + } else { + dirs[depth++] = R; + link = link->rlink; + } } /* The current character doesn't have an outgoing link at @@ -257,14 +260,14 @@ kwsincr (kwset_t kws, char const *text, size_t len) switch (dirs[depth + 1]) { case L: - r = links[depth], t = r->llink, rl = t->rlink; - t->rlink = r, r->llink = rl; + r = links[depth]; t = r->llink; rl = t->rlink; + t->rlink = r; r->llink = rl; t->balance = r->balance = 0; break; case R: - r = links[depth], l = r->llink, t = l->rlink; - rl = t->rlink, lr = t->llink; - t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl; + r = links[depth]; l = r->llink; t = l->rlink; + rl = t->rlink; lr = t->llink; + t->llink = l; l->rlink = lr; t->rlink = r; r->llink = rl; l->balance = t->balance != 1 ? 0 : -1; r->balance = t->balance != (char) -1 ? 0 : 1; t->balance = 0; @@ -277,14 +280,14 @@ kwsincr (kwset_t kws, char const *text, size_t len) switch (dirs[depth + 1]) { case R: - l = links[depth], t = l->rlink, lr = t->llink; - t->llink = l, l->rlink = lr; + l = links[depth]; t = l->rlink; lr = t->llink; + t->llink = l; l->rlink = lr; t->balance = l->balance = 0; break; case L: - l = links[depth], r = l->rlink, t = r->llink; - lr = t->llink, rl = t->rlink; - t->llink = l, l->rlink = lr, t->rlink = r, r->llink = rl; + l = links[depth]; r = l->rlink; t = r->llink; + lr = t->llink; rl = t->rlink; + t->llink = l; l->rlink = lr; t->rlink = r; r->llink = rl; l->balance = t->balance != 1 ? 0 : -1; r->balance = t->balance != (char) -1 ? 0 : 1; t->balance = 0; @@ -567,22 +570,22 @@ bmexec (kwset_t kws, char const *text, size_t size) { while (tp <= ep) { - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; if (d == 0) goto found; - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; if (d == 0) goto found; - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; if (d == 0) goto found; - d = d1[U(tp[-1])], tp += d; - d = d1[U(tp[-1])], tp += d; + d = d1[U(tp[-1])]; tp += d; + d = d1[U(tp[-1])]; tp += d; } break; found: @@ -649,7 +652,8 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch) mch = NULL; else { - mch = text, accept = kwset->trie; + mch = text; + accept = kwset->trie; goto match; } diff --git a/list-objects-filter.c b/list-objects-filter.c index dc598a081b..7765761b3c 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -12,7 +12,7 @@ #include "oidmap.h" #include "oidset.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" /* Remember to update object flag allocation in object.h */ /* diff --git a/list-objects.c b/list-objects.c index 943e62e868..1e5512e131 100644 --- a/list-objects.c +++ b/list-objects.c @@ -14,7 +14,7 @@ #include "list-objects-filter.h" #include "list-objects-filter-options.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "trace.h" #include "environment.h" diff --git a/log-tree.c b/log-tree.c index 8b184d6776..a4d4ab59ca 100644 --- a/log-tree.c +++ b/log-tree.c @@ -9,7 +9,7 @@ #include "environment.h" #include "hex.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "repository.h" #include "tmp-objdir.h" #include "commit.h" @@ -499,7 +499,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, { struct strbuf headers = STRBUF_INIT; const char *name = oid_to_hex(opt->zero_commit ? - null_oid() : &commit->object.oid); + null_oid(the_hash_algo) : &commit->object.oid); *need_8bit_cte_p = 0; /* unknown */ @@ -6,7 +6,7 @@ #include "string-list.h" #include "mailmap.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "setup.h" char *git_mailmap_file; diff --git a/match-trees.c b/match-trees.c index ef14ceb594..72922d5d64 100644 --- a/match-trees.c +++ b/match-trees.c @@ -6,7 +6,8 @@ #include "strbuf.h" #include "tree.h" #include "tree-walk.h" -#include "object-store-ll.h" +#include "object-file.h" +#include "object-store.h" #include "repository.h" static int score_missing(unsigned mode) diff --git a/merge-blobs.c b/merge-blobs.c index 0ad0390fea..53f36dbc17 100644 --- a/merge-blobs.c +++ b/merge-blobs.c @@ -4,7 +4,7 @@ #include "merge-ll.h" #include "blob.h" #include "merge-blobs.h" -#include "object-store-ll.h" +#include "object-store.h" static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { diff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h index aeffa1c87b..b7e1ced9d7 100644 --- a/merge-ort-wrappers.h +++ b/merge-ort-wrappers.h @@ -1,7 +1,7 @@ #ifndef MERGE_ORT_WRAPPERS_H #define MERGE_ORT_WRAPPERS_H -#include "merge-recursive.h" +#include "merge-ort.h" /* * rename-detecting three-way merge, no recursion. diff --git a/merge-ort.c b/merge-ort.c index c41f466577..77310a4a52 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -26,6 +26,7 @@ #include "cache-tree.h" #include "commit.h" #include "commit-reach.h" +#include "config.h" #include "diff.h" #include "diffcore.h" #include "dir.h" @@ -36,8 +37,9 @@ #include "merge-ll.h" #include "match-trees.h" #include "mem-pool.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "path.h" #include "promisor-remote.h" @@ -1817,7 +1819,7 @@ static int merge_submodule(struct merge_options *opt, BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); if ((sub_not_initialized = repo_submodule_init(&subrepo, - opt->repo, path, null_oid()))) { + opt->repo, path, null_oid(the_hash_algo)))) { path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s (not checked out)"), @@ -2199,7 +2201,7 @@ static int handle_content_merge(struct merge_options *opt, two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); merge_status = merge_3way(opt, path, - two_way ? null_oid() : &o->oid, + two_way ? null_oid(the_hash_algo) : &o->oid, &a->oid, &b->oid, pathnames, extra_marker_size, &result_buf); @@ -2231,7 +2233,7 @@ static int handle_content_merge(struct merge_options *opt, } else if (S_ISGITLINK(a->mode)) { int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); clean = merge_submodule(opt, pathnames[0], - two_way ? null_oid() : &o->oid, + two_way ? null_oid(the_hash_algo) : &o->oid, &a->oid, &b->oid, &result->oid); if (clean < 0) return -1; @@ -2739,7 +2741,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, assert(!new_ci->match_mask); new_ci->dirmask = 0; new_ci->stages[1].mode = 0; - oidcpy(&new_ci->stages[1].oid, null_oid()); + oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo)); /* * Now that we have the file information in new_ci, make sure @@ -2752,7 +2754,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, continue; /* zero out any entries related to files */ ci->stages[i].mode = 0; - oidcpy(&ci->stages[i].oid, null_oid()); + oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo)); } /* Now we want to focus on new_ci, so reassign ci to it. */ @@ -3123,7 +3125,7 @@ static int process_renames(struct merge_options *opt, if (type_changed) { /* rename vs. typechange */ /* Mark the original as resolved by removal */ - memcpy(&oldinfo->stages[0].oid, null_oid(), + memcpy(&oldinfo->stages[0].oid, null_oid(the_hash_algo), sizeof(oldinfo->stages[0].oid)); oldinfo->stages[0].mode = 0; oldinfo->filemask &= 0x06; @@ -4006,7 +4008,7 @@ static int process_entry(struct merge_options *opt, if (ci->filemask & (1 << i)) continue; ci->stages[i].mode = 0; - oidcpy(&ci->stages[i].oid, null_oid()); + oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo)); } } else if (ci->df_conflict && ci->merged.result.mode != 0) { /* @@ -4053,7 +4055,7 @@ static int process_entry(struct merge_options *opt, continue; /* zero out any entries related to directories */ new_ci->stages[i].mode = 0; - oidcpy(&new_ci->stages[i].oid, null_oid()); + oidcpy(&new_ci->stages[i].oid, null_oid(the_hash_algo)); } /* @@ -4175,11 +4177,11 @@ static int process_entry(struct merge_options *opt, new_ci->merged.result.mode = ci->stages[2].mode; oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid); new_ci->stages[1].mode = 0; - oidcpy(&new_ci->stages[1].oid, null_oid()); + oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo)); new_ci->filemask = 5; if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) { new_ci->stages[0].mode = 0; - oidcpy(&new_ci->stages[0].oid, null_oid()); + oidcpy(&new_ci->stages[0].oid, null_oid(the_hash_algo)); new_ci->filemask = 4; } @@ -4187,11 +4189,11 @@ static int process_entry(struct merge_options *opt, ci->merged.result.mode = ci->stages[1].mode; oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); ci->stages[2].mode = 0; - oidcpy(&ci->stages[2].oid, null_oid()); + oidcpy(&ci->stages[2].oid, null_oid(the_hash_algo)); ci->filemask = 3; if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) { ci->stages[0].mode = 0; - oidcpy(&ci->stages[0].oid, null_oid()); + oidcpy(&ci->stages[0].oid, null_oid(the_hash_algo)); ci->filemask = 2; } @@ -4316,7 +4318,7 @@ static int process_entry(struct merge_options *opt, /* Deleted on both sides */ ci->merged.is_null = 1; ci->merged.result.mode = 0; - oidcpy(&ci->merged.result.oid, null_oid()); + oidcpy(&ci->merged.result.oid, null_oid(the_hash_algo)); assert(!ci->df_conflict); ci->merged.clean = !ci->path_conflict; } @@ -4957,9 +4959,6 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) } trace2_region_leave("merge", "sanity checks", opt->repo); - /* Default to histogram diff. Actually, just hardcode it...for now. */ - opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF); - /* Handle attr direction stuff for renormalization */ if (opt->renormalize) git_attr_set_direction(GIT_ATTR_CHECKOUT); @@ -5325,3 +5324,161 @@ void merge_incore_recursive(struct merge_options *opt, merge_ort_internal(opt, merge_bases, side1, side2, result); trace2_region_leave("merge", "incore_recursive", opt->repo); } + +static void merge_recursive_config(struct merge_options *opt, int ui) +{ + char *value = NULL; + int renormalize = 0; + git_config_get_int("merge.verbosity", &opt->verbosity); + git_config_get_int("diff.renamelimit", &opt->rename_limit); + git_config_get_int("merge.renamelimit", &opt->rename_limit); + git_config_get_bool("merge.renormalize", &renormalize); + opt->renormalize = renormalize; + if (!git_config_get_string("diff.renames", &value)) { + opt->detect_renames = git_config_rename("diff.renames", value); + free(value); + } + if (!git_config_get_string("merge.renames", &value)) { + opt->detect_renames = git_config_rename("merge.renames", value); + free(value); + } + if (!git_config_get_string("merge.directoryrenames", &value)) { + int boolval = git_parse_maybe_bool(value); + if (0 <= boolval) { + opt->detect_directory_renames = boolval ? + MERGE_DIRECTORY_RENAMES_TRUE : + MERGE_DIRECTORY_RENAMES_NONE; + } else if (!strcasecmp(value, "conflict")) { + opt->detect_directory_renames = + MERGE_DIRECTORY_RENAMES_CONFLICT; + } /* avoid erroring on values from future versions of git */ + free(value); + } + if (ui) { + if (!git_config_get_string("diff.algorithm", &value)) { + long diff_algorithm = parse_algorithm_value(value); + if (diff_algorithm < 0) + die(_("unknown value for config '%s': %s"), "diff.algorithm", value); + opt->xdl_opts = (opt->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | diff_algorithm; + free(value); + } + } + git_config(git_xmerge_config, NULL); +} + +static void init_merge_options(struct merge_options *opt, + struct repository *repo, int ui) +{ + const char *merge_verbosity; + memset(opt, 0, sizeof(struct merge_options)); + + opt->repo = repo; + + opt->detect_renames = -1; + opt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT; + opt->rename_limit = -1; + + opt->verbosity = 2; + opt->buffer_output = 1; + strbuf_init(&opt->obuf, 0); + + opt->renormalize = 0; + + opt->conflict_style = -1; + opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF); + + merge_recursive_config(opt, ui); + merge_verbosity = getenv("GIT_MERGE_VERBOSITY"); + if (merge_verbosity) + opt->verbosity = strtol(merge_verbosity, NULL, 10); + if (opt->verbosity >= 5) + opt->buffer_output = 0; +} + +void init_ui_merge_options(struct merge_options *opt, + struct repository *repo) +{ + init_merge_options(opt, repo, 1); +} + +void init_basic_merge_options(struct merge_options *opt, + struct repository *repo) +{ + init_merge_options(opt, repo, 0); +} + +/* + * For now, members of merge_options do not need deep copying, but + * it may change in the future, in which case we would need to update + * this, and also make a matching change to clear_merge_options() to + * release the resources held by a copied instance. + */ +void copy_merge_options(struct merge_options *dst, struct merge_options *src) +{ + *dst = *src; +} + +void clear_merge_options(struct merge_options *opt UNUSED) +{ + ; /* no-op as our copy is shallow right now */ +} + +int parse_merge_opt(struct merge_options *opt, const char *s) +{ + const char *arg; + + if (!s || !*s) + return -1; + if (!strcmp(s, "ours")) + opt->recursive_variant = MERGE_VARIANT_OURS; + else if (!strcmp(s, "theirs")) + opt->recursive_variant = MERGE_VARIANT_THEIRS; + else if (!strcmp(s, "subtree")) + opt->subtree_shift = ""; + else if (skip_prefix(s, "subtree=", &arg)) + opt->subtree_shift = arg; + else if (!strcmp(s, "patience")) + opt->xdl_opts = DIFF_WITH_ALG(opt, PATIENCE_DIFF); + else if (!strcmp(s, "histogram")) + opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF); + else if (skip_prefix(s, "diff-algorithm=", &arg)) { + long value = parse_algorithm_value(arg); + if (value < 0) + return -1; + /* clear out previous settings */ + DIFF_XDL_CLR(opt, NEED_MINIMAL); + opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; + opt->xdl_opts |= value; + } + else if (!strcmp(s, "ignore-space-change")) + DIFF_XDL_SET(opt, IGNORE_WHITESPACE_CHANGE); + else if (!strcmp(s, "ignore-all-space")) + DIFF_XDL_SET(opt, IGNORE_WHITESPACE); + else if (!strcmp(s, "ignore-space-at-eol")) + DIFF_XDL_SET(opt, IGNORE_WHITESPACE_AT_EOL); + else if (!strcmp(s, "ignore-cr-at-eol")) + DIFF_XDL_SET(opt, IGNORE_CR_AT_EOL); + else if (!strcmp(s, "renormalize")) + opt->renormalize = 1; + else if (!strcmp(s, "no-renormalize")) + opt->renormalize = 0; + else if (!strcmp(s, "no-renames")) + opt->detect_renames = 0; + else if (!strcmp(s, "find-renames")) { + opt->detect_renames = 1; + opt->rename_score = 0; + } + else if (skip_prefix(s, "find-renames=", &arg) || + skip_prefix(s, "rename-threshold=", &arg)) { + if ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0) + return -1; + opt->detect_renames = 1; + } + /* + * Please update $__git_merge_strategy_options in + * git-completion.bash when you add new options + */ + else + return -1; + return 0; +} diff --git a/merge-ort.h b/merge-ort.h index b63bc5424e..30750c0396 100644 --- a/merge-ort.h +++ b/merge-ort.h @@ -1,10 +1,11 @@ #ifndef MERGE_ORT_H #define MERGE_ORT_H -#include "merge-recursive.h" #include "hash.h" +#include "strbuf.h" struct commit; +struct commit_list; struct tree; struct strmap; @@ -44,6 +45,51 @@ struct merge_result { unsigned _properly_initialized; }; +struct merge_options_internal; +struct merge_options { + struct repository *repo; + + /* ref names used in console messages and conflict markers */ + const char *ancestor; + const char *branch1; + const char *branch2; + + /* rename related options */ + int detect_renames; + enum { + MERGE_DIRECTORY_RENAMES_NONE = 0, + MERGE_DIRECTORY_RENAMES_CONFLICT = 1, + MERGE_DIRECTORY_RENAMES_TRUE = 2 + } detect_directory_renames; + int rename_limit; + int rename_score; + int show_rename_progress; + + /* xdiff-related options (patience, ignore whitespace, ours/theirs) */ + long xdl_opts; + int conflict_style; + enum { + MERGE_VARIANT_NORMAL = 0, + MERGE_VARIANT_OURS, + MERGE_VARIANT_THEIRS + } recursive_variant; + + /* console output related options */ + int verbosity; + unsigned buffer_output; /* 1: output at end, 2: keep buffered */ + struct strbuf obuf; /* output buffer; if buffer_output == 2, caller + * must handle and call strbuf_release */ + + /* miscellaneous control options */ + const char *subtree_shift; + unsigned renormalize : 1; + unsigned record_conflict_msgs_as_headers : 1; + const char *msg_header_prefix; + + /* internal fields used by the implementation */ + struct merge_options_internal *priv; +}; + /* Mostly internal function also used by merge-ort-wrappers.c */ struct commit *make_virtual_commit(struct repository *repo, struct tree *tree, @@ -119,4 +165,16 @@ void merge_get_conflicted_files(struct merge_result *result, void merge_finalize(struct merge_options *opt, struct merge_result *result); + +/* for use by porcelain commands */ +void init_ui_merge_options(struct merge_options *opt, struct repository *repo); +/* for use by plumbing commands */ +void init_basic_merge_options(struct merge_options *opt, struct repository *repo); + +void copy_merge_options(struct merge_options *dst, struct merge_options *src); +void clear_merge_options(struct merge_options *opt); + +/* parse the option in s and update the relevant field of opt */ +int parse_merge_opt(struct merge_options *opt, const char *s); + #endif diff --git a/merge-recursive.c b/merge-recursive.c deleted file mode 100644 index 4fbbece922..0000000000 --- a/merge-recursive.c +++ /dev/null @@ -1,4079 +0,0 @@ -/* - * Recursive Merge algorithm stolen from git-merge-recursive.py by - * Fredrik Kuivinen. - * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 - */ - -#define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS - -#include "git-compat-util.h" -#include "merge-recursive.h" - -#include "alloc.h" -#include "cache-tree.h" -#include "commit.h" -#include "commit-reach.h" -#include "config.h" -#include "diff.h" -#include "diffcore.h" -#include "dir.h" -#include "environment.h" -#include "gettext.h" -#include "hex.h" -#include "merge-ll.h" -#include "lockfile.h" -#include "match-trees.h" -#include "name-hash.h" -#include "object-file.h" -#include "object-name.h" -#include "object-store-ll.h" -#include "path.h" -#include "repository.h" -#include "revision.h" -#include "sparse-index.h" -#include "string-list.h" -#include "symlinks.h" -#include "tag.h" -#include "tree-walk.h" -#include "unpack-trees.h" -#include "xdiff-interface.h" - -struct merge_options_internal { - int call_depth; - int needed_rename_limit; - struct hashmap current_file_dir_set; - struct string_list df_conflict_file_set; - struct unpack_trees_options unpack_opts; - struct index_state orig_index; -}; - -struct path_hashmap_entry { - struct hashmap_entry e; - char path[FLEX_ARRAY]; -}; - -static int path_hashmap_cmp(const void *cmp_data UNUSED, - const struct hashmap_entry *eptr, - const struct hashmap_entry *entry_or_key, - const void *keydata) -{ - const struct path_hashmap_entry *a, *b; - const char *key = keydata; - - a = container_of(eptr, const struct path_hashmap_entry, e); - b = container_of(entry_or_key, const struct path_hashmap_entry, e); - - return fspathcmp(a->path, key ? key : b->path); -} - -/* - * For dir_rename_entry, directory names are stored as a full path from the - * toplevel of the repository and do not include a trailing '/'. Also: - * - * dir: original name of directory being renamed - * non_unique_new_dir: if true, could not determine new_dir - * new_dir: final name of directory being renamed - * possible_new_dirs: temporary used to help determine new_dir; see comments - * in get_directory_renames() for details - */ -struct dir_rename_entry { - struct hashmap_entry ent; - char *dir; - unsigned non_unique_new_dir:1; - struct strbuf new_dir; - struct string_list possible_new_dirs; -}; - -static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap, - char *dir) -{ - struct dir_rename_entry key; - - if (!dir) - return NULL; - hashmap_entry_init(&key.ent, strhash(dir)); - key.dir = dir; - return hashmap_get_entry(hashmap, &key, ent, NULL); -} - -static int dir_rename_cmp(const void *cmp_data UNUSED, - const struct hashmap_entry *eptr, - const struct hashmap_entry *entry_or_key, - const void *keydata UNUSED) -{ - const struct dir_rename_entry *e1, *e2; - - e1 = container_of(eptr, const struct dir_rename_entry, ent); - e2 = container_of(entry_or_key, const struct dir_rename_entry, ent); - - return strcmp(e1->dir, e2->dir); -} - -static void dir_rename_init(struct hashmap *map) -{ - hashmap_init(map, dir_rename_cmp, NULL, 0); -} - -static void dir_rename_entry_init(struct dir_rename_entry *entry, - char *directory) -{ - hashmap_entry_init(&entry->ent, strhash(directory)); - entry->dir = directory; - entry->non_unique_new_dir = 0; - strbuf_init(&entry->new_dir, 0); - string_list_init_nodup(&entry->possible_new_dirs); -} - -struct collision_entry { - struct hashmap_entry ent; - char *target_file; - struct string_list source_files; - unsigned reported_already:1; -}; - -static struct collision_entry *collision_find_entry(struct hashmap *hashmap, - char *target_file) -{ - struct collision_entry key; - - hashmap_entry_init(&key.ent, strhash(target_file)); - key.target_file = target_file; - return hashmap_get_entry(hashmap, &key, ent, NULL); -} - -static int collision_cmp(const void *cmp_data UNUSED, - const struct hashmap_entry *eptr, - const struct hashmap_entry *entry_or_key, - const void *keydata UNUSED) -{ - const struct collision_entry *e1, *e2; - - e1 = container_of(eptr, const struct collision_entry, ent); - e2 = container_of(entry_or_key, const struct collision_entry, ent); - - return strcmp(e1->target_file, e2->target_file); -} - -static void collision_init(struct hashmap *map) -{ - hashmap_init(map, collision_cmp, NULL, 0); -} - -static void flush_output(struct merge_options *opt) -{ - if (opt->buffer_output < 2 && opt->obuf.len) { - fputs(opt->obuf.buf, stdout); - strbuf_reset(&opt->obuf); - } -} - -__attribute__((format (printf, 2, 3))) -static int err(struct merge_options *opt, const char *err, ...) -{ - va_list params; - - if (opt->buffer_output < 2) - flush_output(opt); - else { - strbuf_complete(&opt->obuf, '\n'); - strbuf_addstr(&opt->obuf, "error: "); - } - va_start(params, err); - strbuf_vaddf(&opt->obuf, err, params); - va_end(params); - if (opt->buffer_output > 1) - strbuf_addch(&opt->obuf, '\n'); - else { - error("%s", opt->obuf.buf); - strbuf_reset(&opt->obuf); - } - - return -1; -} - -static struct tree *shift_tree_object(struct repository *repo, - struct tree *one, struct tree *two, - const char *subtree_shift) -{ - struct object_id shifted; - - if (!*subtree_shift) { - shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0); - } else { - shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted, - subtree_shift); - } - if (oideq(&two->object.oid, &shifted)) - return two; - return lookup_tree(repo, &shifted); -} - -static inline void set_commit_tree(struct commit *c, struct tree *t) -{ - c->maybe_tree = t; -} - -static struct commit *make_virtual_commit(struct repository *repo, - struct tree *tree, - const char *comment) -{ - struct commit *commit = alloc_commit_node(repo); - - set_merge_remote_desc(commit, comment, (struct object *)commit); - set_commit_tree(commit, tree); - commit->object.parsed = 1; - return commit; -} - -enum rename_type { - RENAME_NORMAL = 0, - RENAME_VIA_DIR, - RENAME_ADD, - RENAME_DELETE, - RENAME_ONE_FILE_TO_ONE, - RENAME_ONE_FILE_TO_TWO, - RENAME_TWO_FILES_TO_ONE -}; - -/* - * Since we want to write the index eventually, we cannot reuse the index - * for these (temporary) data. - */ -struct stage_data { - struct diff_filespec stages[4]; /* mostly for oid & mode; maybe path */ - struct rename_conflict_info *rename_conflict_info; - unsigned processed:1, - rename_conflict_info_owned:1; -}; - -struct rename { - unsigned processed:1; - struct diff_filepair *pair; - const char *branch; /* branch that the rename occurred on */ - /* - * If directory rename detection affected this rename, what was its - * original type ('A' or 'R') and it's original destination before - * the directory rename (otherwise, '\0' and NULL for these two vars). - */ - char dir_rename_original_type; - char *dir_rename_original_dest; - /* - * Purpose of src_entry and dst_entry: - * - * If 'before' is renamed to 'after' then src_entry will contain - * the versions of 'before' from the merge_base, HEAD, and MERGE in - * stages 1, 2, and 3; dst_entry will contain the respective - * versions of 'after' in corresponding locations. Thus, we have a - * total of six modes and oids, though some will be null. (Stage 0 - * is ignored; we're interested in handling conflicts.) - * - * Since we don't turn on break-rewrites by default, neither - * src_entry nor dst_entry can have all three of their stages have - * non-null oids, meaning at most four of the six will be non-null. - * Also, since this is a rename, both src_entry and dst_entry will - * have at least one non-null oid, meaning at least two will be - * non-null. Of the six oids, a typical rename will have three be - * non-null. Only two implies a rename/delete, and four implies a - * rename/add. - */ - struct stage_data *src_entry; - struct stage_data *dst_entry; -}; - -struct rename_conflict_info { - enum rename_type rename_type; - struct rename *ren1; - struct rename *ren2; -}; - -static inline void setup_rename_conflict_info(enum rename_type rename_type, - struct merge_options *opt, - struct rename *ren1, - struct rename *ren2) -{ - struct rename_conflict_info *ci; - - /* - * When we have two renames involved, it's easiest to get the - * correct things into stage 2 and 3, and to make sure that the - * content merge puts HEAD before the other branch if we just - * ensure that branch1 == opt->branch1. So, simply flip arguments - * around if we don't have that. - */ - if (ren2 && ren1->branch != opt->branch1) { - setup_rename_conflict_info(rename_type, opt, ren2, ren1); - return; - } - - CALLOC_ARRAY(ci, 1); - ci->rename_type = rename_type; - ci->ren1 = ren1; - ci->ren2 = ren2; - - ci->ren1->dst_entry->processed = 0; - ci->ren1->dst_entry->rename_conflict_info = ci; - ci->ren1->dst_entry->rename_conflict_info_owned = 1; - if (ren2) { - ci->ren2->dst_entry->rename_conflict_info = ci; - } -} - -static int show(struct merge_options *opt, int v) -{ - return (!opt->priv->call_depth && opt->verbosity >= v) || - opt->verbosity >= 5; -} - -__attribute__((format (printf, 3, 4))) -static void output(struct merge_options *opt, int v, const char *fmt, ...) -{ - va_list ap; - - if (!show(opt, v)) - return; - - strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2); - - va_start(ap, fmt); - strbuf_vaddf(&opt->obuf, fmt, ap); - va_end(ap); - - strbuf_addch(&opt->obuf, '\n'); - if (!opt->buffer_output) - flush_output(opt); -} - -static void repo_output_commit_title(struct merge_options *opt, - struct repository *repo, - struct commit *commit) -{ - struct merge_remote_desc *desc; - - strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2); - desc = merge_remote_util(commit); - if (desc) - strbuf_addf(&opt->obuf, "virtual %s\n", desc->name); - else { - strbuf_repo_add_unique_abbrev(&opt->obuf, repo, - &commit->object.oid, - DEFAULT_ABBREV); - strbuf_addch(&opt->obuf, ' '); - if (repo_parse_commit(repo, commit) != 0) - strbuf_addstr(&opt->obuf, _("(bad commit)\n")); - else { - const char *title; - const char *msg = repo_get_commit_buffer(repo, commit, NULL); - int len = find_commit_subject(msg, &title); - if (len) - strbuf_addf(&opt->obuf, "%.*s\n", len, title); - repo_unuse_commit_buffer(repo, commit, msg); - } - } - flush_output(opt); -} - -static void output_commit_title(struct merge_options *opt, struct commit *commit) -{ - repo_output_commit_title(opt, the_repository, commit); -} - -static int add_cacheinfo(struct merge_options *opt, - const struct diff_filespec *blob, - const char *path, int stage, int refresh, int options) -{ - struct index_state *istate = opt->repo->index; - struct cache_entry *ce; - int ret; - - ce = make_cache_entry(istate, blob->mode, &blob->oid, path, stage, 0); - if (!ce) - return err(opt, _("add_cacheinfo failed for path '%s'; merge aborting."), path); - - ret = add_index_entry(istate, ce, options); - if (refresh) { - struct cache_entry *nce; - - nce = refresh_cache_entry(istate, ce, - CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING); - if (!nce) - return err(opt, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path); - if (nce != ce) - ret = add_index_entry(istate, nce, options); - } - return ret; -} - -static inline int merge_detect_rename(struct merge_options *opt) -{ - return (opt->detect_renames >= 0) ? opt->detect_renames : 1; -} - -static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) -{ - if (parse_tree(tree) < 0) - exit(128); - init_tree_desc(desc, &tree->object.oid, tree->buffer, tree->size); -} - -static int unpack_trees_start(struct merge_options *opt, - struct tree *common, - struct tree *head, - struct tree *merge) -{ - int rc; - struct tree_desc t[3]; - struct index_state tmp_index = INDEX_STATE_INIT(opt->repo); - - memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts)); - if (opt->priv->call_depth) - opt->priv->unpack_opts.index_only = 1; - else { - opt->priv->unpack_opts.update = 1; - /* FIXME: should only do this if !overwrite_ignore */ - opt->priv->unpack_opts.preserve_ignored = 0; - } - opt->priv->unpack_opts.merge = 1; - opt->priv->unpack_opts.head_idx = 2; - opt->priv->unpack_opts.fn = threeway_merge; - opt->priv->unpack_opts.src_index = opt->repo->index; - opt->priv->unpack_opts.dst_index = &tmp_index; - opt->priv->unpack_opts.aggressive = !merge_detect_rename(opt); - setup_unpack_trees_porcelain(&opt->priv->unpack_opts, "merge"); - - init_tree_desc_from_tree(t+0, common); - init_tree_desc_from_tree(t+1, head); - init_tree_desc_from_tree(t+2, merge); - - rc = unpack_trees(3, t, &opt->priv->unpack_opts); - cache_tree_free(&opt->repo->index->cache_tree); - - /* - * Update opt->repo->index to match the new results, AFTER saving a - * copy in opt->priv->orig_index. Update src_index to point to the - * saved copy. (verify_uptodate() checks src_index, and the original - * index is the one that had the necessary modification timestamps.) - */ - opt->priv->orig_index = *opt->repo->index; - *opt->repo->index = tmp_index; - opt->priv->unpack_opts.src_index = &opt->priv->orig_index; - - return rc; -} - -static void unpack_trees_finish(struct merge_options *opt) -{ - discard_index(&opt->priv->orig_index); - clear_unpack_trees_porcelain(&opt->priv->unpack_opts); -} - -static int save_files_dirs(const struct object_id *oid UNUSED, - struct strbuf *base, const char *path, - unsigned int mode, void *context) -{ - struct path_hashmap_entry *entry; - int baselen = base->len; - struct merge_options *opt = context; - - strbuf_addstr(base, path); - - FLEX_ALLOC_MEM(entry, path, base->buf, base->len); - hashmap_entry_init(&entry->e, fspathhash(entry->path)); - hashmap_add(&opt->priv->current_file_dir_set, &entry->e); - - strbuf_setlen(base, baselen); - return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); -} - -static void get_files_dirs(struct merge_options *opt, struct tree *tree) -{ - struct pathspec match_all; - memset(&match_all, 0, sizeof(match_all)); - read_tree(opt->repo, tree, - &match_all, save_files_dirs, opt); -} - -static int get_tree_entry_if_blob(struct repository *r, - const struct object_id *tree, - const char *path, - struct diff_filespec *dfs) -{ - int ret; - - ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode); - if (S_ISDIR(dfs->mode)) { - oidcpy(&dfs->oid, null_oid()); - dfs->mode = 0; - } - return ret; -} - -/* - * Returns an index_entry instance which doesn't have to correspond to - * a real cache entry in Git's index. - */ -static struct stage_data *insert_stage_data(struct repository *r, - const char *path, - struct tree *o, struct tree *a, struct tree *b, - struct string_list *entries) -{ - struct string_list_item *item; - struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry_if_blob(r, &o->object.oid, path, &e->stages[1]); - get_tree_entry_if_blob(r, &a->object.oid, path, &e->stages[2]); - get_tree_entry_if_blob(r, &b->object.oid, path, &e->stages[3]); - item = string_list_insert(entries, path); - item->util = e; - return e; -} - -/* - * Create a dictionary mapping file names to stage_data objects. The - * dictionary contains one entry for every path with a non-zero stage entry. - */ -static struct string_list *get_unmerged(struct index_state *istate) -{ - struct string_list *unmerged = xmalloc(sizeof(struct string_list)); - int i; - - string_list_init_dup(unmerged); - - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(istate); - for (i = 0; i < istate->cache_nr; i++) { - struct string_list_item *item; - struct stage_data *e; - const struct cache_entry *ce = istate->cache[i]; - if (!ce_stage(ce)) - continue; - - item = string_list_lookup(unmerged, ce->name); - if (!item) { - item = string_list_insert(unmerged, ce->name); - item->util = xcalloc(1, sizeof(struct stage_data)); - } - e = item->util; - e->stages[ce_stage(ce)].mode = ce->ce_mode; - oidcpy(&e->stages[ce_stage(ce)].oid, &ce->oid); - } - - return unmerged; -} - -static int string_list_df_name_compare(const char *one, const char *two) -{ - int onelen = strlen(one); - int twolen = strlen(two); - /* - * Here we only care that entries for D/F conflicts are - * adjacent, in particular with the file of the D/F conflict - * appearing before files below the corresponding directory. - * The order of the rest of the list is irrelevant for us. - * - * To achieve this, we sort with df_name_compare and provide - * the mode S_IFDIR so that D/F conflicts will sort correctly. - * We use the mode S_IFDIR for everything else for simplicity, - * since in other cases any changes in their order due to - * sorting cause no problems for us. - */ - int cmp = df_name_compare(one, onelen, S_IFDIR, - two, twolen, S_IFDIR); - /* - * Now that 'foo' and 'foo/bar' compare equal, we have to make sure - * that 'foo' comes before 'foo/bar'. - */ - if (cmp) - return cmp; - return onelen - twolen; -} - -static void record_df_conflict_files(struct merge_options *opt, - struct string_list *entries) -{ - /* If there is a D/F conflict and the file for such a conflict - * currently exists in the working tree, we want to allow it to be - * removed to make room for the corresponding directory if needed. - * The files underneath the directories of such D/F conflicts will - * be processed before the corresponding file involved in the D/F - * conflict. If the D/F directory ends up being removed by the - * merge, then we won't have to touch the D/F file. If the D/F - * directory needs to be written to the working copy, then the D/F - * file will simply be removed (in make_room_for_path()) to make - * room for the necessary paths. Note that if both the directory - * and the file need to be present, then the D/F file will be - * reinstated with a new unique name at the time it is processed. - */ - struct string_list df_sorted_entries = STRING_LIST_INIT_NODUP; - const char *last_file = NULL; - int last_len = 0; - int i; - - /* - * If we're merging merge-bases, we don't want to bother with - * any working directory changes. - */ - if (opt->priv->call_depth) - return; - - /* Ensure D/F conflicts are adjacent in the entries list. */ - for (i = 0; i < entries->nr; i++) { - struct string_list_item *next = &entries->items[i]; - string_list_append(&df_sorted_entries, next->string)->util = - next->util; - } - df_sorted_entries.cmp = string_list_df_name_compare; - string_list_sort(&df_sorted_entries); - - string_list_clear(&opt->priv->df_conflict_file_set, 1); - for (i = 0; i < df_sorted_entries.nr; i++) { - const char *path = df_sorted_entries.items[i].string; - int len = strlen(path); - struct stage_data *e = df_sorted_entries.items[i].util; - - /* - * Check if last_file & path correspond to a D/F conflict; - * i.e. whether path is last_file+'/'+<something>. - * If so, record that it's okay to remove last_file to make - * room for path and friends if needed. - */ - if (last_file && - len > last_len && - memcmp(path, last_file, last_len) == 0 && - path[last_len] == '/') { - string_list_insert(&opt->priv->df_conflict_file_set, last_file); - } - - /* - * Determine whether path could exist as a file in the - * working directory as a possible D/F conflict. This - * will only occur when it exists in stage 2 as a - * file. - */ - if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) { - last_file = path; - last_len = len; - } else { - last_file = NULL; - } - } - string_list_clear(&df_sorted_entries, 0); -} - -static int update_stages(struct merge_options *opt, const char *path, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b) -{ - - /* - * NOTE: It is usually a bad idea to call update_stages on a path - * before calling update_file on that same path, since it can - * sometimes lead to spurious "refusing to lose untracked file..." - * messages from update_file (via make_room_for path via - * would_lose_untracked). Instead, reverse the order of the calls - * (executing update_file first and then update_stages). - */ - int clear = 1; - int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK; - if (clear) - if (remove_file_from_index(opt->repo->index, path)) - return -1; - if (o) - if (add_cacheinfo(opt, o, path, 1, 0, options)) - return -1; - if (a) - if (add_cacheinfo(opt, a, path, 2, 0, options)) - return -1; - if (b) - if (add_cacheinfo(opt, b, path, 3, 0, options)) - return -1; - return 0; -} - -static void update_entry(struct stage_data *entry, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b) -{ - entry->processed = 0; - entry->stages[1].mode = o->mode; - entry->stages[2].mode = a->mode; - entry->stages[3].mode = b->mode; - oidcpy(&entry->stages[1].oid, &o->oid); - oidcpy(&entry->stages[2].oid, &a->oid); - oidcpy(&entry->stages[3].oid, &b->oid); -} - -static int remove_file(struct merge_options *opt, int clean, - const char *path, int no_wd) -{ - int update_cache = opt->priv->call_depth || clean; - int update_working_directory = !opt->priv->call_depth && !no_wd; - - if (update_cache) { - if (remove_file_from_index(opt->repo->index, path)) - return -1; - } - if (update_working_directory) { - if (ignore_case) { - struct cache_entry *ce; - ce = index_file_exists(opt->repo->index, path, strlen(path), - ignore_case); - if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name)) - return 0; - } - if (remove_path(path)) - return -1; - } - return 0; -} - -/* add a string to a strbuf, but converting "/" to "_" */ -static void add_flattened_path(struct strbuf *out, const char *s) -{ - size_t i = out->len; - strbuf_addstr(out, s); - for (; i < out->len; i++) - if (out->buf[i] == '/') - out->buf[i] = '_'; -} - -static char *unique_path(struct merge_options *opt, - const char *path, - const char *branch) -{ - struct path_hashmap_entry *entry; - struct strbuf newpath = STRBUF_INIT; - int suffix = 0; - size_t base_len; - - strbuf_addf(&newpath, "%s~", path); - add_flattened_path(&newpath, branch); - - base_len = newpath.len; - while (hashmap_get_from_hash(&opt->priv->current_file_dir_set, - fspathhash(newpath.buf), newpath.buf) || - (!opt->priv->call_depth && file_exists(newpath.buf))) { - strbuf_setlen(&newpath, base_len); - strbuf_addf(&newpath, "_%d", suffix++); - } - - FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len); - hashmap_entry_init(&entry->e, fspathhash(entry->path)); - hashmap_add(&opt->priv->current_file_dir_set, &entry->e); - return strbuf_detach(&newpath, NULL); -} - -/** - * Check whether a directory in the index is in the way of an incoming - * file. Return 1 if so. If check_working_copy is non-zero, also - * check the working directory. If empty_ok is non-zero, also return - * 0 in the case where the working-tree dir exists but is empty. - */ -static int dir_in_way(struct index_state *istate, const char *path, - int check_working_copy, int empty_ok) -{ - int pos; - struct strbuf dirpath = STRBUF_INIT; - struct stat st; - - strbuf_addstr(&dirpath, path); - strbuf_addch(&dirpath, '/'); - - pos = index_name_pos(istate, dirpath.buf, dirpath.len); - - if (pos < 0) - pos = -1 - pos; - if (pos < istate->cache_nr && - !strncmp(dirpath.buf, istate->cache[pos]->name, dirpath.len)) { - strbuf_release(&dirpath); - return 1; - } - - strbuf_release(&dirpath); - return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode) && - !(empty_ok && is_empty_dir(path)) && - !has_symlink_leading_path(path, strlen(path)); -} - -/* - * Returns whether path was tracked in the index before the merge started, - * and its oid and mode match the specified values - */ -static int was_tracked_and_matches(struct merge_options *opt, const char *path, - const struct diff_filespec *blob) -{ - int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path)); - struct cache_entry *ce; - - if (0 > pos) - /* we were not tracking this path before the merge */ - return 0; - - /* See if the file we were tracking before matches */ - ce = opt->priv->orig_index.cache[pos]; - return (oideq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode); -} - -/* - * Returns whether path was tracked in the index before the merge started - */ -static int was_tracked(struct merge_options *opt, const char *path) -{ - int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path)); - - if (0 <= pos) - /* we were tracking this path before the merge */ - return 1; - - return 0; -} - -static int would_lose_untracked(struct merge_options *opt, const char *path) -{ - struct index_state *istate = opt->repo->index; - - /* - * This may look like it can be simplified to: - * return !was_tracked(opt, path) && file_exists(path) - * but it can't. This function needs to know whether path was in - * the working tree due to EITHER having been tracked in the index - * before the merge OR having been put into the working copy and - * index by unpack_trees(). Due to that either-or requirement, we - * check the current index instead of the original one. - * - * Note that we do not need to worry about merge-recursive itself - * updating the index after unpack_trees() and before calling this - * function, because we strictly require all code paths in - * merge-recursive to update the working tree first and the index - * second. Doing otherwise would break - * update_file()/would_lose_untracked(); see every comment in this - * file which mentions "update_stages". - */ - int pos = index_name_pos(istate, path, strlen(path)); - - if (pos < 0) - pos = -1 - pos; - while (pos < istate->cache_nr && - !strcmp(path, istate->cache[pos]->name)) { - /* - * If stage #0, it is definitely tracked. - * If it has stage #2 then it was tracked - * before this merge started. All other - * cases the path was not tracked. - */ - switch (ce_stage(istate->cache[pos])) { - case 0: - case 2: - return 0; - } - pos++; - } - return file_exists(path); -} - -static int was_dirty(struct merge_options *opt, const char *path) -{ - struct cache_entry *ce; - int dirty = 1; - - if (opt->priv->call_depth || !was_tracked(opt, path)) - return !dirty; - - ce = index_file_exists(opt->priv->unpack_opts.src_index, - path, strlen(path), ignore_case); - dirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0; - return dirty; -} - -static int make_room_for_path(struct merge_options *opt, const char *path) -{ - int status, i; - const char *msg = _("failed to create path '%s'%s"); - - /* Unlink any D/F conflict files that are in the way */ - for (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) { - const char *df_path = opt->priv->df_conflict_file_set.items[i].string; - size_t pathlen = strlen(path); - size_t df_pathlen = strlen(df_path); - if (df_pathlen < pathlen && - path[df_pathlen] == '/' && - strncmp(path, df_path, df_pathlen) == 0) { - output(opt, 3, - _("Removing %s to make room for subdirectory\n"), - df_path); - unlink(df_path); - unsorted_string_list_delete_item(&opt->priv->df_conflict_file_set, - i, 0); - break; - } - } - - /* Make sure leading directories are created */ - status = safe_create_leading_directories_const(path); - if (status) { - if (status == SCLD_EXISTS) - /* something else exists */ - return err(opt, msg, path, _(": perhaps a D/F conflict?")); - return err(opt, msg, path, ""); - } - - /* - * Do not unlink a file in the work tree if we are not - * tracking it. - */ - if (would_lose_untracked(opt, path)) - return err(opt, _("refusing to lose untracked file at '%s'"), - path); - - /* Successful unlink is good.. */ - if (!unlink(path)) - return 0; - /* .. and so is no existing file */ - if (errno == ENOENT) - return 0; - /* .. but not some other error (who really cares what?) */ - return err(opt, msg, path, _(": perhaps a D/F conflict?")); -} - -static int update_file_flags(struct merge_options *opt, - const struct diff_filespec *contents, - const char *path, - int update_cache, - int update_wd) -{ - int ret = 0; - - if (opt->priv->call_depth) - update_wd = 0; - - if (update_wd) { - enum object_type type; - void *buf; - unsigned long size; - - if (S_ISGITLINK(contents->mode)) { - /* - * We may later decide to recursively descend into - * the submodule directory and update its index - * and/or work tree, but we do not do that now. - */ - update_wd = 0; - goto update_index; - } - - buf = repo_read_object_file(the_repository, &contents->oid, - &type, &size); - if (!buf) { - ret = err(opt, _("cannot read object %s '%s'"), - oid_to_hex(&contents->oid), path); - goto free_buf; - } - if (type != OBJ_BLOB) { - ret = err(opt, _("blob expected for %s '%s'"), - oid_to_hex(&contents->oid), path); - goto free_buf; - } - if (S_ISREG(contents->mode)) { - struct strbuf strbuf = STRBUF_INIT; - if (convert_to_working_tree(opt->repo->index, - path, buf, size, &strbuf, NULL)) { - free(buf); - size = strbuf.len; - buf = strbuf_detach(&strbuf, NULL); - } - } - - if (make_room_for_path(opt, path) < 0) { - update_wd = 0; - goto free_buf; - } - if (S_ISREG(contents->mode) || - (!has_symlinks && S_ISLNK(contents->mode))) { - int fd; - int mode = (contents->mode & 0100 ? 0777 : 0666); - - fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); - if (fd < 0) { - ret = err(opt, _("failed to open '%s': %s"), - path, strerror(errno)); - goto free_buf; - } - write_in_full(fd, buf, size); - close(fd); - } else if (S_ISLNK(contents->mode)) { - char *lnk = xmemdupz(buf, size); - safe_create_leading_directories_const(path); - unlink(path); - if (symlink(lnk, path)) - ret = err(opt, _("failed to symlink '%s': %s"), - path, strerror(errno)); - free(lnk); - } else - ret = err(opt, - _("do not know what to do with %06o %s '%s'"), - contents->mode, oid_to_hex(&contents->oid), path); - free_buf: - free(buf); - } -update_index: - if (!ret && update_cache) { - int refresh = (!opt->priv->call_depth && - contents->mode != S_IFGITLINK); - if (add_cacheinfo(opt, contents, path, 0, refresh, - ADD_CACHE_OK_TO_ADD)) - return -1; - } - return ret; -} - -static int update_file(struct merge_options *opt, - int clean, - const struct diff_filespec *contents, - const char *path) -{ - return update_file_flags(opt, contents, path, - opt->priv->call_depth || clean, !opt->priv->call_depth); -} - -/* Low level file merging, update and removal */ - -struct merge_file_info { - struct diff_filespec blob; /* mostly use oid & mode; sometimes path */ - unsigned clean:1, - merge:1; -}; - -static int merge_3way(struct merge_options *opt, - mmbuffer_t *result_buf, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b, - const char *branch1, - const char *branch2, - const int extra_marker_size) -{ - mmfile_t orig, src1, src2; - struct ll_merge_options ll_opts = LL_MERGE_OPTIONS_INIT; - char *base, *name1, *name2; - enum ll_merge_result merge_status; - - ll_opts.renormalize = opt->renormalize; - ll_opts.extra_marker_size = extra_marker_size; - ll_opts.xdl_opts = opt->xdl_opts; - ll_opts.conflict_style = opt->conflict_style; - - if (opt->priv->call_depth) { - ll_opts.virtual_ancestor = 1; - ll_opts.variant = 0; - } else { - switch (opt->recursive_variant) { - case MERGE_VARIANT_OURS: - ll_opts.variant = XDL_MERGE_FAVOR_OURS; - break; - case MERGE_VARIANT_THEIRS: - ll_opts.variant = XDL_MERGE_FAVOR_THEIRS; - break; - default: - ll_opts.variant = 0; - break; - } - } - - assert(a->path && b->path && o->path && opt->ancestor); - if (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) { - base = mkpathdup("%s:%s", opt->ancestor, o->path); - name1 = mkpathdup("%s:%s", branch1, a->path); - name2 = mkpathdup("%s:%s", branch2, b->path); - } else { - base = mkpathdup("%s", opt->ancestor); - name1 = mkpathdup("%s", branch1); - name2 = mkpathdup("%s", branch2); - } - - read_mmblob(&orig, &o->oid); - read_mmblob(&src1, &a->oid); - read_mmblob(&src2, &b->oid); - - /* - * FIXME: Using a->path for normalization rules in ll_merge could be - * wrong if we renamed from a->path to b->path. We should use the - * target path for where the file will be written. - */ - merge_status = ll_merge(result_buf, a->path, &orig, base, - &src1, name1, &src2, name2, - opt->repo->index, &ll_opts); - if (merge_status == LL_MERGE_BINARY_CONFLICT) - warning("Cannot merge binary files: %s (%s vs. %s)", - a->path, name1, name2); - - free(base); - free(name1); - free(name2); - free(orig.ptr); - free(src1.ptr); - free(src2.ptr); - return merge_status; -} - -static int find_first_merges(struct repository *repo, - struct object_array *result, const char *path, - struct commit *a, struct commit *b) -{ - int i, j; - struct object_array merges = OBJECT_ARRAY_INIT; - struct commit *commit; - int contains_another; - - char merged_revision[GIT_MAX_HEXSZ + 2]; - const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path", - "--all", merged_revision, NULL }; - struct rev_info revs; - struct setup_revision_opt rev_opts; - - memset(result, 0, sizeof(struct object_array)); - memset(&rev_opts, 0, sizeof(rev_opts)); - - /* get all revisions that merge commit a */ - xsnprintf(merged_revision, sizeof(merged_revision), "^%s", - oid_to_hex(&a->object.oid)); - repo_init_revisions(repo, &revs, NULL); - /* FIXME: can't handle linked worktrees in submodules yet */ - revs.single_worktree = path != NULL; - setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); - - /* save all revisions from the above list that contain b */ - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - while ((commit = get_revision(&revs)) != NULL) { - struct object *o = &(commit->object); - int ret = repo_in_merge_bases(repo, b, commit); - if (ret < 0) { - object_array_clear(&merges); - release_revisions(&revs); - return ret; - } - if (ret) - add_object_array(o, NULL, &merges); - } - reset_revision_walk(); - - /* Now we've got all merges that contain a and b. Prune all - * merges that contain another found merge and save them in - * result. - */ - for (i = 0; i < merges.nr; i++) { - struct commit *m1 = (struct commit *) merges.objects[i].item; - - contains_another = 0; - for (j = 0; j < merges.nr; j++) { - struct commit *m2 = (struct commit *) merges.objects[j].item; - if (i != j) { - int ret = repo_in_merge_bases(repo, m2, m1); - if (ret < 0) { - object_array_clear(&merges); - release_revisions(&revs); - return ret; - } - if (ret > 0) { - contains_another = 1; - break; - } - } - } - - if (!contains_another) - add_object_array(merges.objects[i].item, NULL, result); - } - - object_array_clear(&merges); - release_revisions(&revs); - return result->nr; -} - -static void print_commit(struct repository *repo, struct commit *commit) -{ - struct strbuf sb = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - ctx.date_mode.type = DATE_NORMAL; - /* FIXME: Merge this with output_commit_title() */ - ASSERT(!merge_remote_util(commit)); - repo_format_commit_message(repo, commit, " %h: %m %s", &sb, &ctx); - fprintf(stderr, "%s\n", sb.buf); - strbuf_release(&sb); -} - -static int is_valid(const struct diff_filespec *dfs) -{ - return dfs->mode != 0 && !is_null_oid(&dfs->oid); -} - -static int merge_submodule(struct merge_options *opt, - struct object_id *result, const char *path, - const struct object_id *base, const struct object_id *a, - const struct object_id *b) -{ - struct repository subrepo; - int ret = 0, ret2; - struct commit *commit_base, *commit_a, *commit_b; - int parent_count; - struct object_array merges; - - int i; - int search = !opt->priv->call_depth; - - /* store a in result in case we fail */ - /* FIXME: This is the WRONG resolution for the recursive case when - * we need to be careful to avoid accidentally matching either side. - * Should probably use o instead there, much like we do for merging - * binaries. - */ - oidcpy(result, a); - - /* we can not handle deletion conflicts */ - if (is_null_oid(base)) - return 0; - if (is_null_oid(a)) - return 0; - if (is_null_oid(b)) - return 0; - - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { - output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path); - return 0; - } - - if (!(commit_base = lookup_commit_reference(&subrepo, base)) || - !(commit_a = lookup_commit_reference(&subrepo, a)) || - !(commit_b = lookup_commit_reference(&subrepo, b))) { - output(opt, 1, _("Failed to merge submodule %s (commits not present)"), path); - goto cleanup; - } - - /* check whether both changes are forward */ - ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_a); - if (ret2 < 0) { - output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); - ret = -1; - goto cleanup; - } - if (ret2 > 0) - ret2 = repo_in_merge_bases(&subrepo, commit_base, commit_b); - if (ret2 < 0) { - output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); - ret = -1; - goto cleanup; - } - if (!ret2) { - output(opt, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path); - goto cleanup; - } - - /* Case #1: a is contained in b or vice versa */ - ret2 = repo_in_merge_bases(&subrepo, commit_a, commit_b); - if (ret2 < 0) { - output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); - ret = -1; - goto cleanup; - } - if (ret2) { - oidcpy(result, b); - if (show(opt, 3)) { - output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path); - repo_output_commit_title(opt, &subrepo, commit_b); - } else if (show(opt, 2)) - output(opt, 2, _("Fast-forwarding submodule %s"), path); - else - ; /* no output */ - - ret = 1; - goto cleanup; - } - ret2 = repo_in_merge_bases(&subrepo, commit_b, commit_a); - if (ret2 < 0) { - output(opt, 1, _("Failed to merge submodule %s (repository corrupt)"), path); - ret = -1; - goto cleanup; - } - if (ret2) { - oidcpy(result, a); - if (show(opt, 3)) { - output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path); - repo_output_commit_title(opt, &subrepo, commit_a); - } else if (show(opt, 2)) - output(opt, 2, _("Fast-forwarding submodule %s"), path); - else - ; /* no output */ - - ret = 1; - goto cleanup; - } - - /* - * Case #2: There are one or more merges that contain a and b in - * the submodule. If there is only one, then present it as a - * suggestion to the user, but leave it marked unmerged so the - * user needs to confirm the resolution. - */ - - /* Skip the search if makes no sense to the calling context. */ - if (!search) - goto cleanup; - - /* find commit which merges them */ - parent_count = find_first_merges(&subrepo, &merges, path, - commit_a, commit_b); - switch (parent_count) { - case -1: - output(opt, 1,_("Failed to merge submodule %s (repository corrupt)"), path); - ret = -1; - break; - case 0: - output(opt, 1, _("Failed to merge submodule %s (merge following commits not found)"), path); - break; - - case 1: - output(opt, 1, _("Failed to merge submodule %s (not fast-forward)"), path); - output(opt, 2, _("Found a possible merge resolution for the submodule:\n")); - print_commit(&subrepo, (struct commit *) merges.objects[0].item); - output(opt, 2, _( - "If this is correct simply add it to the index " - "for example\n" - "by using:\n\n" - " git update-index --cacheinfo 160000 %s \"%s\"\n\n" - "which will accept this suggestion.\n"), - oid_to_hex(&merges.objects[0].item->oid), path); - break; - - default: - output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path); - for (i = 0; i < merges.nr; i++) - print_commit(&subrepo, (struct commit *) merges.objects[i].item); - } - - object_array_clear(&merges); -cleanup: - repo_clear(&subrepo); - return ret; -} - -static int merge_mode_and_contents(struct merge_options *opt, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b, - const char *filename, - const char *branch1, - const char *branch2, - const int extra_marker_size, - struct merge_file_info *result) -{ - if (opt->branch1 != branch1) { - /* - * It's weird getting a reverse merge with HEAD on the bottom - * side of the conflict markers and the other branch on the - * top. Fix that. - */ - return merge_mode_and_contents(opt, o, b, a, - filename, - branch2, branch1, - extra_marker_size, result); - } - - result->merge = 0; - result->clean = 1; - - if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { - result->clean = 0; - /* - * FIXME: This is a bad resolution for recursive case; for - * the recursive case we want something that is unlikely to - * accidentally match either side. Also, while it makes - * sense to prefer regular files over symlinks, it doesn't - * make sense to prefer regular files over submodules. - */ - if (S_ISREG(a->mode)) { - result->blob.mode = a->mode; - oidcpy(&result->blob.oid, &a->oid); - } else { - result->blob.mode = b->mode; - oidcpy(&result->blob.oid, &b->oid); - } - } else { - if (!oideq(&a->oid, &o->oid) && !oideq(&b->oid, &o->oid)) - result->merge = 1; - - /* - * Merge modes - */ - if (a->mode == b->mode || a->mode == o->mode) - result->blob.mode = b->mode; - else { - result->blob.mode = a->mode; - if (b->mode != o->mode) { - result->clean = 0; - result->merge = 1; - } - } - - if (oideq(&a->oid, &b->oid) || oideq(&a->oid, &o->oid)) - oidcpy(&result->blob.oid, &b->oid); - else if (oideq(&b->oid, &o->oid)) - oidcpy(&result->blob.oid, &a->oid); - else if (S_ISREG(a->mode)) { - mmbuffer_t result_buf; - int ret = 0, merge_status; - - merge_status = merge_3way(opt, &result_buf, o, a, b, - branch1, branch2, - extra_marker_size); - - if ((merge_status < 0) || !result_buf.ptr) - ret = err(opt, _("failed to execute internal merge")); - - if (!ret && - write_object_file(result_buf.ptr, result_buf.size, - OBJ_BLOB, &result->blob.oid)) - ret = err(opt, _("unable to add %s to database"), - a->path); - - free(result_buf.ptr); - if (ret) - return ret; - /* FIXME: bug, what if modes didn't match? */ - result->clean = (merge_status == 0); - } else if (S_ISGITLINK(a->mode)) { - int clean = merge_submodule(opt, &result->blob.oid, - o->path, - &o->oid, - &a->oid, - &b->oid); - if (clean < 0) - return -1; - result->clean = clean; - } else if (S_ISLNK(a->mode)) { - switch (opt->recursive_variant) { - case MERGE_VARIANT_NORMAL: - oidcpy(&result->blob.oid, &a->oid); - if (!oideq(&a->oid, &b->oid)) - result->clean = 0; - break; - case MERGE_VARIANT_OURS: - oidcpy(&result->blob.oid, &a->oid); - break; - case MERGE_VARIANT_THEIRS: - oidcpy(&result->blob.oid, &b->oid); - break; - } - } else - BUG("unsupported object type in the tree"); - } - - if (result->merge) - output(opt, 2, _("Auto-merging %s"), filename); - - return 0; -} - -static int handle_rename_via_dir(struct merge_options *opt, - struct rename_conflict_info *ci) -{ - /* - * Handle file adds that need to be renamed due to directory rename - * detection. This differs from handle_rename_normal, because - * there is no content merge to do; just move the file into the - * desired final location. - */ - const struct rename *ren = ci->ren1; - const struct diff_filespec *dest = ren->pair->two; - char *file_path = dest->path; - int mark_conflicted = (opt->detect_directory_renames == - MERGE_DIRECTORY_RENAMES_CONFLICT); - assert(ren->dir_rename_original_dest); - - if (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) { - mark_conflicted = 1; - file_path = unique_path(opt, dest->path, ren->branch); - output(opt, 1, _("Error: Refusing to lose untracked file at %s; " - "writing to %s instead."), - dest->path, file_path); - } - - if (mark_conflicted) { - /* - * Write the file in worktree at file_path. In the index, - * only record the file at dest->path in the appropriate - * higher stage. - */ - if (update_file(opt, 0, dest, file_path)) - return -1; - if (file_path != dest->path) - free(file_path); - if (update_stages(opt, dest->path, NULL, - ren->branch == opt->branch1 ? dest : NULL, - ren->branch == opt->branch1 ? NULL : dest)) - return -1; - return 0; /* not clean, but conflicted */ - } else { - /* Update dest->path both in index and in worktree */ - if (update_file(opt, 1, dest, dest->path)) - return -1; - return 1; /* clean */ - } -} - -static int handle_change_delete(struct merge_options *opt, - const char *path, const char *old_path, - const struct diff_filespec *o, - const struct diff_filespec *changed, - const char *change_branch, - const char *delete_branch, - const char *change, const char *change_past) -{ - char *alt_path = NULL; - const char *update_path = path; - int ret = 0; - - if (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) || - (!opt->priv->call_depth && would_lose_untracked(opt, path))) { - update_path = alt_path = unique_path(opt, path, change_branch); - } - - if (opt->priv->call_depth) { - /* - * We cannot arbitrarily accept either a_sha or b_sha as - * correct; since there is no true "middle point" between - * them, simply reuse the base version for virtual merge base. - */ - ret = remove_file_from_index(opt->repo->index, path); - if (!ret) - ret = update_file(opt, 0, o, update_path); - } else { - /* - * Despite the four nearly duplicate messages and argument - * lists below and the ugliness of the nested if-statements, - * having complete messages makes the job easier for - * translators. - * - * The slight variance among the cases is due to the fact - * that: - * 1) directory/file conflicts (in effect if - * !alt_path) could cause us to need to write the - * file to a different path. - * 2) renames (in effect if !old_path) could mean that - * there are two names for the path that the user - * may know the file by. - */ - if (!alt_path) { - if (!old_path) { - output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree."), - change, path, delete_branch, change_past, - change_branch, change_branch, path); - } else { - output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s to %s in %s. Version %s of %s left in tree."), - change, old_path, delete_branch, change_past, path, - change_branch, change_branch, path); - } - } else { - if (!old_path) { - output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree at %s."), - change, path, delete_branch, change_past, - change_branch, change_branch, path, alt_path); - } else { - output(opt, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s to %s in %s. Version %s of %s left in tree at %s."), - change, old_path, delete_branch, change_past, path, - change_branch, change_branch, path, alt_path); - } - } - /* - * No need to call update_file() on path when change_branch == - * opt->branch1 && !alt_path, since that would needlessly touch - * path. We could call update_file_flags() with update_cache=0 - * and update_wd=0, but that's a no-op. - */ - if (change_branch != opt->branch1 || alt_path) - ret = update_file(opt, 0, changed, update_path); - } - free(alt_path); - - return ret; -} - -static int handle_rename_delete(struct merge_options *opt, - struct rename_conflict_info *ci) -{ - const struct rename *ren = ci->ren1; - const struct diff_filespec *orig = ren->pair->one; - const struct diff_filespec *dest = ren->pair->two; - const char *rename_branch = ren->branch; - const char *delete_branch = (opt->branch1 == ren->branch ? - opt->branch2 : opt->branch1); - - if (handle_change_delete(opt, - opt->priv->call_depth ? orig->path : dest->path, - opt->priv->call_depth ? NULL : orig->path, - orig, dest, - rename_branch, delete_branch, - _("rename"), _("renamed"))) - return -1; - - if (opt->priv->call_depth) - return remove_file_from_index(opt->repo->index, dest->path); - else - return update_stages(opt, dest->path, NULL, - rename_branch == opt->branch1 ? dest : NULL, - rename_branch == opt->branch1 ? NULL : dest); -} - -static int handle_file_collision(struct merge_options *opt, - const char *collide_path, - const char *prev_path1, - const char *prev_path2, - const char *branch1, const char *branch2, - struct diff_filespec *a, - struct diff_filespec *b) -{ - struct merge_file_info mfi; - struct diff_filespec null; - char *alt_path = NULL; - const char *update_path = collide_path; - - /* - * It's easiest to get the correct things into stage 2 and 3, and - * to make sure that the content merge puts HEAD before the other - * branch if we just ensure that branch1 == opt->branch1. So, simply - * flip arguments around if we don't have that. - */ - if (branch1 != opt->branch1) { - return handle_file_collision(opt, collide_path, - prev_path2, prev_path1, - branch2, branch1, - b, a); - } - - /* Remove rename sources if rename/add or rename/rename(2to1) */ - if (prev_path1) - remove_file(opt, 1, prev_path1, - opt->priv->call_depth || would_lose_untracked(opt, prev_path1)); - if (prev_path2) - remove_file(opt, 1, prev_path2, - opt->priv->call_depth || would_lose_untracked(opt, prev_path2)); - - /* - * Remove the collision path, if it wouldn't cause dirty contents - * or an untracked file to get lost. We'll either overwrite with - * merged contents, or just write out to differently named files. - */ - if (was_dirty(opt, collide_path)) { - output(opt, 1, _("Refusing to lose dirty file at %s"), - collide_path); - update_path = alt_path = unique_path(opt, collide_path, "merged"); - } else if (would_lose_untracked(opt, collide_path)) { - /* - * Only way we get here is if both renames were from - * a directory rename AND user had an untracked file - * at the location where both files end up after the - * two directory renames. See testcase 10d of t6043. - */ - output(opt, 1, _("Refusing to lose untracked file at " - "%s, even though it's in the way."), - collide_path); - update_path = alt_path = unique_path(opt, collide_path, "merged"); - } else { - /* - * FIXME: It's possible that the two files are identical - * and that the current working copy happens to match, in - * which case we are unnecessarily touching the working - * tree file. It's not a likely enough scenario that I - * want to code up the checks for it and a better fix is - * available if we restructure how unpack_trees() and - * merge-recursive interoperate anyway, so punting for - * now... - */ - remove_file(opt, 0, collide_path, 0); - } - - /* Store things in diff_filespecs for functions that need it */ - null.path = (char *)collide_path; - oidcpy(&null.oid, null_oid()); - null.mode = 0; - - if (merge_mode_and_contents(opt, &null, a, b, collide_path, - branch1, branch2, opt->priv->call_depth * 2, &mfi)) - return -1; - mfi.clean &= !alt_path; - if (update_file(opt, mfi.clean, &mfi.blob, update_path)) - return -1; - if (!mfi.clean && !opt->priv->call_depth && - update_stages(opt, collide_path, NULL, a, b)) - return -1; - free(alt_path); - /* - * FIXME: If both a & b both started with conflicts (only possible - * if they came from a rename/rename(2to1)), but had IDENTICAL - * contents including those conflicts, then in the next line we claim - * it was clean. If someone cares about this case, we should have the - * caller notify us if we started with conflicts. - */ - return mfi.clean; -} - -static int handle_rename_add(struct merge_options *opt, - struct rename_conflict_info *ci) -{ - /* a was renamed to c, and a separate c was added. */ - struct diff_filespec *a = ci->ren1->pair->one; - struct diff_filespec *c = ci->ren1->pair->two; - char *path = c->path; - char *prev_path_desc; - struct merge_file_info mfi; - - const char *rename_branch = ci->ren1->branch; - const char *add_branch = (opt->branch1 == rename_branch ? - opt->branch2 : opt->branch1); - int other_stage = (ci->ren1->branch == opt->branch1 ? 3 : 2); - - output(opt, 1, _("CONFLICT (rename/add): " - "Rename %s->%s in %s. Added %s in %s"), - a->path, c->path, rename_branch, - c->path, add_branch); - - prev_path_desc = xstrfmt("version of %s from %s", path, a->path); - ci->ren1->src_entry->stages[other_stage].path = a->path; - if (merge_mode_and_contents(opt, a, c, - &ci->ren1->src_entry->stages[other_stage], - prev_path_desc, - opt->branch1, opt->branch2, - 1 + opt->priv->call_depth * 2, &mfi)) - return -1; - free(prev_path_desc); - - ci->ren1->dst_entry->stages[other_stage].path = mfi.blob.path = c->path; - return handle_file_collision(opt, - c->path, a->path, NULL, - rename_branch, add_branch, - &mfi.blob, - &ci->ren1->dst_entry->stages[other_stage]); -} - -static char *find_path_for_conflict(struct merge_options *opt, - const char *path, - const char *branch1, - const char *branch2) -{ - char *new_path = NULL; - if (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) { - new_path = unique_path(opt, path, branch1); - output(opt, 1, _("%s is a directory in %s adding " - "as %s instead"), - path, branch2, new_path); - } else if (would_lose_untracked(opt, path)) { - new_path = unique_path(opt, path, branch1); - output(opt, 1, _("Refusing to lose untracked file" - " at %s; adding as %s instead"), - path, new_path); - } - - return new_path; -} - -/* - * Toggle the stage number between "ours" and "theirs" (2 and 3). - */ -static inline int flip_stage(int stage) -{ - return (2 + 3) - stage; -} - -static int handle_rename_rename_1to2(struct merge_options *opt, - struct rename_conflict_info *ci) -{ - /* One file was renamed in both branches, but to different names. */ - struct merge_file_info mfi; - struct diff_filespec *add; - struct diff_filespec *o = ci->ren1->pair->one; - struct diff_filespec *a = ci->ren1->pair->two; - struct diff_filespec *b = ci->ren2->pair->two; - char *path_desc; - - output(opt, 1, _("CONFLICT (rename/rename): " - "Rename \"%s\"->\"%s\" in branch \"%s\" " - "rename \"%s\"->\"%s\" in \"%s\"%s"), - o->path, a->path, ci->ren1->branch, - o->path, b->path, ci->ren2->branch, - opt->priv->call_depth ? _(" (left unresolved)") : ""); - - path_desc = xstrfmt("%s and %s, both renamed from %s", - a->path, b->path, o->path); - if (merge_mode_and_contents(opt, o, a, b, path_desc, - ci->ren1->branch, ci->ren2->branch, - opt->priv->call_depth * 2, &mfi)) - return -1; - free(path_desc); - - if (opt->priv->call_depth) - remove_file_from_index(opt->repo->index, o->path); - - /* - * For each destination path, we need to see if there is a - * rename/add collision. If not, we can write the file out - * to the specified location. - */ - add = &ci->ren1->dst_entry->stages[flip_stage(2)]; - if (is_valid(add)) { - add->path = mfi.blob.path = a->path; - if (handle_file_collision(opt, a->path, - NULL, NULL, - ci->ren1->branch, - ci->ren2->branch, - &mfi.blob, add) < 0) - return -1; - } else { - char *new_path = find_path_for_conflict(opt, a->path, - ci->ren1->branch, - ci->ren2->branch); - if (update_file(opt, 0, &mfi.blob, - new_path ? new_path : a->path)) - return -1; - free(new_path); - if (!opt->priv->call_depth && - update_stages(opt, a->path, NULL, a, NULL)) - return -1; - } - - if (!mfi.clean && mfi.blob.mode == a->mode && - oideq(&mfi.blob.oid, &a->oid)) { - /* - * Getting here means we were attempting to merge a binary - * blob. Since we can't merge binaries, the merge algorithm - * just takes one side. But we don't want to copy the - * contents of one side to both paths; we'd rather use the - * original content at the given path for each path. - */ - oidcpy(&mfi.blob.oid, &b->oid); - mfi.blob.mode = b->mode; - } - add = &ci->ren2->dst_entry->stages[flip_stage(3)]; - if (is_valid(add)) { - add->path = mfi.blob.path = b->path; - if (handle_file_collision(opt, b->path, - NULL, NULL, - ci->ren1->branch, - ci->ren2->branch, - add, &mfi.blob) < 0) - return -1; - } else { - char *new_path = find_path_for_conflict(opt, b->path, - ci->ren2->branch, - ci->ren1->branch); - if (update_file(opt, 0, &mfi.blob, - new_path ? new_path : b->path)) - return -1; - free(new_path); - if (!opt->priv->call_depth && - update_stages(opt, b->path, NULL, NULL, b)) - return -1; - } - - return 0; -} - -static int handle_rename_rename_2to1(struct merge_options *opt, - struct rename_conflict_info *ci) -{ - /* Two files, a & b, were renamed to the same thing, c. */ - struct diff_filespec *a = ci->ren1->pair->one; - struct diff_filespec *b = ci->ren2->pair->one; - struct diff_filespec *c1 = ci->ren1->pair->two; - struct diff_filespec *c2 = ci->ren2->pair->two; - char *path = c1->path; /* == c2->path */ - char *path_side_1_desc; - char *path_side_2_desc; - struct merge_file_info mfi_c1; - struct merge_file_info mfi_c2; - int ostage1, ostage2; - - output(opt, 1, _("CONFLICT (rename/rename): " - "Rename %s->%s in %s. " - "Rename %s->%s in %s"), - a->path, c1->path, ci->ren1->branch, - b->path, c2->path, ci->ren2->branch); - - path_side_1_desc = xstrfmt("version of %s from %s", path, a->path); - path_side_2_desc = xstrfmt("version of %s from %s", path, b->path); - ostage1 = ci->ren1->branch == opt->branch1 ? 3 : 2; - ostage2 = flip_stage(ostage1); - ci->ren1->src_entry->stages[ostage1].path = a->path; - ci->ren2->src_entry->stages[ostage2].path = b->path; - if (merge_mode_and_contents(opt, a, c1, - &ci->ren1->src_entry->stages[ostage1], - path_side_1_desc, - opt->branch1, opt->branch2, - 1 + opt->priv->call_depth * 2, &mfi_c1) || - merge_mode_and_contents(opt, b, - &ci->ren2->src_entry->stages[ostage2], - c2, path_side_2_desc, - opt->branch1, opt->branch2, - 1 + opt->priv->call_depth * 2, &mfi_c2)) - return -1; - free(path_side_1_desc); - free(path_side_2_desc); - mfi_c1.blob.path = path; - mfi_c2.blob.path = path; - - return handle_file_collision(opt, path, a->path, b->path, - ci->ren1->branch, ci->ren2->branch, - &mfi_c1.blob, &mfi_c2.blob); -} - -/* - * Get the diff_filepairs changed between o_tree and tree. - */ -static struct diff_queue_struct *get_diffpairs(struct merge_options *opt, - struct tree *o_tree, - struct tree *tree) -{ - struct diff_queue_struct *ret; - struct diff_options opts; - - repo_diff_setup(opt->repo, &opts); - opts.flags.recursive = 1; - opts.flags.rename_empty = 0; - opts.detect_rename = merge_detect_rename(opt); - /* - * We do not have logic to handle the detection of copies. In - * fact, it may not even make sense to add such logic: would we - * really want a change to a base file to be propagated through - * multiple other files by a merge? - */ - if (opts.detect_rename > DIFF_DETECT_RENAME) - opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 7000; - opts.rename_score = opt->rename_score; - opts.show_rename_progress = opt->show_rename_progress; - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_setup_done(&opts); - diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts); - diffcore_std(&opts); - if (opts.needed_rename_limit > opt->priv->needed_rename_limit) - opt->priv->needed_rename_limit = opts.needed_rename_limit; - - ret = xmalloc(sizeof(*ret)); - *ret = diff_queued_diff; - - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_queued_diff.nr = 0; - diff_queued_diff.queue = NULL; - diff_flush(&opts); - return ret; -} - -static int tree_has_path(struct repository *r, struct tree *tree, - const char *path) -{ - struct object_id hashy; - unsigned short mode_o; - - return !get_tree_entry(r, - &tree->object.oid, path, - &hashy, &mode_o); -} - -/* - * Return a new string that replaces the beginning portion (which matches - * entry->dir), with entry->new_dir. In perl-speak: - * new_path_name = (old_path =~ s/entry->dir/entry->new_dir/); - * NOTE: - * Caller must ensure that old_path starts with entry->dir + '/'. - */ -static char *apply_dir_rename(struct dir_rename_entry *entry, - const char *old_path) -{ - struct strbuf new_path = STRBUF_INIT; - int oldlen, newlen; - - if (entry->non_unique_new_dir) - return NULL; - - oldlen = strlen(entry->dir); - if (entry->new_dir.len == 0) - /* - * If someone renamed/merged a subdirectory into the root - * directory (e.g. 'some/subdir' -> ''), then we want to - * avoid returning - * '' + '/filename' - * as the rename; we need to make old_path + oldlen advance - * past the '/' character. - */ - oldlen++; - newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1; - strbuf_grow(&new_path, newlen); - strbuf_addbuf(&new_path, &entry->new_dir); - strbuf_addstr(&new_path, &old_path[oldlen]); - - return strbuf_detach(&new_path, NULL); -} - -static void get_renamed_dir_portion(const char *old_path, const char *new_path, - char **old_dir, char **new_dir) -{ - char *end_of_old, *end_of_new; - - /* Default return values: NULL, meaning no rename */ - *old_dir = NULL; - *new_dir = NULL; - - /* - * For - * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c" - * the "e/foo.c" part is the same, we just want to know that - * "a/b/c/d" was renamed to "a/b/some/thing/else" - * so, for this example, this function returns "a/b/c/d" in - * *old_dir and "a/b/some/thing/else" in *new_dir. - */ - - /* - * If the basename of the file changed, we don't care. We want - * to know which portion of the directory, if any, changed. - */ - end_of_old = strrchr(old_path, '/'); - end_of_new = strrchr(new_path, '/'); - - /* - * If end_of_old is NULL, old_path wasn't in a directory, so there - * could not be a directory rename (our rule elsewhere that a - * directory which still exists is not considered to have been - * renamed means the root directory can never be renamed -- because - * the root directory always exists). - */ - if (!end_of_old) - return; /* Note: *old_dir and *new_dir are still NULL */ - - /* - * If new_path contains no directory (end_of_new is NULL), then we - * have a rename of old_path's directory to the root directory. - */ - if (!end_of_new) { - *old_dir = xstrndup(old_path, end_of_old - old_path); - *new_dir = xstrdup(""); - return; - } - - /* Find the first non-matching character traversing backwards */ - while (*--end_of_new == *--end_of_old && - end_of_old != old_path && - end_of_new != new_path) - ; /* Do nothing; all in the while loop */ - - /* - * If both got back to the beginning of their strings, then the - * directory didn't change at all, only the basename did. - */ - if (end_of_old == old_path && end_of_new == new_path && - *end_of_old == *end_of_new) - return; /* Note: *old_dir and *new_dir are still NULL */ - - /* - * If end_of_new got back to the beginning of its string, and - * end_of_old got back to the beginning of some subdirectory, then - * we have a rename/merge of a subdirectory into the root, which - * needs slightly special handling. - * - * Note: There is no need to consider the opposite case, with a - * rename/merge of the root directory into some subdirectory - * because as noted above the root directory always exists so it - * cannot be considered to be renamed. - */ - if (end_of_new == new_path && - end_of_old != old_path && end_of_old[-1] == '/') { - *old_dir = xstrndup(old_path, --end_of_old - old_path); - *new_dir = xstrdup(""); - return; - } - - /* - * We've found the first non-matching character in the directory - * paths. That means the current characters we were looking at - * were part of the first non-matching subdir name going back from - * the end of the strings. Get the whole name by advancing both - * end_of_old and end_of_new to the NEXT '/' character. That will - * represent the entire directory rename. - * - * The reason for the increment is cases like - * a/b/star/foo/whatever.c -> a/b/tar/foo/random.c - * After dropping the basename and going back to the first - * non-matching character, we're now comparing: - * a/b/s and a/b/ - * and we want to be comparing: - * a/b/star/ and a/b/tar/ - * but without the pre-increment, the one on the right would stay - * a/b/. - */ - end_of_old = strchr(++end_of_old, '/'); - end_of_new = strchr(++end_of_new, '/'); - - /* Copy the old and new directories into *old_dir and *new_dir. */ - *old_dir = xstrndup(old_path, end_of_old - old_path); - *new_dir = xstrndup(new_path, end_of_new - new_path); -} - -static void remove_hashmap_entries(struct hashmap *dir_renames, - struct string_list *items_to_remove) -{ - int i; - struct dir_rename_entry *entry; - - for (i = 0; i < items_to_remove->nr; i++) { - entry = items_to_remove->items[i].util; - hashmap_remove(dir_renames, &entry->ent, NULL); - } - string_list_clear(items_to_remove, 0); -} - -/* - * See if there is a directory rename for path, and if there are any file - * level conflicts for the renamed location. If there is a rename and - * there are no conflicts, return the new name. Otherwise, return NULL. - */ -static char *handle_path_level_conflicts(struct merge_options *opt, - const char *path, - struct dir_rename_entry *entry, - struct hashmap *collisions, - struct tree *tree) -{ - char *new_path = NULL; - struct collision_entry *collision_ent; - int clean = 1; - struct strbuf collision_paths = STRBUF_INIT; - - /* - * entry has the mapping of old directory name to new directory name - * that we want to apply to path. - */ - new_path = apply_dir_rename(entry, path); - - if (!new_path) { - /* This should only happen when entry->non_unique_new_dir set */ - if (!entry->non_unique_new_dir) - BUG("entry->non_unique_new_dir not set and !new_path"); - output(opt, 1, _("CONFLICT (directory rename split): " - "Unclear where to place %s because directory " - "%s was renamed to multiple other directories, " - "with no destination getting a majority of the " - "files."), - path, entry->dir); - clean = 0; - return NULL; - } - - /* - * The caller needs to have ensured that it has pre-populated - * collisions with all paths that map to new_path. Do a quick check - * to ensure that's the case. - */ - collision_ent = collision_find_entry(collisions, new_path); - if (!collision_ent) - BUG("collision_ent is NULL"); - - /* - * Check for one-sided add/add/.../add conflicts, i.e. - * where implicit renames from the other side doing - * directory rename(s) can affect this side of history - * to put multiple paths into the same location. Warn - * and bail on directory renames for such paths. - */ - if (collision_ent->reported_already) { - clean = 0; - } else if (tree_has_path(opt->repo, tree, new_path)) { - collision_ent->reported_already = 1; - strbuf_add_separated_string_list(&collision_paths, ", ", - &collision_ent->source_files); - output(opt, 1, _("CONFLICT (implicit dir rename): Existing " - "file/dir at %s in the way of implicit " - "directory rename(s) putting the following " - "path(s) there: %s."), - new_path, collision_paths.buf); - clean = 0; - } else if (collision_ent->source_files.nr > 1) { - collision_ent->reported_already = 1; - strbuf_add_separated_string_list(&collision_paths, ", ", - &collision_ent->source_files); - output(opt, 1, _("CONFLICT (implicit dir rename): Cannot map " - "more than one path to %s; implicit directory " - "renames tried to put these paths there: %s"), - new_path, collision_paths.buf); - clean = 0; - } - - /* Free memory we no longer need */ - strbuf_release(&collision_paths); - if (!clean && new_path) { - free(new_path); - return NULL; - } - - return new_path; -} - -/* - * There are a couple things we want to do at the directory level: - * 1. Check for both sides renaming to the same thing, in order to avoid - * implicit renaming of files that should be left in place. (See - * testcase 6b in t6043 for details.) - * 2. Prune directory renames if there are still files left in the - * original directory. These represent a partial directory rename, - * i.e. a rename where only some of the files within the directory - * were renamed elsewhere. (Technically, this could be done earlier - * in get_directory_renames(), except that would prevent us from - * doing the previous check and thus failing testcase 6b.) - * 3. Check for rename/rename(1to2) conflicts (at the directory level). - * In the future, we could potentially record this info as well and - * omit reporting rename/rename(1to2) conflicts for each path within - * the affected directories, thus cleaning up the merge output. - * NOTE: We do NOT check for rename/rename(2to1) conflicts at the - * directory level, because merging directories is fine. If it - * causes conflicts for files within those merged directories, then - * that should be detected at the individual path level. - */ -static void handle_directory_level_conflicts(struct merge_options *opt, - struct hashmap *dir_re_head, - struct tree *head, - struct hashmap *dir_re_merge, - struct tree *merge) -{ - struct hashmap_iter iter; - struct dir_rename_entry *head_ent; - struct dir_rename_entry *merge_ent; - - struct string_list remove_from_head = STRING_LIST_INIT_NODUP; - struct string_list remove_from_merge = STRING_LIST_INIT_NODUP; - - hashmap_for_each_entry(dir_re_head, &iter, head_ent, - ent /* member name */) { - merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir); - if (merge_ent && - !head_ent->non_unique_new_dir && - !merge_ent->non_unique_new_dir && - !strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) { - /* 1. Renamed identically; remove it from both sides */ - string_list_append(&remove_from_head, - head_ent->dir)->util = head_ent; - strbuf_release(&head_ent->new_dir); - string_list_append(&remove_from_merge, - merge_ent->dir)->util = merge_ent; - strbuf_release(&merge_ent->new_dir); - } else if (tree_has_path(opt->repo, head, head_ent->dir)) { - /* 2. This wasn't a directory rename after all */ - string_list_append(&remove_from_head, - head_ent->dir)->util = head_ent; - strbuf_release(&head_ent->new_dir); - } - } - - remove_hashmap_entries(dir_re_head, &remove_from_head); - remove_hashmap_entries(dir_re_merge, &remove_from_merge); - - hashmap_for_each_entry(dir_re_merge, &iter, merge_ent, - ent /* member name */) { - head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir); - if (tree_has_path(opt->repo, merge, merge_ent->dir)) { - /* 2. This wasn't a directory rename after all */ - string_list_append(&remove_from_merge, - merge_ent->dir)->util = merge_ent; - } else if (head_ent && - !head_ent->non_unique_new_dir && - !merge_ent->non_unique_new_dir) { - /* 3. rename/rename(1to2) */ - /* - * We can assume it's not rename/rename(1to1) because - * that was case (1), already checked above. So we - * know that head_ent->new_dir and merge_ent->new_dir - * are different strings. - */ - output(opt, 1, _("CONFLICT (rename/rename): " - "Rename directory %s->%s in %s. " - "Rename directory %s->%s in %s"), - head_ent->dir, head_ent->new_dir.buf, opt->branch1, - head_ent->dir, merge_ent->new_dir.buf, opt->branch2); - string_list_append(&remove_from_head, - head_ent->dir)->util = head_ent; - strbuf_release(&head_ent->new_dir); - string_list_append(&remove_from_merge, - merge_ent->dir)->util = merge_ent; - strbuf_release(&merge_ent->new_dir); - } - } - - remove_hashmap_entries(dir_re_head, &remove_from_head); - remove_hashmap_entries(dir_re_merge, &remove_from_merge); -} - -static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs) -{ - struct hashmap *dir_renames; - struct hashmap_iter iter; - struct dir_rename_entry *entry; - int i; - - /* - * Typically, we think of a directory rename as all files from a - * certain directory being moved to a target directory. However, - * what if someone first moved two files from the original - * directory in one commit, and then renamed the directory - * somewhere else in a later commit? At merge time, we just know - * that files from the original directory went to two different - * places, and that the bulk of them ended up in the same place. - * We want each directory rename to represent where the bulk of the - * files from that directory end up; this function exists to find - * where the bulk of the files went. - * - * The first loop below simply iterates through the list of file - * renames, finding out how often each directory rename pair - * possibility occurs. - */ - dir_renames = xmalloc(sizeof(*dir_renames)); - dir_rename_init(dir_renames); - for (i = 0; i < pairs->nr; ++i) { - struct string_list_item *item; - int *count; - struct diff_filepair *pair = pairs->queue[i]; - char *old_dir, *new_dir; - - /* File not part of directory rename if it wasn't renamed */ - if (pair->status != 'R') - continue; - - get_renamed_dir_portion(pair->one->path, pair->two->path, - &old_dir, &new_dir); - if (!old_dir) - /* Directory didn't change at all; ignore this one. */ - continue; - - entry = dir_rename_find_entry(dir_renames, old_dir); - if (!entry) { - entry = xmalloc(sizeof(*entry)); - dir_rename_entry_init(entry, old_dir); - hashmap_put(dir_renames, &entry->ent); - } else { - free(old_dir); - } - item = string_list_lookup(&entry->possible_new_dirs, new_dir); - if (!item) { - item = string_list_insert(&entry->possible_new_dirs, - new_dir); - item->util = xcalloc(1, sizeof(int)); - } else { - free(new_dir); - } - count = item->util; - *count += 1; - } - - /* - * For each directory with files moved out of it, we find out which - * target directory received the most files so we can declare it to - * be the "winning" target location for the directory rename. This - * winner gets recorded in new_dir. If there is no winner - * (multiple target directories received the same number of files), - * we set non_unique_new_dir. Once we've determined the winner (or - * that there is no winner), we no longer need possible_new_dirs. - */ - hashmap_for_each_entry(dir_renames, &iter, entry, - ent /* member name */) { - int max = 0; - int bad_max = 0; - char *best = NULL; - - for (i = 0; i < entry->possible_new_dirs.nr; i++) { - int *count = entry->possible_new_dirs.items[i].util; - - if (*count == max) - bad_max = max; - else if (*count > max) { - max = *count; - best = entry->possible_new_dirs.items[i].string; - } - } - if (bad_max == max) - entry->non_unique_new_dir = 1; - else { - assert(entry->new_dir.len == 0); - strbuf_addstr(&entry->new_dir, best); - } - /* - * The relevant directory sub-portion of the original full - * filepaths were xstrndup'ed before inserting into - * possible_new_dirs, and instead of manually iterating the - * list and free'ing each, just lie and tell - * possible_new_dirs that it did the strdup'ing so that it - * will free them for us. - */ - entry->possible_new_dirs.strdup_strings = 1; - string_list_clear(&entry->possible_new_dirs, 1); - } - - return dir_renames; -} - -static struct dir_rename_entry *check_dir_renamed(const char *path, - struct hashmap *dir_renames) -{ - char *temp = xstrdup(path); - char *end; - struct dir_rename_entry *entry = NULL; - - while ((end = strrchr(temp, '/'))) { - *end = '\0'; - entry = dir_rename_find_entry(dir_renames, temp); - if (entry) - break; - } - free(temp); - return entry; -} - -static void compute_collisions(struct hashmap *collisions, - struct hashmap *dir_renames, - struct diff_queue_struct *pairs) -{ - int i; - - /* - * Multiple files can be mapped to the same path due to directory - * renames done by the other side of history. Since that other - * side of history could have merged multiple directories into one, - * if our side of history added the same file basename to each of - * those directories, then all N of them would get implicitly - * renamed by the directory rename detection into the same path, - * and we'd get an add/add/.../add conflict, and all those adds - * from *this* side of history. This is not representable in the - * index, and users aren't going to easily be able to make sense of - * it. So we need to provide a good warning about what's - * happening, and fall back to no-directory-rename detection - * behavior for those paths. - * - * See testcases 9e and all of section 5 from t6043 for examples. - */ - collision_init(collisions); - - for (i = 0; i < pairs->nr; ++i) { - struct dir_rename_entry *dir_rename_ent; - struct collision_entry *collision_ent; - char *new_path; - struct diff_filepair *pair = pairs->queue[i]; - - if (pair->status != 'A' && pair->status != 'R') - continue; - dir_rename_ent = check_dir_renamed(pair->two->path, - dir_renames); - if (!dir_rename_ent) - continue; - - new_path = apply_dir_rename(dir_rename_ent, pair->two->path); - if (!new_path) - /* - * dir_rename_ent->non_unique_new_path is true, which - * means there is no directory rename for us to use, - * which means it won't cause us any additional - * collisions. - */ - continue; - collision_ent = collision_find_entry(collisions, new_path); - if (!collision_ent) { - CALLOC_ARRAY(collision_ent, 1); - hashmap_entry_init(&collision_ent->ent, - strhash(new_path)); - hashmap_put(collisions, &collision_ent->ent); - collision_ent->target_file = new_path; - } else { - free(new_path); - } - string_list_insert(&collision_ent->source_files, - pair->two->path); - } -} - -static char *check_for_directory_rename(struct merge_options *opt, - const char *path, - struct tree *tree, - struct hashmap *dir_renames, - struct hashmap *dir_rename_exclusions, - struct hashmap *collisions, - int *clean_merge) -{ - char *new_path = NULL; - struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames); - struct dir_rename_entry *oentry = NULL; - - if (!entry) - return new_path; - - /* - * This next part is a little weird. We do not want to do an - * implicit rename into a directory we renamed on our side, because - * that will result in a spurious rename/rename(1to2) conflict. An - * example: - * Base commit: dumbdir/afile, otherdir/bfile - * Side 1: smrtdir/afile, otherdir/bfile - * Side 2: dumbdir/afile, dumbdir/bfile - * Here, while working on Side 1, we could notice that otherdir was - * renamed/merged to dumbdir, and change the diff_filepair for - * otherdir/bfile into a rename into dumbdir/bfile. However, Side - * 2 will notice the rename from dumbdir to smrtdir, and do the - * transitive rename to move it from dumbdir/bfile to - * smrtdir/bfile. That gives us bfile in dumbdir vs being in - * smrtdir, a rename/rename(1to2) conflict. We really just want - * the file to end up in smrtdir. And the way to achieve that is - * to not let Side1 do the rename to dumbdir, since we know that is - * the source of one of our directory renames. - * - * That's why oentry and dir_rename_exclusions is here. - * - * As it turns out, this also prevents N-way transient rename - * confusion; See testcases 9c and 9d of t6043. - */ - oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf); - if (oentry) { - output(opt, 1, _("WARNING: Avoiding applying %s -> %s rename " - "to %s, because %s itself was renamed."), - entry->dir, entry->new_dir.buf, path, entry->new_dir.buf); - } else { - new_path = handle_path_level_conflicts(opt, path, entry, - collisions, tree); - *clean_merge &= (new_path != NULL); - } - - return new_path; -} - -static void apply_directory_rename_modifications(struct merge_options *opt, - struct diff_filepair *pair, - char *new_path, - struct rename *re, - struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) -{ - struct string_list_item *item; - int stage = (tree == a_tree ? 2 : 3); - int update_wd; - - /* - * In all cases where we can do directory rename detection, - * unpack_trees() will have read pair->two->path into the - * index and the working copy. We need to remove it so that - * we can instead place it at new_path. It is guaranteed to - * not be untracked (unpack_trees() would have errored out - * saying the file would have been overwritten), but it might - * be dirty, though. - */ - update_wd = !was_dirty(opt, pair->two->path); - if (!update_wd) - output(opt, 1, _("Refusing to lose dirty file at %s"), - pair->two->path); - remove_file(opt, 1, pair->two->path, !update_wd); - - /* Find or create a new re->dst_entry */ - item = string_list_lookup(entries, new_path); - if (item) { - /* - * Since we're renaming on this side of history, and it's - * due to a directory rename on the other side of history - * (which we only allow when the directory in question no - * longer exists on the other side of history), the - * original entry for re->dst_entry is no longer - * necessary... - */ - re->dst_entry->processed = 1; - - /* - * ...because we'll be using this new one. - */ - re->dst_entry = item->util; - } else { - /* - * re->dst_entry is for the before-dir-rename path, and we - * need it to hold information for the after-dir-rename - * path. Before creating a new entry, we need to mark the - * old one as unnecessary (...unless it is shared by - * src_entry, i.e. this didn't use to be a rename, in which - * case we can just allow the normal processing to happen - * for it). - */ - if (pair->status == 'R') - re->dst_entry->processed = 1; - - re->dst_entry = insert_stage_data(opt->repo, new_path, - o_tree, a_tree, b_tree, - entries); - item = string_list_insert(entries, new_path); - item->util = re->dst_entry; - } - - /* - * Update the stage_data with the information about the path we are - * moving into place. That slot will be empty and available for us - * to write to because of the collision checks in - * handle_path_level_conflicts(). In other words, - * re->dst_entry->stages[stage].oid will be the null_oid, so it's - * open for us to write to. - * - * It may be tempting to actually update the index at this point as - * well, using update_stages_for_stage_data(), but as per the big - * "NOTE" in update_stages(), doing so will modify the current - * in-memory index which will break calls to would_lose_untracked() - * that we need to make. Instead, we need to just make sure that - * the various handle_rename_*() functions update the index - * explicitly rather than relying on unpack_trees() to have done it. - */ - get_tree_entry(opt->repo, - &tree->object.oid, - pair->two->path, - &re->dst_entry->stages[stage].oid, - &re->dst_entry->stages[stage].mode); - - /* - * Record the original change status (or 'type' of change). If it - * was originally an add ('A'), this lets us differentiate later - * between a RENAME_DELETE conflict and RENAME_VIA_DIR (they - * otherwise look the same). If it was originally a rename ('R'), - * this lets us remember and report accurately about the transitive - * renaming that occurred via the directory rename detection. Also, - * record the original destination name. - */ - re->dir_rename_original_type = pair->status; - re->dir_rename_original_dest = pair->two->path; - - /* - * We don't actually look at pair->status again, but it seems - * pedagogically correct to adjust it. - */ - pair->status = 'R'; - - /* - * Finally, record the new location. - */ - pair->two->path = new_path; -} - -/* - * Get information of all renames which occurred in 'pairs', making use of - * any implicit directory renames inferred from the other side of history. - * We need the three trees in the merge ('o_tree', 'a_tree' and 'b_tree') - * to be able to associate the correct cache entries with the rename - * information; tree is always equal to either a_tree or b_tree. - */ -static struct string_list *get_renames(struct merge_options *opt, - const char *branch, - struct diff_queue_struct *pairs, - struct hashmap *dir_renames, - struct hashmap *dir_rename_exclusions, - struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries, - int *clean_merge) -{ - int i; - struct hashmap collisions; - struct hashmap_iter iter; - struct collision_entry *e; - struct string_list *renames; - - compute_collisions(&collisions, dir_renames, pairs); - CALLOC_ARRAY(renames, 1); - - for (i = 0; i < pairs->nr; ++i) { - struct string_list_item *item; - struct rename *re; - struct diff_filepair *pair = pairs->queue[i]; - char *new_path; /* non-NULL only with directory renames */ - - if (pair->status != 'A' && pair->status != 'R') { - diff_free_filepair(pair); - continue; - } - new_path = check_for_directory_rename(opt, pair->two->path, tree, - dir_renames, - dir_rename_exclusions, - &collisions, - clean_merge); - if (pair->status != 'R' && !new_path) { - diff_free_filepair(pair); - continue; - } - - re = xmalloc(sizeof(*re)); - re->processed = 0; - re->pair = pair; - re->branch = branch; - re->dir_rename_original_type = '\0'; - re->dir_rename_original_dest = NULL; - item = string_list_lookup(entries, re->pair->one->path); - if (!item) - re->src_entry = insert_stage_data(opt->repo, - re->pair->one->path, - o_tree, a_tree, b_tree, entries); - else - re->src_entry = item->util; - - item = string_list_lookup(entries, re->pair->two->path); - if (!item) - re->dst_entry = insert_stage_data(opt->repo, - re->pair->two->path, - o_tree, a_tree, b_tree, entries); - else - re->dst_entry = item->util; - item = string_list_insert(renames, pair->one->path); - item->util = re; - if (new_path) - apply_directory_rename_modifications(opt, pair, new_path, - re, tree, o_tree, - a_tree, b_tree, - entries); - } - - hashmap_for_each_entry(&collisions, &iter, e, - ent /* member name */) { - free(e->target_file); - string_list_clear(&e->source_files, 0); - } - hashmap_clear_and_free(&collisions, struct collision_entry, ent); - return renames; -} - -static int process_renames(struct merge_options *opt, - struct string_list *a_renames, - struct string_list *b_renames) -{ - int clean_merge = 1, i, j; - struct string_list a_by_dst = STRING_LIST_INIT_NODUP; - struct string_list b_by_dst = STRING_LIST_INIT_NODUP; - const struct rename *sre; - - /* - * Note that as we build the list, we do not need to check if the - * existing destination path is already in the list, because the - * structure of diffcore_rename guarantees we won't have duplicates. - */ - for (i = 0; i < a_renames->nr; i++) { - sre = a_renames->items[i].util; - string_list_append(&a_by_dst, sre->pair->two->path)->util - = (void *)sre; - } - for (i = 0; i < b_renames->nr; i++) { - sre = b_renames->items[i].util; - string_list_append(&b_by_dst, sre->pair->two->path)->util - = (void *)sre; - } - string_list_sort(&a_by_dst); - string_list_sort(&b_by_dst); - - for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { - struct string_list *renames1, *renames2Dst; - struct rename *ren1 = NULL, *ren2 = NULL; - const char *ren1_src, *ren1_dst; - struct string_list_item *lookup; - - if (i >= a_renames->nr) { - ren2 = b_renames->items[j++].util; - } else if (j >= b_renames->nr) { - ren1 = a_renames->items[i++].util; - } else { - int compare = strcmp(a_renames->items[i].string, - b_renames->items[j].string); - if (compare <= 0) - ren1 = a_renames->items[i++].util; - if (compare >= 0) - ren2 = b_renames->items[j++].util; - } - - /* TODO: refactor, so that 1/2 are not needed */ - if (ren1) { - renames1 = a_renames; - renames2Dst = &b_by_dst; - } else { - renames1 = b_renames; - renames2Dst = &a_by_dst; - SWAP(ren2, ren1); - } - - if (ren1->processed) - continue; - ren1->processed = 1; - ren1->dst_entry->processed = 1; - /* BUG: We should only mark src_entry as processed if we - * are not dealing with a rename + add-source case. - */ - ren1->src_entry->processed = 1; - - ren1_src = ren1->pair->one->path; - ren1_dst = ren1->pair->two->path; - - if (ren2) { - /* One file renamed on both sides */ - const char *ren2_src = ren2->pair->one->path; - const char *ren2_dst = ren2->pair->two->path; - enum rename_type rename_type; - if (strcmp(ren1_src, ren2_src) != 0) - BUG("ren1_src != ren2_src"); - ren2->dst_entry->processed = 1; - ren2->processed = 1; - if (strcmp(ren1_dst, ren2_dst) != 0) { - rename_type = RENAME_ONE_FILE_TO_TWO; - clean_merge = 0; - } else { - rename_type = RENAME_ONE_FILE_TO_ONE; - /* BUG: We should only remove ren1_src in - * the base stage (think of rename + - * add-source cases). - */ - remove_file(opt, 1, ren1_src, 1); - update_entry(ren1->dst_entry, - ren1->pair->one, - ren1->pair->two, - ren2->pair->two); - } - setup_rename_conflict_info(rename_type, opt, ren1, ren2); - } else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) { - /* Two different files renamed to the same thing */ - char *ren2_dst; - ren2 = lookup->util; - ren2_dst = ren2->pair->two->path; - if (strcmp(ren1_dst, ren2_dst) != 0) - BUG("ren1_dst != ren2_dst"); - - clean_merge = 0; - ren2->processed = 1; - /* - * BUG: We should only mark src_entry as processed - * if we are not dealing with a rename + add-source - * case. - */ - ren2->src_entry->processed = 1; - - setup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE, - opt, ren1, ren2); - } else { - /* Renamed in 1, maybe changed in 2 */ - /* we only use sha1 and mode of these */ - struct diff_filespec src_other, dst_other; - int try_merge; - - /* - * unpack_trees loads entries from common-commit - * into stage 1, from head-commit into stage 2, and - * from merge-commit into stage 3. We keep track - * of which side corresponds to the rename. - */ - int renamed_stage = a_renames == renames1 ? 2 : 3; - int other_stage = a_renames == renames1 ? 3 : 2; - - /* - * Directory renames have a funny corner case... - */ - int renamed_to_self = !strcmp(ren1_src, ren1_dst); - - /* BUG: We should only remove ren1_src in the base - * stage and in other_stage (think of rename + - * add-source case). - */ - if (!renamed_to_self) - remove_file(opt, 1, ren1_src, - renamed_stage == 2 || - !was_tracked(opt, ren1_src)); - - oidcpy(&src_other.oid, - &ren1->src_entry->stages[other_stage].oid); - src_other.mode = ren1->src_entry->stages[other_stage].mode; - oidcpy(&dst_other.oid, - &ren1->dst_entry->stages[other_stage].oid); - dst_other.mode = ren1->dst_entry->stages[other_stage].mode; - try_merge = 0; - - if (oideq(&src_other.oid, null_oid()) && - ren1->dir_rename_original_type == 'A') { - setup_rename_conflict_info(RENAME_VIA_DIR, - opt, ren1, NULL); - } else if (renamed_to_self) { - setup_rename_conflict_info(RENAME_NORMAL, - opt, ren1, NULL); - } else if (oideq(&src_other.oid, null_oid())) { - setup_rename_conflict_info(RENAME_DELETE, - opt, ren1, NULL); - } else if ((dst_other.mode == ren1->pair->two->mode) && - oideq(&dst_other.oid, &ren1->pair->two->oid)) { - /* - * Added file on the other side identical to - * the file being renamed: clean merge. - * Also, there is no need to overwrite the - * file already in the working copy, so call - * update_file_flags() instead of - * update_file(). - */ - if (update_file_flags(opt, - ren1->pair->two, - ren1_dst, - 1, /* update_cache */ - 0 /* update_wd */)) - clean_merge = -1; - } else if (!oideq(&dst_other.oid, null_oid())) { - /* - * Probably not a clean merge, but it's - * premature to set clean_merge to 0 here, - * because if the rename merges cleanly and - * the merge exactly matches the newly added - * file, then the merge will be clean. - */ - setup_rename_conflict_info(RENAME_ADD, - opt, ren1, NULL); - } else - try_merge = 1; - - if (clean_merge < 0) - goto cleanup_and_return; - if (try_merge) { - struct diff_filespec *o, *a, *b; - src_other.path = (char *)ren1_src; - - o = ren1->pair->one; - if (a_renames == renames1) { - a = ren1->pair->two; - b = &src_other; - } else { - b = ren1->pair->two; - a = &src_other; - } - update_entry(ren1->dst_entry, o, a, b); - setup_rename_conflict_info(RENAME_NORMAL, - opt, ren1, NULL); - } - } - } -cleanup_and_return: - string_list_clear(&a_by_dst, 0); - string_list_clear(&b_by_dst, 0); - - return clean_merge; -} - -struct rename_info { - struct string_list *head_renames; - struct string_list *merge_renames; -}; - -static void initial_cleanup_rename(struct diff_queue_struct *pairs, - struct hashmap *dir_renames) -{ - struct hashmap_iter iter; - struct dir_rename_entry *e; - - hashmap_for_each_entry(dir_renames, &iter, e, - ent /* member name */) { - free(e->dir); - strbuf_release(&e->new_dir); - /* possible_new_dirs already cleared in get_directory_renames */ - } - hashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent); - free(dir_renames); - - free(pairs->queue); - free(pairs); -} - -static int detect_and_process_renames(struct merge_options *opt, - struct tree *common, - struct tree *head, - struct tree *merge, - struct string_list *entries, - struct rename_info *ri) -{ - struct diff_queue_struct *head_pairs, *merge_pairs; - struct hashmap *dir_re_head, *dir_re_merge; - int clean = 1; - - ri->head_renames = NULL; - ri->merge_renames = NULL; - - if (!merge_detect_rename(opt)) - return 1; - - head_pairs = get_diffpairs(opt, common, head); - merge_pairs = get_diffpairs(opt, common, merge); - - if ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) || - (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT && - !opt->priv->call_depth)) { - dir_re_head = get_directory_renames(head_pairs); - dir_re_merge = get_directory_renames(merge_pairs); - - handle_directory_level_conflicts(opt, - dir_re_head, head, - dir_re_merge, merge); - } else { - dir_re_head = xmalloc(sizeof(*dir_re_head)); - dir_re_merge = xmalloc(sizeof(*dir_re_merge)); - dir_rename_init(dir_re_head); - dir_rename_init(dir_re_merge); - } - - ri->head_renames = get_renames(opt, opt->branch1, head_pairs, - dir_re_merge, dir_re_head, head, - common, head, merge, entries, - &clean); - if (clean < 0) - goto cleanup; - ri->merge_renames = get_renames(opt, opt->branch2, merge_pairs, - dir_re_head, dir_re_merge, merge, - common, head, merge, entries, - &clean); - if (clean < 0) - goto cleanup; - clean &= process_renames(opt, ri->head_renames, ri->merge_renames); - -cleanup: - /* - * Some cleanup is deferred until cleanup_renames() because the - * data structures are still needed and referenced in - * process_entry(). But there are a few things we can free now. - */ - initial_cleanup_rename(head_pairs, dir_re_head); - initial_cleanup_rename(merge_pairs, dir_re_merge); - - return clean; -} - -static void final_cleanup_rename(struct string_list *rename) -{ - const struct rename *re; - int i; - - if (!rename) - return; - - for (i = 0; i < rename->nr; i++) { - re = rename->items[i].util; - diff_free_filepair(re->pair); - if (re->src_entry->rename_conflict_info_owned) - FREE_AND_NULL(re->src_entry->rename_conflict_info); - if (re->dst_entry->rename_conflict_info_owned) - FREE_AND_NULL(re->dst_entry->rename_conflict_info); - } - string_list_clear(rename, 1); - free(rename); -} - -static void final_cleanup_renames(struct rename_info *re_info) -{ - final_cleanup_rename(re_info->head_renames); - final_cleanup_rename(re_info->merge_renames); -} - -static int read_oid_strbuf(struct merge_options *opt, - const struct object_id *oid, - struct strbuf *dst) -{ - void *buf; - enum object_type type; - unsigned long size; - buf = repo_read_object_file(the_repository, oid, &type, &size); - if (!buf) - return err(opt, _("cannot read object %s"), oid_to_hex(oid)); - if (type != OBJ_BLOB) { - free(buf); - return err(opt, _("object %s is not a blob"), oid_to_hex(oid)); - } - strbuf_attach(dst, buf, size, size + 1); - return 0; -} - -static int blob_unchanged(struct merge_options *opt, - const struct diff_filespec *o, - const struct diff_filespec *a, - int renormalize, const char *path) -{ - struct strbuf obuf = STRBUF_INIT; - struct strbuf abuf = STRBUF_INIT; - int ret = 0; /* assume changed for safety */ - struct index_state *idx = opt->repo->index; - - if (a->mode != o->mode) - return 0; - if (oideq(&o->oid, &a->oid)) - return 1; - if (!renormalize) - return 0; - - if (read_oid_strbuf(opt, &o->oid, &obuf) || - read_oid_strbuf(opt, &a->oid, &abuf)) - goto error_return; - /* - * Note: binary | is used so that both renormalizations are - * performed. Comparison can be skipped if both files are - * unchanged since their sha1s have already been compared. - */ - if (renormalize_buffer(idx, path, obuf.buf, obuf.len, &obuf) | - renormalize_buffer(idx, path, abuf.buf, abuf.len, &abuf)) - ret = (obuf.len == abuf.len && !memcmp(obuf.buf, abuf.buf, obuf.len)); - -error_return: - strbuf_release(&obuf); - strbuf_release(&abuf); - return ret; -} - -static int handle_modify_delete(struct merge_options *opt, - const char *path, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b) -{ - const char *modify_branch, *delete_branch; - const struct diff_filespec *changed; - - if (is_valid(a)) { - modify_branch = opt->branch1; - delete_branch = opt->branch2; - changed = a; - } else { - modify_branch = opt->branch2; - delete_branch = opt->branch1; - changed = b; - } - - return handle_change_delete(opt, - path, NULL, - o, changed, - modify_branch, delete_branch, - _("modify"), _("modified")); -} - -static int handle_content_merge(struct merge_file_info *mfi, - struct merge_options *opt, - const char *path, - int is_dirty, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b, - struct rename_conflict_info *ci) -{ - const char *reason = _("content"); - unsigned df_conflict_remains = 0; - - if (!is_valid(o)) - reason = _("add/add"); - - assert(o->path && a->path && b->path); - if (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth, - S_ISGITLINK(ci->ren1->pair->two->mode))) - df_conflict_remains = 1; - - if (merge_mode_and_contents(opt, o, a, b, path, - opt->branch1, opt->branch2, - opt->priv->call_depth * 2, mfi)) - return -1; - - /* - * We can skip updating the working tree file iff: - * a) The merge is clean - * b) The merge matches what was in HEAD (content, mode, pathname) - * c) The target path is usable (i.e. not involved in D/F conflict) - */ - if (mfi->clean && was_tracked_and_matches(opt, path, &mfi->blob) && - !df_conflict_remains) { - int pos; - struct cache_entry *ce; - - output(opt, 3, _("Skipped %s (merged same as existing)"), path); - if (add_cacheinfo(opt, &mfi->blob, path, - 0, (!opt->priv->call_depth && !is_dirty), 0)) - return -1; - /* - * However, add_cacheinfo() will delete the old cache entry - * and add a new one. We need to copy over any skip_worktree - * flag to avoid making the file appear as if it were - * deleted by the user. - */ - pos = index_name_pos(&opt->priv->orig_index, path, strlen(path)); - ce = opt->priv->orig_index.cache[pos]; - if (ce_skip_worktree(ce)) { - pos = index_name_pos(opt->repo->index, path, strlen(path)); - ce = opt->repo->index->cache[pos]; - ce->ce_flags |= CE_SKIP_WORKTREE; - } - return mfi->clean; - } - - if (!mfi->clean) { - if (S_ISGITLINK(mfi->blob.mode)) - reason = _("submodule"); - output(opt, 1, _("CONFLICT (%s): Merge conflict in %s"), - reason, path); - if (ci && !df_conflict_remains) - if (update_stages(opt, path, o, a, b)) - return -1; - } - - if (df_conflict_remains || is_dirty) { - char *new_path; - if (opt->priv->call_depth) { - remove_file_from_index(opt->repo->index, path); - } else { - if (!mfi->clean) { - if (update_stages(opt, path, o, a, b)) - return -1; - } else { - int file_from_stage2 = was_tracked(opt, path); - - if (update_stages(opt, path, NULL, - file_from_stage2 ? &mfi->blob : NULL, - file_from_stage2 ? NULL : &mfi->blob)) - return -1; - } - - } - new_path = unique_path(opt, path, ci->ren1->branch); - if (is_dirty) { - output(opt, 1, _("Refusing to lose dirty file at %s"), - path); - } - output(opt, 1, _("Adding as %s instead"), new_path); - if (update_file(opt, 0, &mfi->blob, new_path)) { - free(new_path); - return -1; - } - free(new_path); - mfi->clean = 0; - } else if (update_file(opt, mfi->clean, &mfi->blob, path)) - return -1; - return !is_dirty && mfi->clean; -} - -static int handle_rename_normal(struct merge_options *opt, - const char *path, - const struct diff_filespec *o, - const struct diff_filespec *a, - const struct diff_filespec *b, - struct rename_conflict_info *ci) -{ - struct rename *ren = ci->ren1; - struct merge_file_info mfi; - int clean; - - /* Merge the content and write it out */ - clean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path), - o, a, b, ci); - - if (clean && - opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT && - ren->dir_rename_original_dest) { - if (update_stages(opt, path, - &mfi.blob, &mfi.blob, &mfi.blob)) - return -1; - clean = 0; /* not clean, but conflicted */ - } - return clean; -} - -static void dir_rename_warning(const char *msg, - int is_add, - int clean, - struct merge_options *opt, - struct rename *ren) -{ - const char *other_branch; - other_branch = (ren->branch == opt->branch1 ? - opt->branch2 : opt->branch1); - if (is_add) { - output(opt, clean ? 2 : 1, msg, - ren->pair->one->path, ren->branch, - other_branch, ren->pair->two->path); - return; - } - output(opt, clean ? 2 : 1, msg, - ren->pair->one->path, ren->dir_rename_original_dest, ren->branch, - other_branch, ren->pair->two->path); -} -static int warn_about_dir_renamed_entries(struct merge_options *opt, - struct rename *ren) -{ - const char *msg; - int clean = 1, is_add; - - if (!ren) - return clean; - - /* Return early if ren was not affected/created by a directory rename */ - if (!ren->dir_rename_original_dest) - return clean; - - /* Sanity checks */ - assert(opt->detect_directory_renames > MERGE_DIRECTORY_RENAMES_NONE); - assert(ren->dir_rename_original_type == 'A' || - ren->dir_rename_original_type == 'R'); - - /* Check whether to treat directory renames as a conflict */ - clean = (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE); - - is_add = (ren->dir_rename_original_type == 'A'); - if (ren->dir_rename_original_type == 'A' && clean) { - msg = _("Path updated: %s added in %s inside a " - "directory that was renamed in %s; moving it to %s."); - } else if (ren->dir_rename_original_type == 'A' && !clean) { - msg = _("CONFLICT (file location): %s added in %s " - "inside a directory that was renamed in %s, " - "suggesting it should perhaps be moved to %s."); - } else if (ren->dir_rename_original_type == 'R' && clean) { - msg = _("Path updated: %s renamed to %s in %s, inside a " - "directory that was renamed in %s; moving it to %s."); - } else if (ren->dir_rename_original_type == 'R' && !clean) { - msg = _("CONFLICT (file location): %s renamed to %s in %s, " - "inside a directory that was renamed in %s, " - "suggesting it should perhaps be moved to %s."); - } else { - BUG("Impossible dir_rename_original_type/clean combination"); - } - dir_rename_warning(msg, is_add, clean, opt, ren); - - return clean; -} - -/* Per entry merge function */ -static int process_entry(struct merge_options *opt, - const char *path, struct stage_data *entry) -{ - int clean_merge = 1; - int normalize = opt->renormalize; - - struct diff_filespec *o = &entry->stages[1]; - struct diff_filespec *a = &entry->stages[2]; - struct diff_filespec *b = &entry->stages[3]; - int o_valid = is_valid(o); - int a_valid = is_valid(a); - int b_valid = is_valid(b); - o->path = a->path = b->path = (char*)path; - - entry->processed = 1; - if (entry->rename_conflict_info) { - struct rename_conflict_info *ci = entry->rename_conflict_info; - struct diff_filespec *temp; - int path_clean; - - path_clean = warn_about_dir_renamed_entries(opt, ci->ren1); - path_clean &= warn_about_dir_renamed_entries(opt, ci->ren2); - - /* - * For cases with a single rename, {o,a,b}->path have all been - * set to the rename target path; we need to set two of these - * back to the rename source. - * For rename/rename conflicts, we'll manually fix paths below. - */ - temp = (opt->branch1 == ci->ren1->branch) ? b : a; - o->path = temp->path = ci->ren1->pair->one->path; - if (ci->ren2) { - assert(opt->branch1 == ci->ren1->branch); - } - - switch (ci->rename_type) { - case RENAME_NORMAL: - case RENAME_ONE_FILE_TO_ONE: - clean_merge = handle_rename_normal(opt, path, o, a, b, - ci); - break; - case RENAME_VIA_DIR: - clean_merge = handle_rename_via_dir(opt, ci); - break; - case RENAME_ADD: - /* - * Probably unclean merge, but if the renamed file - * merges cleanly and the result can then be - * two-way merged cleanly with the added file, I - * guess it's a clean merge? - */ - clean_merge = handle_rename_add(opt, ci); - break; - case RENAME_DELETE: - clean_merge = 0; - if (handle_rename_delete(opt, ci)) - clean_merge = -1; - break; - case RENAME_ONE_FILE_TO_TWO: - /* - * Manually fix up paths; note: - * ren[12]->pair->one->path are equal. - */ - o->path = ci->ren1->pair->one->path; - a->path = ci->ren1->pair->two->path; - b->path = ci->ren2->pair->two->path; - - clean_merge = 0; - if (handle_rename_rename_1to2(opt, ci)) - clean_merge = -1; - break; - case RENAME_TWO_FILES_TO_ONE: - /* - * Manually fix up paths; note, - * ren[12]->pair->two->path are actually equal. - */ - o->path = NULL; - a->path = ci->ren1->pair->two->path; - b->path = ci->ren2->pair->two->path; - - /* - * Probably unclean merge, but if the two renamed - * files merge cleanly and the two resulting files - * can then be two-way merged cleanly, I guess it's - * a clean merge? - */ - clean_merge = handle_rename_rename_2to1(opt, ci); - break; - default: - entry->processed = 0; - break; - } - if (path_clean < clean_merge) - clean_merge = path_clean; - } else if (o_valid && (!a_valid || !b_valid)) { - /* Case A: Deleted in one */ - if ((!a_valid && !b_valid) || - (!b_valid && blob_unchanged(opt, o, a, normalize, path)) || - (!a_valid && blob_unchanged(opt, o, b, normalize, path))) { - /* Deleted in both or deleted in one and - * unchanged in the other */ - if (a_valid) - output(opt, 2, _("Removing %s"), path); - /* do not touch working file if it did not exist */ - remove_file(opt, 1, path, !a_valid); - } else { - /* Modify/delete; deleted side may have put a directory in the way */ - clean_merge = 0; - if (handle_modify_delete(opt, path, o, a, b)) - clean_merge = -1; - } - } else if ((!o_valid && a_valid && !b_valid) || - (!o_valid && !a_valid && b_valid)) { - /* Case B: Added in one. */ - /* [nothing|directory] -> ([nothing|directory], file) */ - - const char *add_branch; - const char *other_branch; - const char *conf; - const struct diff_filespec *contents; - - if (a_valid) { - add_branch = opt->branch1; - other_branch = opt->branch2; - contents = a; - conf = _("file/directory"); - } else { - add_branch = opt->branch2; - other_branch = opt->branch1; - contents = b; - conf = _("directory/file"); - } - if (dir_in_way(opt->repo->index, path, - !opt->priv->call_depth && !S_ISGITLINK(a->mode), - 0)) { - char *new_path = unique_path(opt, path, add_branch); - clean_merge = 0; - output(opt, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " - "Adding %s as %s"), - conf, path, other_branch, path, new_path); - if (update_file(opt, 0, contents, new_path)) - clean_merge = -1; - else if (opt->priv->call_depth) - remove_file_from_index(opt->repo->index, path); - free(new_path); - } else { - output(opt, 2, _("Adding %s"), path); - /* do not overwrite file if already present */ - if (update_file_flags(opt, contents, path, 1, !a_valid)) - clean_merge = -1; - } - } else if (a_valid && b_valid) { - if (!o_valid) { - /* Case C: Added in both (check for same permissions) */ - output(opt, 1, - _("CONFLICT (add/add): Merge conflict in %s"), - path); - clean_merge = handle_file_collision(opt, - path, NULL, NULL, - opt->branch1, - opt->branch2, - a, b); - } else { - /* case D: Modified in both, but differently. */ - struct merge_file_info mfi; - int is_dirty = 0; /* unpack_trees would have bailed if dirty */ - clean_merge = handle_content_merge(&mfi, opt, path, - is_dirty, - o, a, b, NULL); - } - } else if (!o_valid && !a_valid && !b_valid) { - /* - * this entry was deleted altogether. a_mode == 0 means - * we had that path and want to actively remove it. - */ - remove_file(opt, 1, path, !a->mode); - } else - BUG("fatal merge failure, shouldn't happen."); - - return clean_merge; -} - -static int merge_trees_internal(struct merge_options *opt, - struct tree *head, - struct tree *merge, - struct tree *merge_base, - struct tree **result) -{ - struct index_state *istate = opt->repo->index; - int code, clean; - - if (opt->subtree_shift) { - merge = shift_tree_object(opt->repo, head, merge, - opt->subtree_shift); - merge_base = shift_tree_object(opt->repo, head, merge_base, - opt->subtree_shift); - } - - if (oideq(&merge_base->object.oid, &merge->object.oid)) { - output(opt, 0, _("Already up to date.")); - *result = head; - return 1; - } - - code = unpack_trees_start(opt, merge_base, head, merge); - - if (code != 0) { - if (show(opt, 4) || opt->priv->call_depth) - err(opt, _("merging of trees %s and %s failed"), - oid_to_hex(&head->object.oid), - oid_to_hex(&merge->object.oid)); - unpack_trees_finish(opt); - return -1; - } - - if (unmerged_index(istate)) { - struct string_list *entries; - struct rename_info re_info; - int i; - /* - * Only need the hashmap while processing entries, so - * initialize it here and free it when we are done running - * through the entries. Keeping it in the merge_options as - * opposed to decaring a local hashmap is for convenience - * so that we don't have to pass it to around. - */ - hashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp, - NULL, 512); - get_files_dirs(opt, head); - get_files_dirs(opt, merge); - - entries = get_unmerged(opt->repo->index); - clean = detect_and_process_renames(opt, merge_base, head, merge, - entries, &re_info); - record_df_conflict_files(opt, entries); - if (clean < 0) - goto cleanup; - for (i = entries->nr-1; 0 <= i; i--) { - const char *path = entries->items[i].string; - struct stage_data *e = entries->items[i].util; - if (!e->processed) { - int ret = process_entry(opt, path, e); - if (!ret) - clean = 0; - else if (ret < 0) { - clean = ret; - goto cleanup; - } - } - } - for (i = 0; i < entries->nr; i++) { - struct stage_data *e = entries->items[i].util; - if (!e->processed) - BUG("unprocessed path??? %s", - entries->items[i].string); - } - - cleanup: - final_cleanup_renames(&re_info); - - string_list_clear(entries, 1); - free(entries); - - hashmap_clear_and_free(&opt->priv->current_file_dir_set, - struct path_hashmap_entry, e); - - if (clean < 0) { - unpack_trees_finish(opt); - return clean; - } - } - else - clean = 1; - - unpack_trees_finish(opt); - - if (opt->priv->call_depth && - !(*result = write_in_core_index_as_tree(opt->repo))) - return -1; - - return clean; -} - -/* - * Merge the commits h1 and h2, returning a flag (int) indicating the - * cleanness of the merge. Also, if opt->priv->call_depth, create a - * virtual commit and write its location to *result. - */ -static int merge_recursive_internal(struct merge_options *opt, - struct commit *h1, - struct commit *h2, - const struct commit_list *_merge_bases, - struct commit **result) -{ - struct commit_list *merge_bases = copy_commit_list(_merge_bases); - struct commit_list *iter; - struct commit *merged_merge_bases; - struct tree *result_tree; - const char *ancestor_name; - struct strbuf merge_base_abbrev = STRBUF_INIT; - int ret; - - if (show(opt, 4)) { - output(opt, 4, _("Merging:")); - output_commit_title(opt, h1); - output_commit_title(opt, h2); - } - - if (!merge_bases) { - if (repo_get_merge_bases(the_repository, h1, h2, - &merge_bases) < 0) { - ret = -1; - goto out; - } - merge_bases = reverse_commit_list(merge_bases); - } - - if (show(opt, 5)) { - unsigned cnt = commit_list_count(merge_bases); - - output(opt, 5, Q_("found %u common ancestor:", - "found %u common ancestors:", cnt), cnt); - for (iter = merge_bases; iter; iter = iter->next) - output_commit_title(opt, iter->item); - } - - merged_merge_bases = pop_commit(&merge_bases); - if (!merged_merge_bases) { - /* if there is no common ancestor, use an empty tree */ - struct tree *tree; - - tree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree); - merged_merge_bases = make_virtual_commit(opt->repo, tree, - "ancestor"); - ancestor_name = "empty tree"; - } else if (opt->ancestor && !opt->priv->call_depth) { - ancestor_name = opt->ancestor; - } else if (merge_bases) { - ancestor_name = "merged common ancestors"; - } else { - strbuf_add_unique_abbrev(&merge_base_abbrev, - &merged_merge_bases->object.oid, - DEFAULT_ABBREV); - ancestor_name = merge_base_abbrev.buf; - } - - for (iter = merge_bases; iter; iter = iter->next) { - const char *saved_b1, *saved_b2; - opt->priv->call_depth++; - /* - * When the merge fails, the result contains files - * with conflict markers. The cleanness flag is - * ignored (unless indicating an error), it was never - * actually used, as result of merge_trees has always - * overwritten it: the committed "conflicts" were - * already resolved. - */ - discard_index(opt->repo->index); - saved_b1 = opt->branch1; - saved_b2 = opt->branch2; - opt->branch1 = "Temporary merge branch 1"; - opt->branch2 = "Temporary merge branch 2"; - if (merge_recursive_internal(opt, merged_merge_bases, iter->item, - NULL, &merged_merge_bases) < 0) { - ret = -1; - goto out; - } - opt->branch1 = saved_b1; - opt->branch2 = saved_b2; - opt->priv->call_depth--; - - if (!merged_merge_bases) { - ret = err(opt, _("merge returned no commit")); - goto out; - } - } - - /* - * FIXME: Since merge_recursive_internal() is only ever called by - * places that ensure the index is loaded first - * (e.g. builtin/merge.c, rebase/sequencer, etc.), in the common - * case where the merge base was unique that means when we get here - * we immediately discard the index and re-read it, which is a - * complete waste of time. We should only be discarding and - * re-reading if we were forced to recurse. - */ - discard_index(opt->repo->index); - if (!opt->priv->call_depth) - repo_read_index(opt->repo); - - opt->ancestor = ancestor_name; - ret = merge_trees_internal(opt, - repo_get_commit_tree(opt->repo, h1), - repo_get_commit_tree(opt->repo, h2), - repo_get_commit_tree(opt->repo, - merged_merge_bases), - &result_tree); - opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */ - if (ret < 0) { - flush_output(opt); - goto out; - } - - if (opt->priv->call_depth) { - *result = make_virtual_commit(opt->repo, result_tree, - "merged tree"); - commit_list_insert(h1, &(*result)->parents); - commit_list_insert(h2, &(*result)->parents->next); - } - -out: - strbuf_release(&merge_base_abbrev); - free_commit_list(merge_bases); - return ret; -} - -static int merge_start(struct merge_options *opt, struct tree *head) -{ - struct strbuf sb = STRBUF_INIT; - - /* Sanity checks on opt */ - assert(opt->repo); - - assert(opt->branch1 && opt->branch2); - - assert(opt->detect_renames >= -1 && - opt->detect_renames <= DIFF_DETECT_COPY); - assert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE && - opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE); - assert(opt->rename_limit >= -1); - assert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE); - assert(opt->show_rename_progress >= 0 && opt->show_rename_progress <= 1); - - assert(opt->xdl_opts >= 0); - assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL && - opt->recursive_variant <= MERGE_VARIANT_THEIRS); - - assert(opt->verbosity >= 0 && opt->verbosity <= 5); - assert(opt->buffer_output <= 2); - assert(opt->obuf.len == 0); - - assert(opt->priv == NULL); - - /* Not supported; option specific to merge-ort */ - assert(!opt->record_conflict_msgs_as_headers); - assert(!opt->msg_header_prefix); - - /* Sanity check on repo state; index must match head */ - if (repo_index_has_changes(opt->repo, head, &sb)) { - err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"), - sb.buf); - strbuf_release(&sb); - return -1; - } - - CALLOC_ARRAY(opt->priv, 1); - string_list_init_dup(&opt->priv->df_conflict_file_set); - return 0; -} - -static void merge_finalize(struct merge_options *opt) -{ - flush_output(opt); - if (!opt->priv->call_depth && opt->buffer_output < 2) - strbuf_release(&opt->obuf); - if (show(opt, 2)) - diff_warn_rename_limit("merge.renamelimit", - opt->priv->needed_rename_limit, 0); - hashmap_clear_and_free(&opt->priv->current_file_dir_set, - struct path_hashmap_entry, e); - string_list_clear(&opt->priv->df_conflict_file_set, 0); - FREE_AND_NULL(opt->priv); -} - -int merge_trees(struct merge_options *opt, - struct tree *head, - struct tree *merge, - struct tree *merge_base) -{ - int clean; - struct tree *ignored; - - assert(opt->ancestor != NULL); - - if (merge_start(opt, head)) - return -1; - clean = merge_trees_internal(opt, head, merge, merge_base, &ignored); - merge_finalize(opt); - - return clean; -} - -int merge_recursive(struct merge_options *opt, - struct commit *h1, - struct commit *h2, - const struct commit_list *merge_bases, - struct commit **result) -{ - int clean; - - assert(opt->ancestor == NULL || - !strcmp(opt->ancestor, "constructed merge base")); - - prepare_repo_settings(opt->repo); - opt->repo->settings.command_requires_full_index = 1; - - if (merge_start(opt, repo_get_commit_tree(opt->repo, h1))) - return -1; - clean = merge_recursive_internal(opt, h1, h2, merge_bases, result); - merge_finalize(opt); - - return clean; -} - -static struct commit *get_ref(struct repository *repo, - const struct object_id *oid, - const char *name) -{ - struct object *object; - - object = deref_tag(repo, parse_object(repo, oid), - name, strlen(name)); - if (!object) - return NULL; - if (object->type == OBJ_TREE) - return make_virtual_commit(repo, (struct tree*)object, name); - if (object->type != OBJ_COMMIT) - return NULL; - if (repo_parse_commit(repo, (struct commit *)object)) - return NULL; - return (struct commit *)object; -} - -int merge_recursive_generic(struct merge_options *opt, - const struct object_id *head, - const struct object_id *merge, - int num_merge_bases, - const struct object_id *merge_bases, - struct commit **result) -{ - int clean; - struct lock_file lock = LOCK_INIT; - struct commit *head_commit = get_ref(opt->repo, head, opt->branch1); - struct commit *next_commit = get_ref(opt->repo, merge, opt->branch2); - struct commit_list *ca = NULL; - - if (merge_bases) { - int i; - for (i = 0; i < num_merge_bases; ++i) { - struct commit *base; - if (!(base = get_ref(opt->repo, &merge_bases[i], - oid_to_hex(&merge_bases[i])))) - return err(opt, _("Could not parse object '%s'"), - oid_to_hex(&merge_bases[i])); - commit_list_insert(base, &ca); - } - if (num_merge_bases == 1) - opt->ancestor = "constructed merge base"; - } - - repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR); - clean = merge_recursive(opt, head_commit, next_commit, ca, - result); - free_commit_list(ca); - if (clean < 0) { - rollback_lock_file(&lock); - return clean; - } - - if (write_locked_index(opt->repo->index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) - return err(opt, _("Unable to write index.")); - - return clean ? 0 : 1; -} - -static void merge_recursive_config(struct merge_options *opt, int ui) -{ - char *value = NULL; - int renormalize = 0; - git_config_get_int("merge.verbosity", &opt->verbosity); - git_config_get_int("diff.renamelimit", &opt->rename_limit); - git_config_get_int("merge.renamelimit", &opt->rename_limit); - git_config_get_bool("merge.renormalize", &renormalize); - opt->renormalize = renormalize; - if (!git_config_get_string("diff.renames", &value)) { - opt->detect_renames = git_config_rename("diff.renames", value); - free(value); - } - if (!git_config_get_string("merge.renames", &value)) { - opt->detect_renames = git_config_rename("merge.renames", value); - free(value); - } - if (!git_config_get_string("merge.directoryrenames", &value)) { - int boolval = git_parse_maybe_bool(value); - if (0 <= boolval) { - opt->detect_directory_renames = boolval ? - MERGE_DIRECTORY_RENAMES_TRUE : - MERGE_DIRECTORY_RENAMES_NONE; - } else if (!strcasecmp(value, "conflict")) { - opt->detect_directory_renames = - MERGE_DIRECTORY_RENAMES_CONFLICT; - } /* avoid erroring on values from future versions of git */ - free(value); - } - if (ui) { - if (!git_config_get_string("diff.algorithm", &value)) { - long diff_algorithm = parse_algorithm_value(value); - if (diff_algorithm < 0) - die(_("unknown value for config '%s': %s"), "diff.algorithm", value); - opt->xdl_opts = (opt->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | diff_algorithm; - free(value); - } - } - git_config(git_xmerge_config, NULL); -} - -static void init_merge_options(struct merge_options *opt, - struct repository *repo, int ui) -{ - const char *merge_verbosity; - memset(opt, 0, sizeof(struct merge_options)); - - opt->repo = repo; - - opt->detect_renames = -1; - opt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT; - opt->rename_limit = -1; - - opt->verbosity = 2; - opt->buffer_output = 1; - strbuf_init(&opt->obuf, 0); - - opt->renormalize = 0; - - opt->conflict_style = -1; - - merge_recursive_config(opt, ui); - merge_verbosity = getenv("GIT_MERGE_VERBOSITY"); - if (merge_verbosity) - opt->verbosity = strtol(merge_verbosity, NULL, 10); - if (opt->verbosity >= 5) - opt->buffer_output = 0; -} - -void init_ui_merge_options(struct merge_options *opt, - struct repository *repo) -{ - init_merge_options(opt, repo, 1); -} - -void init_basic_merge_options(struct merge_options *opt, - struct repository *repo) -{ - init_merge_options(opt, repo, 0); -} - -/* - * For now, members of merge_options do not need deep copying, but - * it may change in the future, in which case we would need to update - * this, and also make a matching change to clear_merge_options() to - * release the resources held by a copied instance. - */ -void copy_merge_options(struct merge_options *dst, struct merge_options *src) -{ - *dst = *src; -} - -void clear_merge_options(struct merge_options *opt UNUSED) -{ - ; /* no-op as our copy is shallow right now */ -} - -int parse_merge_opt(struct merge_options *opt, const char *s) -{ - const char *arg; - - if (!s || !*s) - return -1; - if (!strcmp(s, "ours")) - opt->recursive_variant = MERGE_VARIANT_OURS; - else if (!strcmp(s, "theirs")) - opt->recursive_variant = MERGE_VARIANT_THEIRS; - else if (!strcmp(s, "subtree")) - opt->subtree_shift = ""; - else if (skip_prefix(s, "subtree=", &arg)) - opt->subtree_shift = arg; - else if (!strcmp(s, "patience")) - opt->xdl_opts = DIFF_WITH_ALG(opt, PATIENCE_DIFF); - else if (!strcmp(s, "histogram")) - opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF); - else if (skip_prefix(s, "diff-algorithm=", &arg)) { - long value = parse_algorithm_value(arg); - if (value < 0) - return -1; - /* clear out previous settings */ - DIFF_XDL_CLR(opt, NEED_MINIMAL); - opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; - opt->xdl_opts |= value; - } - else if (!strcmp(s, "ignore-space-change")) - DIFF_XDL_SET(opt, IGNORE_WHITESPACE_CHANGE); - else if (!strcmp(s, "ignore-all-space")) - DIFF_XDL_SET(opt, IGNORE_WHITESPACE); - else if (!strcmp(s, "ignore-space-at-eol")) - DIFF_XDL_SET(opt, IGNORE_WHITESPACE_AT_EOL); - else if (!strcmp(s, "ignore-cr-at-eol")) - DIFF_XDL_SET(opt, IGNORE_CR_AT_EOL); - else if (!strcmp(s, "renormalize")) - opt->renormalize = 1; - else if (!strcmp(s, "no-renormalize")) - opt->renormalize = 0; - else if (!strcmp(s, "no-renames")) - opt->detect_renames = 0; - else if (!strcmp(s, "find-renames")) { - opt->detect_renames = 1; - opt->rename_score = 0; - } - else if (skip_prefix(s, "find-renames=", &arg) || - skip_prefix(s, "rename-threshold=", &arg)) { - if ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0) - return -1; - opt->detect_renames = 1; - } - /* - * Please update $__git_merge_strategy_options in - * git-completion.bash when you add new options - */ - else - return -1; - return 0; -} diff --git a/merge-recursive.h b/merge-recursive.h deleted file mode 100644 index 0b91f28f90..0000000000 --- a/merge-recursive.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef MERGE_RECURSIVE_H -#define MERGE_RECURSIVE_H - -#include "strbuf.h" - -struct commit; -struct commit_list; -struct object_id; -struct repository; -struct tree; - -struct merge_options_internal; -struct merge_options { - struct repository *repo; - - /* ref names used in console messages and conflict markers */ - const char *ancestor; - const char *branch1; - const char *branch2; - - /* rename related options */ - int detect_renames; - enum { - MERGE_DIRECTORY_RENAMES_NONE = 0, - MERGE_DIRECTORY_RENAMES_CONFLICT = 1, - MERGE_DIRECTORY_RENAMES_TRUE = 2 - } detect_directory_renames; - int rename_limit; - int rename_score; - int show_rename_progress; - - /* xdiff-related options (patience, ignore whitespace, ours/theirs) */ - long xdl_opts; - int conflict_style; - enum { - MERGE_VARIANT_NORMAL = 0, - MERGE_VARIANT_OURS, - MERGE_VARIANT_THEIRS - } recursive_variant; - - /* console output related options */ - int verbosity; - unsigned buffer_output; /* 1: output at end, 2: keep buffered */ - struct strbuf obuf; /* output buffer; if buffer_output == 2, caller - * must handle and call strbuf_release */ - - /* miscellaneous control options */ - const char *subtree_shift; - unsigned renormalize : 1; - unsigned record_conflict_msgs_as_headers : 1; - const char *msg_header_prefix; - - /* internal fields used by the implementation */ - struct merge_options_internal *priv; -}; - -/* for use by porcelain commands */ -void init_ui_merge_options(struct merge_options *opt, struct repository *repo); -/* for use by plumbing commands */ -void init_basic_merge_options(struct merge_options *opt, struct repository *repo); - -void copy_merge_options(struct merge_options *dst, struct merge_options *src); -void clear_merge_options(struct merge_options *opt); - -/* parse the option in s and update the relevant field of opt */ -int parse_merge_opt(struct merge_options *opt, const char *s); - -/* - * RETURN VALUES: All the merge_* functions below return a value as follows: - * > 0 Merge was clean - * = 0 Merge had conflicts - * < 0 Merge hit an unexpected and unrecoverable problem (e.g. disk - * full) and aborted merge part-way through. - */ - -/* - * rename-detecting three-way merge, no recursion. - * - * Outputs: - * - See RETURN VALUES above - * - opt->repo->index has the new index - * - new index NOT written to disk - * - The working tree is updated with results of the merge - */ -int merge_trees(struct merge_options *opt, - struct tree *head, - struct tree *merge, - struct tree *merge_base); - -/* - * merge_recursive is like merge_trees() but with recursive ancestor - * consolidation. - * - * NOTE: empirically, about a decade ago it was determined that with more - * than two merge bases, optimal behavior was found when the - * merge_bases were passed in the order of oldest commit to newest - * commit. Also, merge_bases will be consumed (emptied) so make a - * copy if you need it. - * - * Outputs: - * - See RETURN VALUES above - * - *result is treated as scratch space for temporary recursive merges - * - opt->repo->index has the new index - * - new index NOT written to disk - * - The working tree is updated with results of the merge - */ -int merge_recursive(struct merge_options *opt, - struct commit *h1, - struct commit *h2, - const struct commit_list *merge_bases, - struct commit **result); - -/* - * merge_recursive_generic can operate on trees instead of commits, by - * wrapping the trees into virtual commits, and calling merge_recursive(). - * It also writes out the in-memory index to disk if the merge is successful. - * - * Outputs: - * - See RETURN VALUES above - * - *result is treated as scratch space for temporary recursive merges - * - opt->repo->index has the new index - * - new index also written to $GIT_INDEX_FILE on disk - * - The working tree is updated with results of the merge - */ -int merge_recursive_generic(struct merge_options *opt, - const struct object_id *head, - const struct object_id *merge, - int num_merge_bases, - const struct object_id *merge_bases, - struct commit **result); - -#endif diff --git a/meson.build b/meson.build index 97753d2cfa..14b8109852 100644 --- a/meson.build +++ b/meson.build @@ -155,6 +155,37 @@ # These machine files can be passed to `meson setup` via the `--native-file` # option. # +# Cross compilation +# ================= +# +# Machine files can also be used in the context of cross-compilation to +# describe the target machine as well as the cross-compiler toolchain that +# shall be used. An example machine file could look like the following: +# +# [binaries] +# c = 'x86_64-w64-mingw32-gcc' +# cpp = 'x86_64-w64-mingw32-g++' +# ar = 'x86_64-w64-mingw32-ar' +# windres = 'x86_64-w64-mingw32-windres' +# strip = 'x86_64-w64-mingw32-strip' +# exe_wrapper = 'wine64' +# sh = 'C:/Program Files/Git for Windows/usr/bin/sh.exe' +# +# [host_machine] +# system = 'windows' +# cpu_family = 'x86_64' +# cpu = 'x86_64' +# endian = 'little' +# +# These machine files can be passed to `meson setup` via the `--cross-file` +# option. +# +# Note that next to the cross-compiler toolchain, the `[binaries]` section is +# also used to locate a couple of binaries that will be built into Git. This +# includes `sh`, `python` and `perl`, so when cross-compiling Git you likely +# want to set these binary paths in addition to the cross-compiler toolchain +# binaries. +# # Subproject wrappers # =================== # @@ -173,7 +204,7 @@ project('git', 'c', # The version is only of cosmetic nature, so if we cannot find a shell yet we # simply don't set up a version at all. This may be the case for example on # Windows systems, where we first have to bootstrap the host environment. - version: find_program('sh', required: false).found() ? run_command( + version: find_program('sh', native: true, required: false).found() ? run_command( 'GIT-VERSION-GEN', meson.current_source_dir(), '--format=@GIT_VERSION@', capture: true, check: true, @@ -198,16 +229,18 @@ elif host_machine.system() == 'windows' program_path = [ 'C:/Program Files/Git/bin', 'C:/Program Files/Git/usr/bin' ] endif -cygpath = find_program('cygpath', dirs: program_path, required: false) -diff = find_program('diff', dirs: program_path) -git = find_program('git', dirs: program_path, required: false) -sed = find_program('sed', dirs: program_path) -shell = find_program('sh', dirs: program_path) -tar = find_program('tar', dirs: program_path) +cygpath = find_program('cygpath', dirs: program_path, native: true, required: false) +diff = find_program('diff', dirs: program_path, native: true) +git = find_program('git', dirs: program_path, native: true, required: false) +sed = find_program('sed', dirs: program_path, native: true) +shell = find_program('sh', dirs: program_path, native: true) +tar = find_program('tar', dirs: program_path, native: true) + +target_shell = find_program('sh', dirs: program_path, native: false) # Sanity-check that programs required for the build exist. foreach tool : ['cat', 'cut', 'grep', 'sort', 'tr', 'uname'] - find_program(tool, dirs: program_path) + find_program(tool, dirs: program_path, native: true) endforeach script_environment = environment() @@ -263,6 +296,7 @@ libgit_sources = [ 'common-init.c', 'compat/nonblock.c', 'compat/obstack.c', + 'compat/open.c', 'compat/terminal.c', 'compiler-tricks/not-constant.c', 'config.c', @@ -311,6 +345,7 @@ libgit_sources = [ 'graph.c', 'grep.c', 'hash-lookup.c', + 'hash.c', 'hashmap.c', 'help.c', 'hex.c', @@ -338,7 +373,6 @@ libgit_sources = [ 'merge-ll.c', 'merge-ort.c', 'merge-ort-wrappers.c', - 'merge-recursive.c', 'merge.c', 'midx.c', 'midx-write.c', @@ -353,6 +387,7 @@ libgit_sources = [ 'object-file-convert.c', 'object-file.c', 'object-name.c', + 'object-store.c', 'object.c', 'oid-array.c', 'oidmap.c', @@ -411,10 +446,10 @@ libgit_sources = [ 'reftable/iter.c', 'reftable/merged.c', 'reftable/pq.c', - 'reftable/reader.c', 'reftable/record.c', 'reftable/stack.c', 'reftable/system.c', + 'reftable/table.c', 'reftable/tree.c', 'reftable/writer.c', 'remote.c', @@ -692,10 +727,7 @@ endif # These variables are used for building libgit.a. libgit_c_args = [ '-DBINDIR="' + get_option('bindir') + '"', - '-DDEFAULT_EDITOR="' + get_option('default_editor') + '"', '-DDEFAULT_GIT_TEMPLATE_DIR="' + get_option('datadir') / 'git-core/templates' + '"', - '-DDEFAULT_HELP_FORMAT="' + get_option('default_help_format') + '"', - '-DDEFAULT_PAGER="' + get_option('default_pager') + '"', '-DETC_GITATTRIBUTES="' + get_option('gitattributes') + '"', '-DETC_GITCONFIG="' + get_option('gitconfig') + '"', '-DFALLBACK_RUNTIME_PREFIX="' + get_option('prefix') + '"', @@ -705,8 +737,31 @@ libgit_c_args = [ '-DGIT_LOCALE_PATH="' + get_option('localedir') + '"', '-DGIT_MAN_PATH="' + get_option('mandir') + '"', '-DPAGER_ENV="' + get_option('pager_environment') + '"', - '-DSHELL_PATH="' + fs.as_posix(shell.full_path()) + '"', + '-DSHELL_PATH="' + fs.as_posix(target_shell.full_path()) + '"', ] + +editor_opt = get_option('default_editor') +if editor_opt != '' and editor_opt != 'vi' + libgit_c_args += '-DDEFAULT_EDITOR="' + editor_opt + '"' +endif + +pager_opt = get_option('default_pager') +if pager_opt != '' and pager_opt != 'less' + libgit_c_args += '-DDEFAULT_PAGER="' + pager_opt + '"' +endif + +help_format_opt = get_option('default_help_format') +if help_format_opt == 'platform' + if host_machine.system() == 'windows' + help_format_opt = 'html' + else + help_format_opt = 'man' + endif +endif +if help_format_opt != 'man' + libgit_c_args += '-DDEFAULT_HELP_FORMAT="' + help_format_opt + '"' +endif + libgit_include_directories = [ '.' ] libgit_dependencies = [ ] @@ -714,6 +769,7 @@ libgit_dependencies = [ ] # Makefile. if get_option('warning_level') in ['2','3', 'everything'] and compiler.get_argument_syntax() == 'gcc' foreach cflag : [ + '-Wcomma', '-Wdeclaration-after-statement', '-Wformat-security', '-Wold-style-definition', @@ -768,6 +824,7 @@ endif build_options_config.set_quoted('X', executable_suffix) python = import('python').find_installation('python3', required: get_option('python')) +target_python = find_program('python3', native: false, required: python.found()) if python.found() build_options_config.set('NO_PYTHON', '') else @@ -797,9 +854,11 @@ endif # which we can do starting with Meson 1.5.0 and newer, or we have to # match against the minor version. if meson.version().version_compare('>=1.5.0') - perl = find_program('perl', dirs: program_path, required: perl_required, version: '>=5.26.0', version_argument: '-V:version') + perl = find_program('perl', dirs: program_path, native: true, required: perl_required, version: '>=5.26.0', version_argument: '-V:version') + target_perl = find_program('perl', dirs: program_path, native: false, required: perl.found(), version: '>=5.26.0', version_argument: '-V:version') else - perl = find_program('perl', dirs: program_path, required: perl_required, version: '>=26') + perl = find_program('perl', dirs: program_path, native: true, required: perl_required, version: '>=26') + target_perl = find_program('perl', dirs: program_path, native: false, required: perl.found(), version: '>=26') endif perl_features_enabled = perl.found() and get_option('perl').allowed() if perl_features_enabled @@ -850,7 +909,7 @@ else build_options_config.set('NO_PTHREADS', '1') endif -msgfmt = find_program('msgfmt', dirs: program_path, required: false) +msgfmt = find_program('msgfmt', dirs: program_path, native: true, required: false) gettext_option = get_option('gettext').disable_auto_if(not msgfmt.found()) if not msgfmt.found() and gettext_option.enabled() error('Internationalization via libintl requires msgfmt') @@ -973,7 +1032,6 @@ if curl.found() # Most executables don't have to link against libcurl, but we still need its # include directories so that we can resolve LIBCURL_VERSION in "help.c". libgit_dependencies += curl.partial_dependency(includes: true) - libgit_c_args += '-DCURL_DISABLE_TYPECHECK' build_options_config.set('NO_CURL', '') else libgit_c_args += '-DNO_CURL' @@ -1114,7 +1172,6 @@ if host_machine.system() == 'cygwin' ] elif host_machine.system() == 'windows' libgit_sources += [ - 'compat/mingw.c', 'compat/winansi.c', 'compat/win32/dirent.c', 'compat/win32/flush.c', @@ -1141,6 +1198,9 @@ elif host_machine.system() == 'windows' libgit_include_directories += 'compat/win32' if compiler.get_id() == 'msvc' libgit_include_directories += 'compat/vcbuild/include' + libgit_sources += 'compat/msvc.c' + else + libgit_sources += 'compat/mingw.c' endif endif @@ -1693,7 +1753,7 @@ bin_wrappers += executable('scalar', install_dir: get_option('libexecdir') / 'git-core', ) -if get_option('curl').enabled() +if curl.found() libgit_curl = declare_dependency( sources: [ 'http.c', @@ -1981,9 +2041,9 @@ foreach key, value : { 'GIT_TEST_TEMPLATE_DIR': meson.project_build_root() / 'templates', 'GIT_TEST_TEXTDOMAINDIR': meson.project_build_root() / 'po', 'PAGER_ENV': get_option('pager_environment'), - 'PERL_PATH': perl.found() ? perl.full_path() : '', - 'PYTHON_PATH': python.found () ? python.full_path() : '', - 'SHELL_PATH': shell.full_path(), + 'PERL_PATH': target_perl.found() ? target_perl.full_path() : '', + 'PYTHON_PATH': target_python.found () ? target_python.full_path() : '', + 'SHELL_PATH': target_shell.full_path(), 'TAR': tar.full_path(), 'TEST_OUTPUT_DIRECTORY': test_output_directory, 'TEST_SHELL_PATH': shell.full_path(), diff --git a/meson_options.txt b/meson_options.txt index 78d172a740..8ac30a5223 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -95,7 +95,7 @@ option('highlight_bin', type: 'string', value: 'highlight') # Documentation. option('docs', type: 'array', choices: ['man', 'html'], value: [], description: 'Which documenattion formats to build and install.') -option('default_help_format', type: 'combo', choices: ['man', 'html'], value: 'man', +option('default_help_format', type: 'combo', choices: ['man', 'html', 'platform'], value: 'platform', description: 'Default format used when executing git-help(1).') option('docs_backend', type: 'combo', choices: ['asciidoc', 'asciidoctor', 'auto'], value: 'auto', description: 'Which backend to use to generate documentation.') diff --git a/midx-write.c b/midx-write.c index 0897cbd829..dd3b3070e5 100644 --- a/midx-write.c +++ b/midx-write.c @@ -664,7 +664,7 @@ static void write_midx_reverse_index(struct write_midx_context *ctx, get_midx_filename_ext(ctx->repo->hash_algo, &buf, object_dir, midx_hash, MIDX_EXT_REV); - tmp_file = write_rev_file_order(ctx->repo->hash_algo, NULL, ctx->pack_order, + tmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order, ctx->entries_nr, midx_hash, WRITE_REV); if (finalize_object_file(tmp_file, buf.buf)) @@ -714,7 +714,7 @@ static int add_ref_to_pending(const char *refname, const char *referent UNUSED, if (!peel_iterated_oid(revs->repo, oid, &peeled)) oid = &peeled; - object = parse_object_or_die(oid, refname); + object = parse_object_or_die(revs->repo, oid, refname); if (object->type != OBJ_COMMIT) return 0; @@ -774,7 +774,7 @@ static int read_refs_snapshot(const char *refs_snapshot, if (*end) die(_("malformed line: %s"), buf.buf); - object = parse_object_or_die(&oid, NULL); + object = parse_object_or_die(revs->repo, &oid, NULL); if (preferred) object->flags |= NEEDS_BITMAP; @@ -1098,7 +1098,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, object_dir); else get_midx_filename(r->hash_algo, &midx_name, object_dir); - if (safe_create_leading_directories(midx_name.buf)) + if (safe_create_leading_directories(r, midx_name.buf)) die_errno(_("unable to create leading directories of %s"), midx_name.buf); @@ -1361,10 +1361,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir, return -1; } - f = hashfd(get_tempfile_fd(incr), get_tempfile_path(incr)); + f = hashfd(r->hash_algo, get_tempfile_fd(incr), + get_tempfile_path(incr)); } else { hold_lock_file_for_update(&lk, midx_name.buf, LOCK_DIE_ON_ERROR); - f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk)); + f = hashfd(r->hash_algo, get_lock_file_fd(&lk), + get_lock_file_path(&lk)); } cf = init_chunkfile(f); @@ -5,7 +5,6 @@ #include "dir.h" #include "hex.h" #include "packfile.h" -#include "object-file.h" #include "hash-lookup.h" #include "midx.h" #include "progress.h" @@ -747,7 +746,8 @@ int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, i int midx_checksum_valid(struct multi_pack_index *m) { - return hashfile_checksum_valid(m->data, m->data_len); + return hashfile_checksum_valid(m->repo->hash_algo, + m->data, m->data_len); } struct clear_midx_data { diff --git a/notes-cache.c b/notes-cache.c index ecfdf6e43b..150241b15e 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -2,7 +2,8 @@ #include "git-compat-util.h" #include "notes-cache.h" -#include "object-store-ll.h" +#include "object-file.h" +#include "object-store.h" #include "pretty.h" #include "repository.h" #include "commit.h" diff --git a/notes-merge.c b/notes-merge.c index 67a472020d..dae8e6a281 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -8,7 +8,7 @@ #include "refs.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "repository.h" #include "diff.h" @@ -296,7 +296,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o) "(%s exists)."), repo_git_path_replace(the_repository, &buf, "NOTES_MERGE_*")); } - if (safe_create_leading_directories_const(repo_git_path_replace(the_repository, &buf, + if (safe_create_leading_directories_const(the_repository, repo_git_path_replace(the_repository, &buf, NOTES_MERGE_WORKTREE "/.test"))) die_errno("unable to create directory %s", repo_git_path_replace(the_repository, &buf, NOTES_MERGE_WORKTREE)); @@ -314,7 +314,7 @@ static void write_buf_to_worktree(const struct object_id *obj, { int fd; char *path = repo_git_path(the_repository, NOTES_MERGE_WORKTREE "/%s", oid_to_hex(obj)); - if (safe_create_leading_directories_const(path)) + if (safe_create_leading_directories_const(the_repository, path)) die_errno("unable to create directory for '%s'", path); fd = xopen(path, O_WRONLY | O_EXCL | O_CREAT, 0666); @@ -617,7 +617,7 @@ int notes_merge(struct notes_merge_options *o, if (repo_get_merge_bases(the_repository, local, remote, &bases) < 0) exit(128); if (!bases) { - base_oid = null_oid(); + base_oid = null_oid(the_hash_algo); base_tree_oid = the_hash_algo->empty_tree; if (o->verbosity >= 4) printf("No merge base found; doing history-less merge\n"); @@ -729,7 +729,7 @@ int notes_merge_commit(struct notes_merge_options *o, /* write file as blob, and add to partial_tree */ if (stat(path.buf, &st)) die_errno("Failed to stat '%s'", path.buf); - if (index_path(o->repo->index, &blob_oid, path.buf, &st, HASH_WRITE_OBJECT)) + if (index_path(o->repo->index, &blob_oid, path.buf, &st, INDEX_WRITE_OBJECT)) die("Failed to write blob object from '%s'", path.buf); if (add_note(partial_tree, &obj_oid, &blob_oid, NULL)) die("Failed to add resolved note '%s' to notes tree", @@ -6,8 +6,9 @@ #include "environment.h" #include "hex.h" #include "notes.h" +#include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "utf8.h" #include "strbuf.h" #include "tree-walk.h" @@ -1353,7 +1354,7 @@ int copy_note(struct notes_tree *t, if (note) return add_note(t, to_obj, note, combine_notes); else if (existing_note) - return add_note(t, to_obj, null_oid(), combine_notes); + return add_note(t, to_obj, null_oid(the_hash_algo), combine_notes); return 0; } diff --git a/object-file-convert.c b/object-file-convert.c index eba71955cf..7ab875afe6 100644 --- a/object-file-convert.c +++ b/object-file-convert.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -63,7 +62,8 @@ static int decode_tree_entry_raw(struct object_id *oid, const char **path, return 0; } -static int convert_tree_object(struct strbuf *out, +static int convert_tree_object(struct repository *repo, + struct strbuf *out, const struct git_hash_algo *from, const struct git_hash_algo *to, const char *buffer, size_t size) @@ -78,7 +78,7 @@ static int convert_tree_object(struct strbuf *out, if (decode_tree_entry_raw(&entry_oid, &path, &pathlen, from, p, end - p)) return error(_("failed to decode tree entry")); - if (repo_oid_to_algop(the_repository, &entry_oid, to, &mapped_oid)) + if (repo_oid_to_algop(repo, &entry_oid, to, &mapped_oid)) return error(_("failed to map tree entry for %s"), oid_to_hex(&entry_oid)); strbuf_add(out, p, path - p); strbuf_add(out, path, pathlen); @@ -88,7 +88,8 @@ static int convert_tree_object(struct strbuf *out, return 0; } -static int convert_tag_object(struct strbuf *out, +static int convert_tag_object(struct repository *repo, + struct strbuf *out, const struct git_hash_algo *from, const struct git_hash_algo *to, const char *buffer, size_t size) @@ -105,7 +106,7 @@ static int convert_tag_object(struct strbuf *out, return error("bogus tag object"); if (parse_oid_hex_algop(buffer + 7, &oid, &p, from) < 0) return error("bad tag object ID"); - if (repo_oid_to_algop(the_repository, &oid, to, &mapped_oid)) + if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) return error("unable to map tree %s in tag object", oid_to_hex(&oid)); size -= ((p + 1) - buffer); @@ -139,7 +140,8 @@ static int convert_tag_object(struct strbuf *out, return 0; } -static int convert_commit_object(struct strbuf *out, +static int convert_commit_object(struct repository *repo, + struct strbuf *out, const struct git_hash_algo *from, const struct git_hash_algo *to, const char *buffer, size_t size) @@ -165,7 +167,7 @@ static int convert_commit_object(struct strbuf *out, (p != eol)) return error(_("bad %s in commit"), "tree"); - if (repo_oid_to_algop(the_repository, &oid, to, &mapped_oid)) + if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) return error(_("unable to map %s %s in commit object"), "tree", oid_to_hex(&oid)); strbuf_addf(out, "tree %s\n", oid_to_hex(&mapped_oid)); @@ -177,7 +179,7 @@ static int convert_commit_object(struct strbuf *out, (p != eol)) return error(_("bad %s in commit"), "parent"); - if (repo_oid_to_algop(the_repository, &oid, to, &mapped_oid)) + if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) return error(_("unable to map %s %s in commit object"), "parent", oid_to_hex(&oid)); @@ -202,7 +204,7 @@ static int convert_commit_object(struct strbuf *out, } /* Compute the new tag object */ - if (convert_tag_object(&new_tag, from, to, tag.buf, tag.len)) { + if (convert_tag_object(repo, &new_tag, from, to, tag.buf, tag.len)) { strbuf_release(&tag); strbuf_release(&new_tag); return -1; @@ -241,7 +243,8 @@ static int convert_commit_object(struct strbuf *out, return 0; } -int convert_object_file(struct strbuf *outbuf, +int convert_object_file(struct repository *repo, + struct strbuf *outbuf, const struct git_hash_algo *from, const struct git_hash_algo *to, const void *buf, size_t len, @@ -256,13 +259,13 @@ int convert_object_file(struct strbuf *outbuf, switch (type) { case OBJ_COMMIT: - ret = convert_commit_object(outbuf, from, to, buf, len); + ret = convert_commit_object(repo, outbuf, from, to, buf, len); break; case OBJ_TREE: - ret = convert_tree_object(outbuf, from, to, buf, len); + ret = convert_tree_object(repo, outbuf, from, to, buf, len); break; case OBJ_TAG: - ret = convert_tag_object(outbuf, from, to, buf, len); + ret = convert_tag_object(repo, outbuf, from, to, buf, len); break; default: /* Not implemented yet, so fail. */ diff --git a/object-file-convert.h b/object-file-convert.h index a4f802aa8e..9b3cc5e533 100644 --- a/object-file-convert.h +++ b/object-file-convert.h @@ -14,7 +14,8 @@ int repo_oid_to_algop(struct repository *repo, const struct object_id *src, * Convert an object file from one hash algorithm to another algorithm. * Return -1 on failure, 0 on success. */ -int convert_object_file(struct strbuf *outbuf, +int convert_object_file(struct repository *repo, + struct strbuf *outbuf, const struct git_hash_algo *from, const struct git_hash_algo *to, const void *buf, size_t len, diff --git a/object-file.c b/object-file.c index 4fb3cd9dcb..9cc3a24a40 100644 --- a/object-file.c +++ b/object-file.c @@ -11,502 +11,37 @@ #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" -#include "abspath.h" -#include "config.h" +#include "bulk-checkin.h" #include "convert.h" +#include "dir.h" #include "environment.h" +#include "fsck.h" #include "gettext.h" #include "hex.h" -#include "string-list.h" -#include "lockfile.h" -#include "pack.h" -#include "commit.h" -#include "run-command.h" -#include "refs.h" -#include "bulk-checkin.h" -#include "repository.h" -#include "replace-object.h" -#include "streaming.h" -#include "dir.h" -#include "list.h" -#include "quote.h" -#include "packfile.h" +#include "loose.h" +#include "object-file-convert.h" #include "object-file.h" #include "object-store.h" #include "oidtree.h" +#include "pack.h" +#include "packfile.h" #include "path.h" -#include "promisor-remote.h" #include "setup.h" -#include "submodule.h" -#include "fsck.h" -#include "loose.h" -#include "object-file-convert.h" +#include "streaming.h" /* The maximum size for an object header. */ #define MAX_HEADER_LEN 32 -static const struct object_id empty_tree_oid = { - .hash = { - 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, - 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 - }, - .algo = GIT_HASH_SHA1, -}; -static const struct object_id empty_blob_oid = { - .hash = { - 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, - 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 - }, - .algo = GIT_HASH_SHA1, -}; -static const struct object_id null_oid_sha1 = { - .hash = {0}, - .algo = GIT_HASH_SHA1, -}; -static const struct object_id empty_tree_oid_sha256 = { - .hash = { - 0x6e, 0xf1, 0x9b, 0x41, 0x22, 0x5c, 0x53, 0x69, 0xf1, 0xc1, - 0x04, 0xd4, 0x5d, 0x8d, 0x85, 0xef, 0xa9, 0xb0, 0x57, 0xb5, - 0x3b, 0x14, 0xb4, 0xb9, 0xb9, 0x39, 0xdd, 0x74, 0xde, 0xcc, - 0x53, 0x21 - }, - .algo = GIT_HASH_SHA256, -}; -static const struct object_id empty_blob_oid_sha256 = { - .hash = { - 0x47, 0x3a, 0x0f, 0x4c, 0x3b, 0xe8, 0xa9, 0x36, 0x81, 0xa2, - 0x67, 0xe3, 0xb1, 0xe9, 0xa7, 0xdc, 0xda, 0x11, 0x85, 0x43, - 0x6f, 0xe1, 0x41, 0xf7, 0x74, 0x91, 0x20, 0xa3, 0x03, 0x72, - 0x18, 0x13 - }, - .algo = GIT_HASH_SHA256, -}; -static const struct object_id null_oid_sha256 = { - .hash = {0}, - .algo = GIT_HASH_SHA256, -}; - -static void git_hash_sha1_init(struct git_hash_ctx *ctx) -{ - ctx->algop = &hash_algos[GIT_HASH_SHA1]; - git_SHA1_Init(&ctx->state.sha1); -} - -static void git_hash_sha1_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) -{ - dst->algop = src->algop; - git_SHA1_Clone(&dst->state.sha1, &src->state.sha1); -} - -static void git_hash_sha1_update(struct git_hash_ctx *ctx, const void *data, size_t len) -{ - git_SHA1_Update(&ctx->state.sha1, data, len); -} - -static void git_hash_sha1_final(unsigned char *hash, struct git_hash_ctx *ctx) -{ - git_SHA1_Final(hash, &ctx->state.sha1); -} - -static void git_hash_sha1_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) -{ - git_SHA1_Final(oid->hash, &ctx->state.sha1); - memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ); - oid->algo = GIT_HASH_SHA1; -} - -static void git_hash_sha1_init_unsafe(struct git_hash_ctx *ctx) -{ - ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA1]); - git_SHA1_Init_unsafe(&ctx->state.sha1_unsafe); -} - -static void git_hash_sha1_clone_unsafe(struct git_hash_ctx *dst, const struct git_hash_ctx *src) -{ - dst->algop = src->algop; - git_SHA1_Clone_unsafe(&dst->state.sha1_unsafe, &src->state.sha1_unsafe); -} - -static void git_hash_sha1_update_unsafe(struct git_hash_ctx *ctx, const void *data, - size_t len) -{ - git_SHA1_Update_unsafe(&ctx->state.sha1_unsafe, data, len); -} - -static void git_hash_sha1_final_unsafe(unsigned char *hash, struct git_hash_ctx *ctx) -{ - git_SHA1_Final_unsafe(hash, &ctx->state.sha1_unsafe); -} - -static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, struct git_hash_ctx *ctx) -{ - git_SHA1_Final_unsafe(oid->hash, &ctx->state.sha1_unsafe); - memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ); - oid->algo = GIT_HASH_SHA1; -} - -static void git_hash_sha256_init(struct git_hash_ctx *ctx) -{ - ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA256]); - git_SHA256_Init(&ctx->state.sha256); -} - -static void git_hash_sha256_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) -{ - dst->algop = src->algop; - git_SHA256_Clone(&dst->state.sha256, &src->state.sha256); -} - -static void git_hash_sha256_update(struct git_hash_ctx *ctx, const void *data, size_t len) -{ - git_SHA256_Update(&ctx->state.sha256, data, len); -} - -static void git_hash_sha256_final(unsigned char *hash, struct git_hash_ctx *ctx) -{ - git_SHA256_Final(hash, &ctx->state.sha256); -} - -static void git_hash_sha256_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) -{ - git_SHA256_Final(oid->hash, &ctx->state.sha256); - /* - * This currently does nothing, so the compiler should optimize it out, - * but keep it in case we extend the hash size again. - */ - memset(oid->hash + GIT_SHA256_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA256_RAWSZ); - oid->algo = GIT_HASH_SHA256; -} - -static void git_hash_unknown_init(struct git_hash_ctx *ctx UNUSED) -{ - BUG("trying to init unknown hash"); -} - -static void git_hash_unknown_clone(struct git_hash_ctx *dst UNUSED, - const struct git_hash_ctx *src UNUSED) -{ - BUG("trying to clone unknown hash"); -} - -static void git_hash_unknown_update(struct git_hash_ctx *ctx UNUSED, - const void *data UNUSED, - size_t len UNUSED) -{ - BUG("trying to update unknown hash"); -} - -static void git_hash_unknown_final(unsigned char *hash UNUSED, - struct git_hash_ctx *ctx UNUSED) -{ - BUG("trying to finalize unknown hash"); -} - -static void git_hash_unknown_final_oid(struct object_id *oid UNUSED, - struct git_hash_ctx *ctx UNUSED) -{ - BUG("trying to finalize unknown hash"); -} - -static const struct git_hash_algo sha1_unsafe_algo = { - .name = "sha1", - .format_id = GIT_SHA1_FORMAT_ID, - .rawsz = GIT_SHA1_RAWSZ, - .hexsz = GIT_SHA1_HEXSZ, - .blksz = GIT_SHA1_BLKSZ, - .init_fn = git_hash_sha1_init_unsafe, - .clone_fn = git_hash_sha1_clone_unsafe, - .update_fn = git_hash_sha1_update_unsafe, - .final_fn = git_hash_sha1_final_unsafe, - .final_oid_fn = git_hash_sha1_final_oid_unsafe, - .empty_tree = &empty_tree_oid, - .empty_blob = &empty_blob_oid, - .null_oid = &null_oid_sha1, -}; - -const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { - { - .name = NULL, - .format_id = 0x00000000, - .rawsz = 0, - .hexsz = 0, - .blksz = 0, - .init_fn = git_hash_unknown_init, - .clone_fn = git_hash_unknown_clone, - .update_fn = git_hash_unknown_update, - .final_fn = git_hash_unknown_final, - .final_oid_fn = git_hash_unknown_final_oid, - .empty_tree = NULL, - .empty_blob = NULL, - .null_oid = NULL, - }, - { - .name = "sha1", - .format_id = GIT_SHA1_FORMAT_ID, - .rawsz = GIT_SHA1_RAWSZ, - .hexsz = GIT_SHA1_HEXSZ, - .blksz = GIT_SHA1_BLKSZ, - .init_fn = git_hash_sha1_init, - .clone_fn = git_hash_sha1_clone, - .update_fn = git_hash_sha1_update, - .final_fn = git_hash_sha1_final, - .final_oid_fn = git_hash_sha1_final_oid, - .unsafe = &sha1_unsafe_algo, - .empty_tree = &empty_tree_oid, - .empty_blob = &empty_blob_oid, - .null_oid = &null_oid_sha1, - }, - { - .name = "sha256", - .format_id = GIT_SHA256_FORMAT_ID, - .rawsz = GIT_SHA256_RAWSZ, - .hexsz = GIT_SHA256_HEXSZ, - .blksz = GIT_SHA256_BLKSZ, - .init_fn = git_hash_sha256_init, - .clone_fn = git_hash_sha256_clone, - .update_fn = git_hash_sha256_update, - .final_fn = git_hash_sha256_final, - .final_oid_fn = git_hash_sha256_final_oid, - .empty_tree = &empty_tree_oid_sha256, - .empty_blob = &empty_blob_oid_sha256, - .null_oid = &null_oid_sha256, - } -}; - -const struct object_id *null_oid(void) -{ - return the_hash_algo->null_oid; -} - -const char *empty_tree_oid_hex(const struct git_hash_algo *algop) -{ - static char buf[GIT_MAX_HEXSZ + 1]; - return oid_to_hex_r(buf, algop->empty_tree); -} - -int hash_algo_by_name(const char *name) -{ - int i; - if (!name) - return GIT_HASH_UNKNOWN; - for (i = 1; i < GIT_HASH_NALGOS; i++) - if (!strcmp(name, hash_algos[i].name)) - return i; - return GIT_HASH_UNKNOWN; -} - -int hash_algo_by_id(uint32_t format_id) -{ - int i; - for (i = 1; i < GIT_HASH_NALGOS; i++) - if (format_id == hash_algos[i].format_id) - return i; - return GIT_HASH_UNKNOWN; -} - -int hash_algo_by_length(int len) -{ - int i; - for (i = 1; i < GIT_HASH_NALGOS; i++) - if (len == hash_algos[i].rawsz) - return i; - return GIT_HASH_UNKNOWN; -} - -const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop) -{ - /* If we have a faster "unsafe" implementation, use that. */ - if (algop->unsafe) - return algop->unsafe; - /* Otherwise use the default one. */ - return algop; -} - -/* - * This is meant to hold a *small* number of objects that you would - * want repo_read_object_file() to be able to return, but yet you do not want - * to write them into the object store (e.g. a browse-only - * application). - */ -static struct cached_object_entry { - struct object_id oid; - struct cached_object { - enum object_type type; - const void *buf; - unsigned long size; - } value; -} *cached_objects; -static int cached_object_nr, cached_object_alloc; - -static const struct cached_object *find_cached_object(const struct object_id *oid) -{ - static const struct cached_object empty_tree = { - .type = OBJ_TREE, - .buf = "", - }; - int i; - const struct cached_object_entry *co = cached_objects; - - for (i = 0; i < cached_object_nr; i++, co++) { - if (oideq(&co->oid, oid)) - return &co->value; - } - if (oideq(oid, the_hash_algo->empty_tree)) - return &empty_tree; - return NULL; -} - - static int get_conv_flags(unsigned flags) { - if (flags & HASH_RENORMALIZE) + if (flags & INDEX_RENORMALIZE) return CONV_EOL_RENORMALIZE; - else if (flags & HASH_WRITE_OBJECT) + else if (flags & INDEX_WRITE_OBJECT) return global_conv_flags_eol | CONV_WRITE_OBJECT; else return 0; } - -int mkdir_in_gitdir(const char *path) -{ - if (mkdir(path, 0777)) { - int saved_errno = errno; - struct stat st; - struct strbuf sb = STRBUF_INIT; - - if (errno != EEXIST) - return -1; - /* - * Are we looking at a path in a symlinked worktree - * whose original repository does not yet have it? - * e.g. .git/rr-cache pointing at its original - * repository in which the user hasn't performed any - * conflict resolution yet? - */ - if (lstat(path, &st) || !S_ISLNK(st.st_mode) || - strbuf_readlink(&sb, path, st.st_size) || - !is_absolute_path(sb.buf) || - mkdir(sb.buf, 0777)) { - strbuf_release(&sb); - errno = saved_errno; - return -1; - } - strbuf_release(&sb); - } - return adjust_shared_perm(the_repository, path); -} - -static enum scld_error safe_create_leading_directories_1(char *path, int share) -{ - char *next_component = path + offset_1st_component(path); - enum scld_error ret = SCLD_OK; - - while (ret == SCLD_OK && next_component) { - struct stat st; - char *slash = next_component, slash_character; - - while (*slash && !is_dir_sep(*slash)) - slash++; - - if (!*slash) - break; - - next_component = slash + 1; - while (is_dir_sep(*next_component)) - next_component++; - if (!*next_component) - break; - - slash_character = *slash; - *slash = '\0'; - if (!stat(path, &st)) { - /* path exists */ - if (!S_ISDIR(st.st_mode)) { - errno = ENOTDIR; - ret = SCLD_EXISTS; - } - } else if (mkdir(path, 0777)) { - if (errno == EEXIST && - !stat(path, &st) && S_ISDIR(st.st_mode)) - ; /* somebody created it since we checked */ - else if (errno == ENOENT) - /* - * Either mkdir() failed because - * somebody just pruned the containing - * directory, or stat() failed because - * the file that was in our way was - * just removed. Either way, inform - * the caller that it might be worth - * trying again: - */ - ret = SCLD_VANISHED; - else - ret = SCLD_FAILED; - } else if (share && adjust_shared_perm(the_repository, path)) { - ret = SCLD_PERMS; - } - *slash = slash_character; - } - return ret; -} - -enum scld_error safe_create_leading_directories(char *path) -{ - return safe_create_leading_directories_1(path, 1); -} - -enum scld_error safe_create_leading_directories_no_share(char *path) -{ - return safe_create_leading_directories_1(path, 0); -} - -enum scld_error safe_create_leading_directories_const(const char *path) -{ - int save_errno; - /* path points to cache entries, so xstrdup before messing with it */ - char *buf = xstrdup(path); - enum scld_error result = safe_create_leading_directories(buf); - - save_errno = errno; - free(buf); - errno = save_errno; - return result; -} - -int odb_mkstemp(struct strbuf *temp_filename, const char *pattern) -{ - int fd; - /* - * we let the umask do its job, don't try to be more - * restrictive except to remove write permission. - */ - int mode = 0444; - repo_git_path_replace(the_repository, temp_filename, "objects/%s", pattern); - fd = git_mkstemp_mode(temp_filename->buf, mode); - if (0 <= fd) - return fd; - - /* slow path */ - /* some mkstemp implementations erase temp_filename on failure */ - repo_git_path_replace(the_repository, temp_filename, "objects/%s", pattern); - safe_create_leading_directories(temp_filename->buf); - return xmkstemp_mode(temp_filename->buf, mode); -} - -int odb_pack_keep(const char *name) -{ - int fd; - - fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600); - if (0 <= fd) - return fd; - - /* slow path */ - safe_create_leading_directories_const(name); - return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); -} - static void fill_loose_path(struct strbuf *buf, const struct object_id *oid) { int i; @@ -520,9 +55,9 @@ static void fill_loose_path(struct strbuf *buf, const struct object_id *oid) } } -static const char *odb_loose_path(struct object_directory *odb, - struct strbuf *buf, - const struct object_id *oid) +const char *odb_loose_path(struct object_directory *odb, + struct strbuf *buf, + const struct object_id *oid) { strbuf_reset(buf); strbuf_addstr(buf, odb->path); @@ -531,513 +66,6 @@ static const char *odb_loose_path(struct object_directory *odb, return buf->buf; } -const char *loose_object_path(struct repository *r, struct strbuf *buf, - const struct object_id *oid) -{ - return odb_loose_path(r->objects->odb, buf, oid); -} - -/* - * Return non-zero iff the path is usable as an alternate object database. - */ -static int alt_odb_usable(struct raw_object_store *o, - struct strbuf *path, - const char *normalized_objdir, khiter_t *pos) -{ - int r; - - /* Detect cases where alternate disappeared */ - if (!is_directory(path->buf)) { - error(_("object directory %s does not exist; " - "check .git/objects/info/alternates"), - path->buf); - return 0; - } - - /* - * Prevent the common mistake of listing the same - * thing twice, or object directory itself. - */ - if (!o->odb_by_path) { - khiter_t p; - - o->odb_by_path = kh_init_odb_path_map(); - assert(!o->odb->next); - p = kh_put_odb_path_map(o->odb_by_path, o->odb->path, &r); - assert(r == 1); /* never used */ - kh_value(o->odb_by_path, p) = o->odb; - } - if (fspatheq(path->buf, normalized_objdir)) - return 0; - *pos = kh_put_odb_path_map(o->odb_by_path, path->buf, &r); - /* r: 0 = exists, 1 = never used, 2 = deleted */ - return r == 0 ? 0 : 1; -} - -/* - * Prepare alternate object database registry. - * - * The variable alt_odb_list points at the list of struct - * object_directory. The elements on this list come from - * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT - * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, - * whose contents is similar to that environment variable but can be - * LF separated. Its base points at a statically allocated buffer that - * contains "/the/directory/corresponding/to/.git/objects/...", while - * its name points just after the slash at the end of ".git/objects/" - * in the example above, and has enough space to hold all hex characters - * of the object ID, an extra slash for the first level indirection, and - * the terminating NUL. - */ -static void read_info_alternates(struct repository *r, - const char *relative_base, - int depth); -static int link_alt_odb_entry(struct repository *r, const struct strbuf *entry, - const char *relative_base, int depth, const char *normalized_objdir) -{ - struct object_directory *ent; - struct strbuf pathbuf = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; - khiter_t pos; - int ret = -1; - - if (!is_absolute_path(entry->buf) && relative_base) { - strbuf_realpath(&pathbuf, relative_base, 1); - strbuf_addch(&pathbuf, '/'); - } - strbuf_addbuf(&pathbuf, entry); - - if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { - error(_("unable to normalize alternate object path: %s"), - pathbuf.buf); - goto error; - } - strbuf_swap(&pathbuf, &tmp); - - /* - * The trailing slash after the directory name is given by - * this function at the end. Remove duplicates. - */ - while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') - strbuf_setlen(&pathbuf, pathbuf.len - 1); - - if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir, &pos)) - goto error; - - CALLOC_ARRAY(ent, 1); - /* pathbuf.buf is already in r->objects->odb_by_path */ - ent->path = strbuf_detach(&pathbuf, NULL); - - /* add the alternate entry */ - *r->objects->odb_tail = ent; - r->objects->odb_tail = &(ent->next); - ent->next = NULL; - assert(r->objects->odb_by_path); - kh_value(r->objects->odb_by_path, pos) = ent; - - /* recursively add alternates */ - read_info_alternates(r, ent->path, depth + 1); - ret = 0; - error: - strbuf_release(&tmp); - strbuf_release(&pathbuf); - return ret; -} - -static const char *parse_alt_odb_entry(const char *string, - int sep, - struct strbuf *out) -{ - const char *end; - - strbuf_reset(out); - - if (*string == '#') { - /* comment; consume up to next separator */ - end = strchrnul(string, sep); - } else if (*string == '"' && !unquote_c_style(out, string, &end)) { - /* - * quoted path; unquote_c_style has copied the - * data for us and set "end". Broken quoting (e.g., - * an entry that doesn't end with a quote) falls - * back to the unquoted case below. - */ - } else { - /* normal, unquoted path */ - end = strchrnul(string, sep); - strbuf_add(out, string, end - string); - } - - if (*end) - end++; - return end; -} - -static void link_alt_odb_entries(struct repository *r, const char *alt, - int sep, const char *relative_base, int depth) -{ - struct strbuf objdirbuf = STRBUF_INIT; - struct strbuf entry = STRBUF_INIT; - - if (!alt || !*alt) - return; - - if (depth > 5) { - error(_("%s: ignoring alternate object stores, nesting too deep"), - relative_base); - return; - } - - strbuf_realpath(&objdirbuf, r->objects->odb->path, 1); - - while (*alt) { - alt = parse_alt_odb_entry(alt, sep, &entry); - if (!entry.len) - continue; - link_alt_odb_entry(r, &entry, - relative_base, depth, objdirbuf.buf); - } - strbuf_release(&entry); - strbuf_release(&objdirbuf); -} - -static void read_info_alternates(struct repository *r, - const char *relative_base, - int depth) -{ - char *path; - struct strbuf buf = STRBUF_INIT; - - path = xstrfmt("%s/info/alternates", relative_base); - if (strbuf_read_file(&buf, path, 1024) < 0) { - warn_on_fopen_errors(path); - free(path); - return; - } - - link_alt_odb_entries(r, buf.buf, '\n', relative_base, depth); - strbuf_release(&buf); - free(path); -} - -void add_to_alternates_file(const char *reference) -{ - struct lock_file lock = LOCK_INIT; - char *alts = repo_git_path(the_repository, "objects/info/alternates"); - FILE *in, *out; - int found = 0; - - hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR); - out = fdopen_lock_file(&lock, "w"); - if (!out) - die_errno(_("unable to fdopen alternates lockfile")); - - in = fopen(alts, "r"); - if (in) { - struct strbuf line = STRBUF_INIT; - - while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(reference, line.buf)) { - found = 1; - break; - } - fprintf_or_die(out, "%s\n", line.buf); - } - - strbuf_release(&line); - fclose(in); - } - else if (errno != ENOENT) - die_errno(_("unable to read alternates file")); - - if (found) { - rollback_lock_file(&lock); - } else { - fprintf_or_die(out, "%s\n", reference); - if (commit_lock_file(&lock)) - die_errno(_("unable to move new alternates file into place")); - if (the_repository->objects->loaded_alternates) - link_alt_odb_entries(the_repository, reference, - '\n', NULL, 0); - } - free(alts); -} - -void add_to_alternates_memory(const char *reference) -{ - /* - * Make sure alternates are initialized, or else our entry may be - * overwritten when they are. - */ - prepare_alt_odb(the_repository); - - link_alt_odb_entries(the_repository, reference, - '\n', NULL, 0); -} - -struct object_directory *set_temporary_primary_odb(const char *dir, int will_destroy) -{ - struct object_directory *new_odb; - - /* - * Make sure alternates are initialized, or else our entry may be - * overwritten when they are. - */ - prepare_alt_odb(the_repository); - - /* - * Make a new primary odb and link the old primary ODB in as an - * alternate - */ - new_odb = xcalloc(1, sizeof(*new_odb)); - new_odb->path = xstrdup(dir); - - /* - * Disable ref updates while a temporary odb is active, since - * the objects in the database may roll back. - */ - new_odb->disable_ref_updates = 1; - new_odb->will_destroy = will_destroy; - new_odb->next = the_repository->objects->odb; - the_repository->objects->odb = new_odb; - return new_odb->next; -} - -void restore_primary_odb(struct object_directory *restore_odb, const char *old_path) -{ - struct object_directory *cur_odb = the_repository->objects->odb; - - if (strcmp(old_path, cur_odb->path)) - BUG("expected %s as primary object store; found %s", - old_path, cur_odb->path); - - if (cur_odb->next != restore_odb) - BUG("we expect the old primary object store to be the first alternate"); - - the_repository->objects->odb = restore_odb; - free_object_directory(cur_odb); -} - -/* - * Compute the exact path an alternate is at and returns it. In case of - * error NULL is returned and the human readable error is added to `err` - * `path` may be relative and should point to $GIT_DIR. - * `err` must not be null. - */ -char *compute_alternate_path(const char *path, struct strbuf *err) -{ - char *ref_git = NULL; - const char *repo; - int seen_error = 0; - - ref_git = real_pathdup(path, 0); - if (!ref_git) { - seen_error = 1; - strbuf_addf(err, _("path '%s' does not exist"), path); - goto out; - } - - repo = read_gitfile(ref_git); - if (!repo) - repo = read_gitfile(mkpath("%s/.git", ref_git)); - if (repo) { - free(ref_git); - ref_git = xstrdup(repo); - } - - if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { - char *ref_git_git = mkpathdup("%s/.git", ref_git); - free(ref_git); - ref_git = ref_git_git; - } else if (!is_directory(mkpath("%s/objects", ref_git))) { - struct strbuf sb = STRBUF_INIT; - seen_error = 1; - if (get_common_dir(&sb, ref_git)) { - strbuf_addf(err, - _("reference repository '%s' as a linked " - "checkout is not supported yet."), - path); - goto out; - } - - strbuf_addf(err, _("reference repository '%s' is not a " - "local repository."), path); - goto out; - } - - if (!access(mkpath("%s/shallow", ref_git), F_OK)) { - strbuf_addf(err, _("reference repository '%s' is shallow"), - path); - seen_error = 1; - goto out; - } - - if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) { - strbuf_addf(err, - _("reference repository '%s' is grafted"), - path); - seen_error = 1; - goto out; - } - -out: - if (seen_error) { - FREE_AND_NULL(ref_git); - } - - return ref_git; -} - -struct object_directory *find_odb(struct repository *r, const char *obj_dir) -{ - struct object_directory *odb; - char *obj_dir_real = real_pathdup(obj_dir, 1); - struct strbuf odb_path_real = STRBUF_INIT; - - prepare_alt_odb(r); - for (odb = r->objects->odb; odb; odb = odb->next) { - strbuf_realpath(&odb_path_real, odb->path, 1); - if (!strcmp(obj_dir_real, odb_path_real.buf)) - break; - } - - free(obj_dir_real); - strbuf_release(&odb_path_real); - - if (!odb) - die(_("could not find object directory matching %s"), obj_dir); - return odb; -} - -static void fill_alternate_refs_command(struct child_process *cmd, - const char *repo_path) -{ - const char *value; - - if (!git_config_get_value("core.alternateRefsCommand", &value)) { - cmd->use_shell = 1; - - strvec_push(&cmd->args, value); - strvec_push(&cmd->args, repo_path); - } else { - cmd->git_cmd = 1; - - strvec_pushf(&cmd->args, "--git-dir=%s", repo_path); - strvec_push(&cmd->args, "for-each-ref"); - strvec_push(&cmd->args, "--format=%(objectname)"); - - if (!git_config_get_value("core.alternateRefsPrefixes", &value)) { - strvec_push(&cmd->args, "--"); - strvec_split(&cmd->args, value); - } - } - - strvec_pushv(&cmd->env, (const char **)local_repo_env); - cmd->out = -1; -} - -static void read_alternate_refs(const char *path, - alternate_ref_fn *cb, - void *data) -{ - struct child_process cmd = CHILD_PROCESS_INIT; - struct strbuf line = STRBUF_INIT; - FILE *fh; - - fill_alternate_refs_command(&cmd, path); - - if (start_command(&cmd)) - return; - - fh = xfdopen(cmd.out, "r"); - while (strbuf_getline_lf(&line, fh) != EOF) { - struct object_id oid; - const char *p; - - if (parse_oid_hex(line.buf, &oid, &p) || *p) { - warning(_("invalid line while parsing alternate refs: %s"), - line.buf); - break; - } - - cb(&oid, data); - } - - fclose(fh); - finish_command(&cmd); - strbuf_release(&line); -} - -struct alternate_refs_data { - alternate_ref_fn *fn; - void *data; -}; - -static int refs_from_alternate_cb(struct object_directory *e, - void *data) -{ - struct strbuf path = STRBUF_INIT; - size_t base_len; - struct alternate_refs_data *cb = data; - - if (!strbuf_realpath(&path, e->path, 0)) - goto out; - if (!strbuf_strip_suffix(&path, "/objects")) - goto out; - base_len = path.len; - - /* Is this a git repository with refs? */ - strbuf_addstr(&path, "/refs"); - if (!is_directory(path.buf)) - goto out; - strbuf_setlen(&path, base_len); - - read_alternate_refs(path.buf, cb->fn, cb->data); - -out: - strbuf_release(&path); - return 0; -} - -void for_each_alternate_ref(alternate_ref_fn fn, void *data) -{ - struct alternate_refs_data cb; - cb.fn = fn; - cb.data = data; - foreach_alt_odb(refs_from_alternate_cb, &cb); -} - -int foreach_alt_odb(alt_odb_fn fn, void *cb) -{ - struct object_directory *ent; - int r = 0; - - prepare_alt_odb(the_repository); - for (ent = the_repository->objects->odb->next; ent; ent = ent->next) { - r = fn(ent, cb); - if (r) - break; - } - return r; -} - -void prepare_alt_odb(struct repository *r) -{ - if (r->objects->loaded_alternates) - return; - - link_alt_odb_entries(r, r->objects->alternate_db, PATH_SEP, NULL, 0); - - read_info_alternates(r, r->objects->odb->path, 0); - r->objects->loaded_alternates = 1; -} - -int has_alt_odb(struct repository *r) -{ - prepare_alt_odb(r); - return !!r->objects->odb->next; -} - /* Returns 1 if we have successfully freshened the file, 0 otherwise. */ static int freshen_file(const char *fn) { @@ -1102,54 +130,6 @@ int has_loose_object(const struct object_id *oid) return check_and_freshen(oid, 0); } -static void mmap_limit_check(size_t length) -{ - static size_t limit = 0; - if (!limit) { - limit = git_env_ulong("GIT_MMAP_LIMIT", 0); - if (!limit) - limit = SIZE_MAX; - } - if (length > limit) - die(_("attempting to mmap %"PRIuMAX" over limit %"PRIuMAX), - (uintmax_t)length, (uintmax_t)limit); -} - -void *xmmap_gently(void *start, size_t length, - int prot, int flags, int fd, off_t offset) -{ - void *ret; - - mmap_limit_check(length); - ret = mmap(start, length, prot, flags, fd, offset); - if (ret == MAP_FAILED && !length) - ret = NULL; - return ret; -} - -const char *mmap_os_err(void) -{ - static const char blank[] = ""; -#if defined(__linux__) - if (errno == ENOMEM) { - /* this continues an existing error message: */ - static const char enomem[] = -", check sys.vm.max_map_count and/or RLIMIT_DATA"; - return enomem; - } -#endif /* OS-specific bits */ - return blank; -} - -void *xmmap(void *start, size_t length, - int prot, int flags, int fd, off_t offset) -{ - void *ret = xmmap_gently(start, length, prot, flags, fd, offset); - if (ret == MAP_FAILED) - die_errno(_("mmap failed%s"), mmap_os_err()); - return ret; -} - static int format_object_header_literally(char *str, size_t size, const char *type, size_t objsize) { @@ -1217,33 +197,6 @@ int stream_object_signature(struct repository *r, const struct object_id *oid) return !oideq(oid, &real_oid) ? -1 : 0; } -int git_open_cloexec(const char *name, int flags) -{ - int fd; - static int o_cloexec = O_CLOEXEC; - - fd = open(name, flags | o_cloexec); - if ((o_cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { - /* Try again w/o O_CLOEXEC: the kernel might not support it */ - o_cloexec &= ~O_CLOEXEC; - fd = open(name, flags | o_cloexec); - } - -#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) - { - static int fd_cloexec = FD_CLOEXEC; - - if (!o_cloexec && 0 <= fd && fd_cloexec) { - /* Opened w/o O_CLOEXEC? try with fcntl(2) to add it */ - int flags = fcntl(fd, F_GETFD); - if (fcntl(fd, F_SETFD, flags | fd_cloexec)) - fd_cloexec = 0; - } - } -#endif - return fd; -} - /* * Find "oid" as a loose object in the local repository or in an alternate. * Returns 0 on success, negative on failure. @@ -1362,7 +315,7 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, obj_read_unlock(); status = git_inflate(stream, 0); obj_read_lock(); - if (status < Z_OK) + if (status != Z_OK && status != Z_STREAM_END) return ULHR_BAD; /* @@ -1385,20 +338,19 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, * reading the stream. */ strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer); - stream->next_out = buffer; - stream->avail_out = bufsiz; do { + stream->next_out = buffer; + stream->avail_out = bufsiz; + obj_read_unlock(); status = git_inflate(stream, 0); obj_read_lock(); strbuf_add(header, buffer, stream->next_out - (unsigned char *)buffer); if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer)) return 0; - stream->next_out = buffer; - stream->avail_out = bufsiz; - } while (status != Z_STREAM_END); - return ULHR_TOO_LONG; + } while (status == Z_OK); + return ULHR_BAD; } static void *unpack_loose_rest(git_zstream *stream, @@ -1437,18 +389,17 @@ static void *unpack_loose_rest(git_zstream *stream, obj_read_lock(); } } - if (status == Z_STREAM_END && !stream->avail_in) { - git_inflate_end(stream); - return buf; - } - if (status < 0) + if (status != Z_STREAM_END) { error(_("corrupt loose object '%s'"), oid_to_hex(oid)); - else if (stream->avail_in) + FREE_AND_NULL(buf); + } else if (stream->avail_in) { error(_("garbage at end of loose object '%s'"), oid_to_hex(oid)); - free(buf); - return NULL; + FREE_AND_NULL(buf); + } + + return buf; } /* @@ -1514,9 +465,9 @@ int parse_loose_header(const char *hdr, struct object_info *oi) return 0; } -static int loose_object_info(struct repository *r, - const struct object_id *oid, - struct object_info *oi, int flags) +int loose_object_info(struct repository *r, + const struct object_id *oid, + struct object_info *oi, int flags) { int status = 0; int fd; @@ -1580,6 +531,8 @@ static int loose_object_info(struct repository *r, if (!oi->contentp) break; + if (hdrbuf.len) + BUG("unpacking content with unknown types not yet supported"); *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); if (*oi->contentp) goto cleanup; @@ -1600,8 +553,8 @@ static int loose_object_info(struct repository *r, die(_("loose object %s (stored in %s) is corrupt"), oid_to_hex(oid), path); - git_inflate_end(&stream); cleanup: + git_inflate_end(&stream); munmap(map, mapsize); if (oi->sizep == &size_scratch) oi->sizep = NULL; @@ -1612,345 +565,6 @@ cleanup: return status; } -int obj_read_use_lock = 0; -pthread_mutex_t obj_read_mutex; - -void enable_obj_read_lock(void) -{ - if (obj_read_use_lock) - return; - - obj_read_use_lock = 1; - init_recursive_mutex(&obj_read_mutex); -} - -void disable_obj_read_lock(void) -{ - if (!obj_read_use_lock) - return; - - obj_read_use_lock = 0; - pthread_mutex_destroy(&obj_read_mutex); -} - -int fetch_if_missing = 1; - -static int do_oid_object_info_extended(struct repository *r, - const struct object_id *oid, - struct object_info *oi, unsigned flags) -{ - static struct object_info blank_oi = OBJECT_INFO_INIT; - const struct cached_object *co; - struct pack_entry e; - int rtype; - const struct object_id *real = oid; - int already_retried = 0; - - - if (flags & OBJECT_INFO_LOOKUP_REPLACE) - real = lookup_replace_object(r, oid); - - if (is_null_oid(real)) - return -1; - - if (!oi) - oi = &blank_oi; - - co = find_cached_object(real); - if (co) { - if (oi->typep) - *(oi->typep) = co->type; - if (oi->sizep) - *(oi->sizep) = co->size; - if (oi->disk_sizep) - *(oi->disk_sizep) = 0; - if (oi->delta_base_oid) - oidclr(oi->delta_base_oid, the_repository->hash_algo); - if (oi->type_name) - strbuf_addstr(oi->type_name, type_name(co->type)); - if (oi->contentp) - *oi->contentp = xmemdupz(co->buf, co->size); - oi->whence = OI_CACHED; - return 0; - } - - while (1) { - if (find_pack_entry(r, real, &e)) - break; - - /* Most likely it's a loose object. */ - if (!loose_object_info(r, real, oi, flags)) - return 0; - - /* Not a loose object; someone else may have just packed it. */ - if (!(flags & OBJECT_INFO_QUICK)) { - reprepare_packed_git(r); - if (find_pack_entry(r, real, &e)) - break; - } - - /* - * If r is the_repository, this might be an attempt at - * accessing a submodule object as if it were in the_repository - * (having called add_submodule_odb() on that submodule's ODB). - * If any such ODBs exist, register them and try again. - */ - if (r == the_repository && - register_all_submodule_odb_as_alternates()) - /* We added some alternates; retry */ - continue; - - /* Check if it is a missing object */ - if (fetch_if_missing && repo_has_promisor_remote(r) && - !already_retried && - !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) { - promisor_remote_get_direct(r, real, 1); - already_retried = 1; - continue; - } - - if (flags & OBJECT_INFO_DIE_IF_CORRUPT) { - const struct packed_git *p; - if ((flags & OBJECT_INFO_LOOKUP_REPLACE) && !oideq(real, oid)) - die(_("replacement %s not found for %s"), - oid_to_hex(real), oid_to_hex(oid)); - if ((p = has_packed_and_bad(r, real))) - die(_("packed object %s (stored in %s) is corrupt"), - oid_to_hex(real), p->pack_name); - } - return -1; - } - - if (oi == &blank_oi) - /* - * We know that the caller doesn't actually need the - * information below, so return early. - */ - return 0; - rtype = packed_object_info(r, e.p, e.offset, oi); - if (rtype < 0) { - mark_bad_packed_object(e.p, real); - return do_oid_object_info_extended(r, real, oi, 0); - } else if (oi->whence == OI_PACKED) { - oi->u.packed.offset = e.offset; - oi->u.packed.pack = e.p; - oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA || - rtype == OBJ_OFS_DELTA); - } - - return 0; -} - -static int oid_object_info_convert(struct repository *r, - const struct object_id *input_oid, - struct object_info *input_oi, unsigned flags) -{ - const struct git_hash_algo *input_algo = &hash_algos[input_oid->algo]; - int do_die = flags & OBJECT_INFO_DIE_IF_CORRUPT; - struct strbuf type_name = STRBUF_INIT; - struct object_id oid, delta_base_oid; - struct object_info new_oi, *oi; - unsigned long size; - void *content; - int ret; - - if (repo_oid_to_algop(r, input_oid, the_hash_algo, &oid)) { - if (do_die) - die(_("missing mapping of %s to %s"), - oid_to_hex(input_oid), the_hash_algo->name); - return -1; - } - - /* Is new_oi needed? */ - oi = input_oi; - if (input_oi && (input_oi->delta_base_oid || input_oi->sizep || - input_oi->contentp)) { - new_oi = *input_oi; - /* Does delta_base_oid need to be converted? */ - if (input_oi->delta_base_oid) - new_oi.delta_base_oid = &delta_base_oid; - /* Will the attributes differ when converted? */ - if (input_oi->sizep || input_oi->contentp) { - new_oi.contentp = &content; - new_oi.sizep = &size; - new_oi.type_name = &type_name; - } - oi = &new_oi; - } - - ret = oid_object_info_extended(r, &oid, oi, flags); - if (ret) - return -1; - if (oi == input_oi) - return ret; - - if (new_oi.contentp) { - struct strbuf outbuf = STRBUF_INIT; - enum object_type type; - - type = type_from_string_gently(type_name.buf, type_name.len, - !do_die); - if (type == -1) - return -1; - if (type != OBJ_BLOB) { - ret = convert_object_file(&outbuf, - the_hash_algo, input_algo, - content, size, type, !do_die); - free(content); - if (ret == -1) - return -1; - size = outbuf.len; - content = strbuf_detach(&outbuf, NULL); - } - if (input_oi->sizep) - *input_oi->sizep = size; - if (input_oi->contentp) - *input_oi->contentp = content; - else - free(content); - if (input_oi->type_name) - *input_oi->type_name = type_name; - else - strbuf_release(&type_name); - } - if (new_oi.delta_base_oid == &delta_base_oid) { - if (repo_oid_to_algop(r, &delta_base_oid, input_algo, - input_oi->delta_base_oid)) { - if (do_die) - die(_("missing mapping of %s to %s"), - oid_to_hex(&delta_base_oid), - input_algo->name); - return -1; - } - } - input_oi->whence = new_oi.whence; - input_oi->u = new_oi.u; - return ret; -} - -int oid_object_info_extended(struct repository *r, const struct object_id *oid, - struct object_info *oi, unsigned flags) -{ - int ret; - - if (oid->algo && (hash_algo_by_ptr(r->hash_algo) != oid->algo)) - return oid_object_info_convert(r, oid, oi, flags); - - obj_read_lock(); - ret = do_oid_object_info_extended(r, oid, oi, flags); - obj_read_unlock(); - return ret; -} - - -/* returns enum object_type or negative */ -int oid_object_info(struct repository *r, - const struct object_id *oid, - unsigned long *sizep) -{ - enum object_type type; - struct object_info oi = OBJECT_INFO_INIT; - - oi.typep = &type; - oi.sizep = sizep; - if (oid_object_info_extended(r, oid, &oi, - OBJECT_INFO_LOOKUP_REPLACE) < 0) - return -1; - return type; -} - -int pretend_object_file(void *buf, unsigned long len, enum object_type type, - struct object_id *oid) -{ - struct cached_object_entry *co; - char *co_buf; - - hash_object_file(the_hash_algo, buf, len, type, oid); - if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) || - find_cached_object(oid)) - return 0; - ALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc); - co = &cached_objects[cached_object_nr++]; - co->value.size = len; - co->value.type = type; - co_buf = xmalloc(len); - memcpy(co_buf, buf, len); - co->value.buf = co_buf; - oidcpy(&co->oid, oid); - return 0; -} - -/* - * This function dies on corrupt objects; the callers who want to - * deal with them should arrange to call oid_object_info_extended() and give - * error messages themselves. - */ -void *repo_read_object_file(struct repository *r, - const struct object_id *oid, - enum object_type *type, - unsigned long *size) -{ - struct object_info oi = OBJECT_INFO_INIT; - unsigned flags = OBJECT_INFO_DIE_IF_CORRUPT | OBJECT_INFO_LOOKUP_REPLACE; - void *data; - - oi.typep = type; - oi.sizep = size; - oi.contentp = &data; - if (oid_object_info_extended(r, oid, &oi, flags)) - return NULL; - - return data; -} - -void *read_object_with_reference(struct repository *r, - const struct object_id *oid, - enum object_type required_type, - unsigned long *size, - struct object_id *actual_oid_return) -{ - enum object_type type; - void *buffer; - unsigned long isize; - struct object_id actual_oid; - - oidcpy(&actual_oid, oid); - while (1) { - int ref_length = -1; - const char *ref_type = NULL; - - buffer = repo_read_object_file(r, &actual_oid, &type, &isize); - if (!buffer) - return NULL; - if (type == required_type) { - *size = isize; - if (actual_oid_return) - oidcpy(actual_oid_return, &actual_oid); - return buffer; - } - /* Handle references */ - else if (type == OBJ_COMMIT) - ref_type = "tree "; - else if (type == OBJ_TAG) - ref_type = "object "; - else { - free(buffer); - return NULL; - } - ref_length = strlen(ref_type); - - if (ref_length + the_hash_algo->hexsz > isize || - memcmp(buffer, ref_type, ref_length) || - get_oid_hex((char *) buffer + ref_length, &actual_oid)) { - free(buffer); - return NULL; - } - free(buffer); - /* Now we have the ID of the referred-to object in - * actual_oid. Check again. */ - } -} - static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c, const void *buf, unsigned long len, struct object_id *oid, @@ -2222,7 +836,7 @@ static int start_loose_object_common(struct strbuf *tmp_file, fd = create_tmpfile(tmp_file, filename); if (fd < 0) { - if (flags & HASH_SILENT) + if (flags & WRITE_OBJECT_FILE_SILENT) return -1; else if (errno == EACCES) return error(_("insufficient permission for adding " @@ -2354,7 +968,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr, utb.actime = mtime; utb.modtime = mtime; if (utime(tmp_file.buf, &utb) < 0 && - !(flags & HASH_SILENT)) + !(flags & WRITE_OBJECT_FILE_SILENT)) warning_errno(_("failed utime() on %s"), tmp_file.buf); } @@ -2473,7 +1087,8 @@ int stream_loose_object(struct input_stream *in_stream, size_t len, struct strbuf dir = STRBUF_INIT; strbuf_add(&dir, filename.buf, dirlen); - if (mkdir_in_gitdir(dir.buf) && errno != EEXIST) { + if (safe_create_dir_in_gitdir(the_repository, dir.buf) && + errno != EEXIST) { err = error_errno(_("unable to create directory %s"), dir.buf); strbuf_release(&dir); goto cleanup; @@ -2510,7 +1125,7 @@ int write_object_file_flags(const void *buf, unsigned long len, hash_object_file(compat, buf, len, type, &compat_oid); else { struct strbuf converted = STRBUF_INIT; - convert_object_file(&converted, algo, compat, + convert_object_file(the_repository, &converted, algo, compat, buf, len, type, 0); hash_object_file(compat, converted.buf, converted.len, type, &compat_oid); @@ -2550,7 +1165,8 @@ int write_object_file_literally(const void *buf, unsigned long len, &compat_oid); else if (compat_type != -1) { struct strbuf converted = STRBUF_INIT; - convert_object_file(&converted, algo, compat, + convert_object_file(the_repository, + &converted, algo, compat, buf, len, compat_type, 0); hash_object_file(compat, converted.buf, converted.len, compat_type, &compat_oid); @@ -2564,7 +1180,7 @@ int write_object_file_literally(const void *buf, unsigned long len, write_object_file_prepare_literally(the_hash_algo, buf, len, type, oid, header, &hdrlen); - if (!(flags & HASH_WRITE_OBJECT)) + if (!(flags & WRITE_OBJECT_FILE_PERSIST)) goto cleanup; if (freshen_packed_object(oid) || freshen_loose_object(oid)) goto cleanup; @@ -2611,32 +1227,6 @@ int force_object_loose(const struct object_id *oid, time_t mtime) return ret; } -int has_object(struct repository *r, const struct object_id *oid, - unsigned flags) -{ - int quick = !(flags & HAS_OBJECT_RECHECK_PACKED); - unsigned object_info_flags = OBJECT_INFO_SKIP_FETCH_OBJECT | - (quick ? OBJECT_INFO_QUICK : 0); - - if (!startup_info->have_repository) - return 0; - return oid_object_info_extended(r, oid, NULL, object_info_flags) >= 0; -} - -int repo_has_object_file_with_flags(struct repository *r, - const struct object_id *oid, int flags) -{ - if (!startup_info->have_repository) - return 0; - return oid_object_info_extended(r, oid, NULL, flags) >= 0; -} - -int repo_has_object_file(struct repository *r, - const struct object_id *oid) -{ - return repo_has_object_file_with_flags(r, oid, 0); -} - /* * We can't use the normal fsck_error_function() for index_mem(), * because we don't yet have a valid oid for it to report. Instead, @@ -2661,7 +1251,7 @@ static int index_mem(struct index_state *istate, { struct strbuf nbuf = STRBUF_INIT; int ret = 0; - int write_object = flags & HASH_WRITE_OBJECT; + int write_object = flags & INDEX_WRITE_OBJECT; if (!type) type = OBJ_BLOB; @@ -2676,12 +1266,12 @@ static int index_mem(struct index_state *istate, size = nbuf.len; } } - if (flags & HASH_FORMAT_CHECK) { + if (flags & INDEX_FORMAT_CHECK) { struct fsck_options opts = FSCK_OPTIONS_DEFAULT; opts.strict = 1; opts.error_func = hash_format_check_report; - if (fsck_buffer(null_oid(), type, buf, size, &opts)) + if (fsck_buffer(null_oid(the_hash_algo), type, buf, size, &opts)) die(_("refusing to create malformed object")); fsck_finish(&opts); } @@ -2702,7 +1292,7 @@ static int index_stream_convert_blob(struct index_state *istate, unsigned flags) { int ret = 0; - const int write_object = flags & HASH_WRITE_OBJECT; + const int write_object = flags & INDEX_WRITE_OBJECT; struct strbuf sbuf = STRBUF_INIT; assert(path); @@ -2767,28 +1357,6 @@ static int index_core(struct index_state *istate, return ret; } -/* - * This creates one packfile per large blob unless bulk-checkin - * machinery is "plugged". - * - * This also bypasses the usual "convert-to-git" dance, and that is on - * purpose. We could write a streaming version of the converting - * functions and insert that before feeding the data to fast-import - * (or equivalent in-core API described above). However, that is - * somewhat complicated, as we do not know the size of the filter - * result, which we need to know beforehand when writing a git object. - * Since the primary motivation for trying to stream from the working - * tree file and to avoid mmaping it in core is to deal with large - * binary blobs, they generally do not want to get any conversion, and - * callers should avoid this code path when filters are requested. - */ -static int index_blob_stream(struct object_id *oid, int fd, size_t size, - const char *path, - unsigned flags) -{ - return index_blob_bulk_checkin(oid, fd, size, path, flags); -} - int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags) @@ -2803,13 +1371,14 @@ int index_fd(struct index_state *istate, struct object_id *oid, ret = index_stream_convert_blob(istate, oid, fd, path, flags); else if (!S_ISREG(st->st_mode)) ret = index_pipe(istate, oid, fd, type, path, flags); - else if (st->st_size <= big_file_threshold || type != OBJ_BLOB || + else if (st->st_size <= repo_settings_get_big_file_threshold(the_repository) || + type != OBJ_BLOB || (path && would_convert_to_git(istate, path))) ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); else - ret = index_blob_stream(oid, fd, xsize_t(st->st_size), path, - flags); + ret = index_blob_bulk_checkin(oid, fd, xsize_t(st->st_size), path, + flags); close(fd); return ret; } @@ -2833,7 +1402,7 @@ int index_path(struct index_state *istate, struct object_id *oid, case S_IFLNK: if (strbuf_readlink(&sb, path, st->st_size)) return error_errno("readlink(\"%s\")", path); - if (!(flags & HASH_WRITE_OBJECT)) + if (!(flags & INDEX_WRITE_OBJECT)) hash_object_file(the_hash_algo, sb.buf, sb.len, OBJ_BLOB, oid); else if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid)) @@ -2863,16 +1432,6 @@ int read_pack_header(int fd, struct pack_header *header) return 0; } -void assert_oid_type(const struct object_id *oid, enum object_type expect) -{ - enum object_type type = oid_object_info(the_repository, oid, NULL); - if (type < 0) - die(_("%s is not a valid object"), oid_to_hex(oid)); - if (type != expect) - die(_("%s is not a valid '%s' object"), oid_to_hex(oid), - type_name(expect)); -} - int for_each_file_in_obj_subdir(unsigned int subdir_nr, struct strbuf *path, each_loose_object_fn obj_cb, @@ -3080,7 +1639,6 @@ static int check_stream_oid(git_zstream *stream, git_hash_update(&c, buf, stream->next_out - buf); total_read += stream->next_out - buf; } - git_inflate_end(stream); if (status != Z_STREAM_END) { error(_("corrupt loose object '%s'"), oid_to_hex(expected_oid)); @@ -3127,35 +1685,35 @@ int read_loose_object(const char *path, if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr), NULL) != ULHR_OK) { error(_("unable to unpack header of %s"), path); - git_inflate_end(&stream); - goto out; + goto out_inflate; } if (parse_loose_header(hdr, oi) < 0) { error(_("unable to parse header of %s"), path); - git_inflate_end(&stream); - goto out; + goto out_inflate; } - if (*oi->typep == OBJ_BLOB && *size > big_file_threshold) { + if (*oi->typep == OBJ_BLOB && + *size > repo_settings_get_big_file_threshold(the_repository)) { if (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0) - goto out; + goto out_inflate; } else { *contents = unpack_loose_rest(&stream, hdr, *size, expected_oid); if (!*contents) { error(_("unable to unpack contents of %s"), path); - git_inflate_end(&stream); - goto out; + goto out_inflate; } hash_object_file_literally(the_repository->hash_algo, *contents, *size, oi->type_name->buf, real_oid); if (!oideq(expected_oid, real_oid)) - goto out; + goto out_inflate; } ret = 0; /* everything checks out */ +out_inflate: + git_inflate_end(&stream); out: if (map) munmap(map, mapsize); diff --git a/object-file.h b/object-file.h index 81b30d269c..c002fbe234 100644 --- a/object-file.h +++ b/object-file.h @@ -14,50 +14,37 @@ struct index_state; */ extern int fetch_if_missing; -#define HASH_WRITE_OBJECT 1 -#define HASH_FORMAT_CHECK 2 -#define HASH_RENORMALIZE 4 -#define HASH_SILENT 8 +enum { + INDEX_WRITE_OBJECT = (1 << 0), + INDEX_FORMAT_CHECK = (1 << 1), + INDEX_RENORMALIZE = (1 << 2), +}; + int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags); +struct object_directory; + +const char *odb_loose_path(struct object_directory *odb, + struct strbuf *buf, + const struct object_id *oid); + /* - * Create the directory containing the named path, using care to be - * somewhat safe against races. Return one of the scld_error values to - * indicate success/failure. On error, set errno to describe the - * problem. - * - * SCLD_VANISHED indicates that one of the ancestor directories of the - * path existed at one point during the function call and then - * suddenly vanished, probably because another process pruned the - * directory while we were working. To be robust against this kind of - * race, callers might want to try invoking the function again when it - * returns SCLD_VANISHED. - * - * safe_create_leading_directories() temporarily changes path while it - * is working but restores it before returning. - * safe_create_leading_directories_const() doesn't modify path, even - * temporarily. Both these variants adjust the permissions of the - * created directories to honor core.sharedRepository, so they are best - * suited for files inside the git dir. For working tree files, use - * safe_create_leading_directories_no_share() instead, as it ignores - * the core.sharedRepository setting. + * Return true iff an alternate object database has a loose object + * with the specified name. This function does not respect replace + * references. */ -enum scld_error { - SCLD_OK = 0, - SCLD_FAILED = -1, - SCLD_PERMS = -2, - SCLD_EXISTS = -3, - SCLD_VANISHED = -4 -}; -enum scld_error safe_create_leading_directories(char *path); -enum scld_error safe_create_leading_directories_const(const char *path); -enum scld_error safe_create_leading_directories_no_share(char *path); +int has_loose_object_nonlocal(const struct object_id *); -int mkdir_in_gitdir(const char *path); +int has_loose_object(const struct object_id *); -int git_open_cloexec(const char *name, int flags); -#define git_open(name) git_open_cloexec(name, O_RDONLY) +/** + * format_object_header() is a thin wrapper around s xsnprintf() that + * writes the initial "<type> <obj-len>" part of the loose object + * header. It returns the size that snprintf() returns + 1. + */ +int format_object_header(char *str, size_t size, enum object_type type, + size_t objsize); /** * unpack_loose_header() initializes the data stream needed to unpack @@ -99,6 +86,44 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, struct object_info; int parse_loose_header(const char *hdr, struct object_info *oi); +enum { + /* + * By default, `write_object_file_literally()` does not actually write + * anything into the object store, but only computes the object ID. + * This flag changes that so that the object will be written as a loose + * object and persisted. + */ + WRITE_OBJECT_FILE_PERSIST = (1 << 0), + + /* + * Do not print an error in case something gose wrong. + */ + WRITE_OBJECT_FILE_SILENT = (1 << 1), +}; + +int write_object_file_flags(const void *buf, unsigned long len, + enum object_type type, struct object_id *oid, + struct object_id *comapt_oid_in, unsigned flags); +static inline int write_object_file(const void *buf, unsigned long len, + enum object_type type, struct object_id *oid) +{ + return write_object_file_flags(buf, len, type, oid, NULL, 0); +} + +struct input_stream { + const void *(*read)(struct input_stream *, unsigned long *len); + void *data; + int is_finished; +}; + +int write_object_file_literally(const void *buf, unsigned long len, + const char *type, struct object_id *oid, + unsigned flags); +int stream_loose_object(struct input_stream *in_stream, size_t len, + struct object_id *oid); + +int force_object_loose(const struct object_id *oid, time_t mtime); + /** * With in-core object data in "buf", rehash it to make sure the * object name actually matches "oid" to detect object corruption. @@ -117,6 +142,10 @@ int check_object_signature(struct repository *r, const struct object_id *oid, */ int stream_object_signature(struct repository *r, const struct object_id *oid); +int loose_object_info(struct repository *r, + const struct object_id *oid, + struct object_info *oi, int flags); + enum finalize_object_file_flags { FOF_SKIP_COLLISION_CHECK = 1, }; @@ -128,10 +157,18 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename, /* Helper to check and "touch" a file */ int check_and_freshen_file(const char *fn, int freshen); -void *read_object_with_reference(struct repository *r, - const struct object_id *oid, - enum object_type required_type, - unsigned long *size, - struct object_id *oid_ret); +/* + * Open the loose object at path, check its hash, and return the contents, + * use the "oi" argument to assert things about the object, or e.g. populate its + * type, and size. If the object is a blob, then "contents" may return NULL, + * to allow streaming of large blobs. + * + * Returns 0 on success, negative on error (details may be written to stderr). + */ +int read_loose_object(const char *path, + const struct object_id *expected_oid, + struct object_id *real_oid, + void **contents, + struct object_info *oi); #endif /* OBJECT_FILE_H */ diff --git a/object-name.c b/object-name.c index 91f731373a..2c751a5352 100644 --- a/object-name.c +++ b/object-name.c @@ -19,7 +19,7 @@ #include "oidtree.h" #include "packfile.h" #include "pretty.h" -#include "object-store-ll.h" +#include "object-store.h" #include "read-cache-ll.h" #include "repo-settings.h" #include "repository.h" diff --git a/object-store-ll.h b/object-store-ll.h deleted file mode 100644 index cd3bd5bd99..0000000000 --- a/object-store-ll.h +++ /dev/null @@ -1,556 +0,0 @@ -#ifndef OBJECT_STORE_LL_H -#define OBJECT_STORE_LL_H - -#include "hashmap.h" -#include "object.h" -#include "list.h" -#include "thread-utils.h" -#include "oidset.h" - -struct oidmap; -struct oidtree; -struct strbuf; -struct repository; - -struct object_directory { - struct object_directory *next; - - /* - * Used to store the results of readdir(3) calls when we are OK - * sacrificing accuracy due to races for speed. That includes - * object existence with OBJECT_INFO_QUICK, as well as - * our search for unique abbreviated hashes. Don't use it for tasks - * requiring greater accuracy! - * - * Be sure to call odb_load_loose_cache() before using. - */ - uint32_t loose_objects_subdir_seen[8]; /* 256 bits */ - struct oidtree *loose_objects_cache; - - /* Map between object IDs for loose objects. */ - struct loose_object_map *loose_map; - - /* - * This is a temporary object store created by the tmp_objdir - * facility. Disable ref updates since the objects in the store - * might be discarded on rollback. - */ - int disable_ref_updates; - - /* - * This object store is ephemeral, so there is no need to fsync. - */ - int will_destroy; - - /* - * Path to the alternative object store. If this is a relative path, - * it is relative to the current working directory. - */ - char *path; -}; - -struct input_stream { - const void *(*read)(struct input_stream *, unsigned long *len); - void *data; - int is_finished; -}; - -void prepare_alt_odb(struct repository *r); -int has_alt_odb(struct repository *r); -char *compute_alternate_path(const char *path, struct strbuf *err); -struct object_directory *find_odb(struct repository *r, const char *obj_dir); -typedef int alt_odb_fn(struct object_directory *, void *); -int foreach_alt_odb(alt_odb_fn, void*); -typedef void alternate_ref_fn(const struct object_id *oid, void *); -void for_each_alternate_ref(alternate_ref_fn, void *); - -/* - * Add the directory to the on-disk alternates file; the new entry will also - * take effect in the current process. - */ -void add_to_alternates_file(const char *dir); - -/* - * Add the directory to the in-memory list of alternates (along with any - * recursive alternates it points to), but do not modify the on-disk alternates - * file. - */ -void add_to_alternates_memory(const char *dir); - -/* - * Replace the current writable object directory with the specified temporary - * object directory; returns the former primary object directory. - */ -struct object_directory *set_temporary_primary_odb(const char *dir, int will_destroy); - -/* - * Restore a previous ODB replaced by set_temporary_main_odb. - */ -void restore_primary_odb(struct object_directory *restore_odb, const char *old_path); - -/* - * Populate and return the loose object cache array corresponding to the - * given object ID. - */ -struct oidtree *odb_loose_cache(struct object_directory *odb, - const struct object_id *oid); - -/* Empty the loose object cache for the specified object directory. */ -void odb_clear_loose_cache(struct object_directory *odb); - -/* Clear and free the specified object directory */ -void free_object_directory(struct object_directory *odb); - -struct packed_git { - struct hashmap_entry packmap_ent; - struct packed_git *next; - struct list_head mru; - struct pack_window *windows; - off_t pack_size; - const void *index_data; - size_t index_size; - uint32_t num_objects; - size_t crc_offset; - struct oidset bad_objects; - int index_version; - time_t mtime; - int pack_fd; - int index; /* for builtin/pack-objects.c */ - unsigned pack_local:1, - pack_keep:1, - pack_keep_in_core:1, - freshened:1, - do_not_close:1, - pack_promisor:1, - multi_pack_index:1, - is_cruft:1; - unsigned char hash[GIT_MAX_RAWSZ]; - struct revindex_entry *revindex; - const uint32_t *revindex_data; - const uint32_t *revindex_map; - size_t revindex_size; - /* - * mtimes_map points at the beginning of the memory mapped region of - * this pack's corresponding .mtimes file, and mtimes_size is the size - * of that .mtimes file - */ - const uint32_t *mtimes_map; - size_t mtimes_size; - - /* repo denotes the repository this packfile belongs to */ - struct repository *repo; - - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[FLEX_ARRAY]; /* more */ -}; - -struct multi_pack_index; - -static inline int pack_map_entry_cmp(const void *cmp_data UNUSED, - const struct hashmap_entry *entry, - const struct hashmap_entry *entry2, - const void *keydata) -{ - const char *key = keydata; - const struct packed_git *pg1, *pg2; - - pg1 = container_of(entry, const struct packed_git, packmap_ent); - pg2 = container_of(entry2, const struct packed_git, packmap_ent); - - return strcmp(pg1->pack_name, key ? key : pg2->pack_name); -} - -struct raw_object_store { - /* - * Set of all object directories; the main directory is first (and - * cannot be NULL after initialization). Subsequent directories are - * alternates. - */ - struct object_directory *odb; - struct object_directory **odb_tail; - struct kh_odb_path_map *odb_by_path; - - int loaded_alternates; - - /* - * A list of alternate object directories loaded from the environment; - * this should not generally need to be accessed directly, but will - * populate the "odb" list when prepare_alt_odb() is run. - */ - char *alternate_db; - - /* - * Objects that should be substituted by other objects - * (see git-replace(1)). - */ - struct oidmap *replace_map; - unsigned replace_map_initialized : 1; - pthread_mutex_t replace_mutex; /* protect object replace functions */ - - struct commit_graph *commit_graph; - unsigned commit_graph_attempted : 1; /* if loading has been attempted */ - - /* - * private data - * - * should only be accessed directly by packfile.c and midx.c - */ - struct multi_pack_index *multi_pack_index; - - /* - * private data - * - * should only be accessed directly by packfile.c - */ - - struct packed_git *packed_git; - /* A most-recently-used ordered version of the packed_git list. */ - struct list_head packed_git_mru; - - struct { - struct packed_git **packs; - unsigned flags; - } kept_pack_cache; - - /* - * A map of packfiles to packed_git structs for tracking which - * packs have been loaded already. - */ - struct hashmap pack_map; - - /* - * A fast, rough count of the number of objects in the repository. - * These two fields are not meant for direct access. Use - * repo_approximate_object_count() instead. - */ - unsigned long approximate_object_count; - unsigned approximate_object_count_valid : 1; - - /* - * Whether packed_git has already been populated with this repository's - * packs. - */ - unsigned packed_git_initialized : 1; -}; - -struct raw_object_store *raw_object_store_new(void); -void raw_object_store_clear(struct raw_object_store *o); - -/* - * Create a temporary file rooted in the object database directory, or - * die on failure. The filename is taken from "pattern", which should have the - * usual "XXXXXX" trailer, and the resulting filename is written into the - * "template" buffer. Returns the open descriptor. - */ -int odb_mkstemp(struct strbuf *temp_filename, const char *pattern); - -/* - * Create a pack .keep file named "name" (which should generally be the output - * of odb_pack_name). Returns a file descriptor opened for writing, or -1 on - * error. - */ -int odb_pack_keep(const char *name); - -/* - * Put in `buf` the name of the file in the local object database that - * would be used to store a loose object with the specified oid. - */ -const char *loose_object_path(struct repository *r, struct strbuf *buf, - const struct object_id *oid); - -void *map_loose_object(struct repository *r, const struct object_id *oid, - unsigned long *size); - -void *repo_read_object_file(struct repository *r, - const struct object_id *oid, - enum object_type *type, - unsigned long *size); - -/* Read and unpack an object file into memory, write memory to an object file */ -int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); - -void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, - struct object_id *oid); - -int write_object_file_flags(const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - struct object_id *comapt_oid_in, unsigned flags); -static inline int write_object_file(const void *buf, unsigned long len, - enum object_type type, struct object_id *oid) -{ - return write_object_file_flags(buf, len, type, oid, NULL, 0); -} - -int write_object_file_literally(const void *buf, unsigned long len, - const char *type, struct object_id *oid, - unsigned flags); -int stream_loose_object(struct input_stream *in_stream, size_t len, - struct object_id *oid); - -/* - * Add an object file to the in-memory object store, without writing it - * to disk. - * - * Callers are responsible for calling write_object_file to record the - * object in persistent storage before writing any other new objects - * that reference it. - */ -int pretend_object_file(void *, unsigned long, enum object_type, - struct object_id *oid); - -int force_object_loose(const struct object_id *oid, time_t mtime); - -struct object_info { - /* Request */ - enum object_type *typep; - unsigned long *sizep; - off_t *disk_sizep; - struct object_id *delta_base_oid; - struct strbuf *type_name; - void **contentp; - - /* Response */ - enum { - OI_CACHED, - OI_LOOSE, - OI_PACKED, - OI_DBCACHED - } whence; - union { - /* - * struct { - * ... Nothing to expose in this case - * } cached; - * struct { - * ... Nothing to expose in this case - * } loose; - */ - struct { - struct packed_git *pack; - off_t offset; - unsigned int is_delta; - } packed; - } u; -}; - -/* - * Initializer for a "struct object_info" that wants no items. You may - * also memset() the memory to all-zeroes. - */ -#define OBJECT_INFO_INIT { 0 } - -/* Invoke lookup_replace_object() on the given hash */ -#define OBJECT_INFO_LOOKUP_REPLACE 1 -/* Allow reading from a loose object file of unknown/bogus type */ -#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2 -/* Do not retry packed storage after checking packed and loose storage */ -#define OBJECT_INFO_QUICK 8 -/* - * Do not attempt to fetch the object if missing (even if fetch_is_missing is - * nonzero). - */ -#define OBJECT_INFO_SKIP_FETCH_OBJECT 16 -/* - * This is meant for bulk prefetching of missing blobs in a partial - * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK - */ -#define OBJECT_INFO_FOR_PREFETCH (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) - -/* Die if object corruption (not just an object being missing) was detected. */ -#define OBJECT_INFO_DIE_IF_CORRUPT 32 - -int oid_object_info_extended(struct repository *r, - const struct object_id *, - struct object_info *, unsigned flags); - -/* - * Open the loose object at path, check its hash, and return the contents, - * use the "oi" argument to assert things about the object, or e.g. populate its - * type, and size. If the object is a blob, then "contents" may return NULL, - * to allow streaming of large blobs. - * - * Returns 0 on success, negative on error (details may be written to stderr). - */ -int read_loose_object(const char *path, - const struct object_id *expected_oid, - struct object_id *real_oid, - void **contents, - struct object_info *oi); - -/* Retry packed storage after checking packed and loose storage */ -#define HAS_OBJECT_RECHECK_PACKED 1 - -/* - * Returns 1 if the object exists. This function will not lazily fetch objects - * in a partial clone. - */ -int has_object(struct repository *r, const struct object_id *oid, - unsigned flags); - -/* - * These macros and functions are deprecated. If checking existence for an - * object that is likely to be missing and/or whose absence is relatively - * inconsequential (or is consequential but the caller is prepared to handle - * it), use has_object(), which has better defaults (no lazy fetch in a partial - * clone and no rechecking of packed storage). In the unlikely event that a - * caller needs to assert existence of an object that it fully expects to - * exist, and wants to trigger a lazy fetch in a partial clone, use - * oid_object_info_extended() with a NULL struct object_info. - * - * These functions can be removed once all callers have migrated to - * has_object() and/or oid_object_info_extended(). - */ -int repo_has_object_file(struct repository *r, const struct object_id *oid); -int repo_has_object_file_with_flags(struct repository *r, - const struct object_id *oid, int flags); - -/* - * Return true iff an alternate object database has a loose object - * with the specified name. This function does not respect replace - * references. - */ -int has_loose_object_nonlocal(const struct object_id *); - -int has_loose_object(const struct object_id *); - -/** - * format_object_header() is a thin wrapper around s xsnprintf() that - * writes the initial "<type> <obj-len>" part of the loose object - * header. It returns the size that snprintf() returns + 1. - */ -int format_object_header(char *str, size_t size, enum object_type type, - size_t objsize); - -void assert_oid_type(const struct object_id *oid, enum object_type expect); - -/* - * Enabling the object read lock allows multiple threads to safely call the - * following functions in parallel: repo_read_object_file(), - * read_object_with_reference(), oid_object_info() and oid_object_info_extended(). - * - * obj_read_lock() and obj_read_unlock() may also be used to protect other - * section which cannot execute in parallel with object reading. Since the used - * lock is a recursive mutex, these sections can even contain calls to object - * reading functions. However, beware that in these cases zlib inflation won't - * be performed in parallel, losing performance. - * - * TODO: oid_object_info_extended()'s call stack has a recursive behavior. If - * any of its callees end up calling it, this recursive call won't benefit from - * parallel inflation. - */ -void enable_obj_read_lock(void); -void disable_obj_read_lock(void); - -extern int obj_read_use_lock; -extern pthread_mutex_t obj_read_mutex; - -static inline void obj_read_lock(void) -{ - if(obj_read_use_lock) - pthread_mutex_lock(&obj_read_mutex); -} - -static inline void obj_read_unlock(void) -{ - if(obj_read_use_lock) - pthread_mutex_unlock(&obj_read_mutex); -} - -/* - * Iterate over the files in the loose-object parts of the object - * directory "path", triggering the following callbacks: - * - * - loose_object is called for each loose object we find. - * - * - loose_cruft is called for any files that do not appear to be - * loose objects. Note that we only look in the loose object - * directories "objects/[0-9a-f]{2}/", so we will not report - * "objects/foobar" as cruft. - * - * - loose_subdir is called for each top-level hashed subdirectory - * of the object directory (e.g., "$OBJDIR/f0"). It is called - * after the objects in the directory are processed. - * - * Any callback that is NULL will be ignored. Callbacks returning non-zero - * will end the iteration. - * - * In the "buf" variant, "path" is a strbuf which will also be used as a - * scratch buffer, but restored to its original contents before - * the function returns. - */ -typedef int each_loose_object_fn(const struct object_id *oid, - const char *path, - void *data); -typedef int each_loose_cruft_fn(const char *basename, - const char *path, - void *data); -typedef int each_loose_subdir_fn(unsigned int nr, - const char *path, - void *data); -int for_each_file_in_obj_subdir(unsigned int subdir_nr, - struct strbuf *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); -int for_each_loose_file_in_objdir(const char *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); -int for_each_loose_file_in_objdir_buf(struct strbuf *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); - -/* Flags for for_each_*_object() below. */ -enum for_each_object_flags { - /* Iterate only over local objects, not alternates. */ - FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0), - - /* Only iterate over packs obtained from the promisor remote. */ - FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1), - - /* - * Visit objects within a pack in packfile order rather than .idx order - */ - FOR_EACH_OBJECT_PACK_ORDER = (1<<2), - - /* Only iterate over packs that are not marked as kept in-core. */ - FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3), - - /* Only iterate over packs that do not have .keep files. */ - FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4), -}; - -/* - * Iterate over all accessible loose objects without respect to - * reachability. By default, this includes both local and alternate objects. - * The order in which objects are visited is unspecified. - * - * Any flags specific to packs are ignored. - */ -int for_each_loose_object(each_loose_object_fn, void *, - enum for_each_object_flags flags); - -/* - * Iterate over all accessible packed objects without respect to reachability. - * By default, this includes both local and alternate packs. - * - * Note that some objects may appear twice if they are found in multiple packs. - * Each pack is visited in an unspecified order. By default, objects within a - * pack are visited in pack-idx order (i.e., sorted by oid). - */ -typedef int each_packed_object_fn(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, - void *data); -int for_each_object_in_pack(struct packed_git *p, - each_packed_object_fn, void *data, - enum for_each_object_flags flags); -int for_each_packed_object(struct repository *repo, each_packed_object_fn cb, - void *data, enum for_each_object_flags flags); - -#endif /* OBJECT_STORE_LL_H */ diff --git a/object-store.c b/object-store.c new file mode 100644 index 0000000000..6ab50d25d3 --- /dev/null +++ b/object-store.c @@ -0,0 +1,1050 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "git-compat-util.h" +#include "abspath.h" +#include "commit-graph.h" +#include "config.h" +#include "dir.h" +#include "environment.h" +#include "gettext.h" +#include "hex.h" +#include "khash.h" +#include "lockfile.h" +#include "loose.h" +#include "object-file-convert.h" +#include "object-file.h" +#include "object-store.h" +#include "packfile.h" +#include "path.h" +#include "promisor-remote.h" +#include "quote.h" +#include "replace-object.h" +#include "run-command.h" +#include "setup.h" +#include "strbuf.h" +#include "strvec.h" +#include "submodule.h" +#include "write-or-die.h" + +KHASH_INIT(odb_path_map, const char * /* key: odb_path */, + struct object_directory *, 1, fspathhash, fspatheq) + +/* + * This is meant to hold a *small* number of objects that you would + * want repo_read_object_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +struct cached_object_entry { + struct object_id oid; + struct cached_object { + enum object_type type; + const void *buf; + unsigned long size; + } value; +}; + +static const struct cached_object *find_cached_object(struct raw_object_store *object_store, + const struct object_id *oid) +{ + static const struct cached_object empty_tree = { + .type = OBJ_TREE, + .buf = "", + }; + const struct cached_object_entry *co = object_store->cached_objects; + + for (size_t i = 0; i < object_store->cached_object_nr; i++, co++) + if (oideq(&co->oid, oid)) + return &co->value; + + if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree)) + return &empty_tree; + + return NULL; +} + +int odb_mkstemp(struct strbuf *temp_filename, const char *pattern) +{ + int fd; + /* + * we let the umask do its job, don't try to be more + * restrictive except to remove write permission. + */ + int mode = 0444; + repo_git_path_replace(the_repository, temp_filename, "objects/%s", pattern); + fd = git_mkstemp_mode(temp_filename->buf, mode); + if (0 <= fd) + return fd; + + /* slow path */ + /* some mkstemp implementations erase temp_filename on failure */ + repo_git_path_replace(the_repository, temp_filename, "objects/%s", pattern); + safe_create_leading_directories(the_repository, temp_filename->buf); + return xmkstemp_mode(temp_filename->buf, mode); +} + +int odb_pack_keep(const char *name) +{ + int fd; + + fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600); + if (0 <= fd) + return fd; + + /* slow path */ + safe_create_leading_directories_const(the_repository, name); + return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); +} + +const char *loose_object_path(struct repository *r, struct strbuf *buf, + const struct object_id *oid) +{ + return odb_loose_path(r->objects->odb, buf, oid); +} + +/* + * Return non-zero iff the path is usable as an alternate object database. + */ +static int alt_odb_usable(struct raw_object_store *o, + struct strbuf *path, + const char *normalized_objdir, khiter_t *pos) +{ + int r; + + /* Detect cases where alternate disappeared */ + if (!is_directory(path->buf)) { + error(_("object directory %s does not exist; " + "check .git/objects/info/alternates"), + path->buf); + return 0; + } + + /* + * Prevent the common mistake of listing the same + * thing twice, or object directory itself. + */ + if (!o->odb_by_path) { + khiter_t p; + + o->odb_by_path = kh_init_odb_path_map(); + assert(!o->odb->next); + p = kh_put_odb_path_map(o->odb_by_path, o->odb->path, &r); + assert(r == 1); /* never used */ + kh_value(o->odb_by_path, p) = o->odb; + } + if (fspatheq(path->buf, normalized_objdir)) + return 0; + *pos = kh_put_odb_path_map(o->odb_by_path, path->buf, &r); + /* r: 0 = exists, 1 = never used, 2 = deleted */ + return r == 0 ? 0 : 1; +} + +/* + * Prepare alternate object database registry. + * + * The variable alt_odb_list points at the list of struct + * object_directory. The elements on this list come from + * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT + * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, + * whose contents is similar to that environment variable but can be + * LF separated. Its base points at a statically allocated buffer that + * contains "/the/directory/corresponding/to/.git/objects/...", while + * its name points just after the slash at the end of ".git/objects/" + * in the example above, and has enough space to hold all hex characters + * of the object ID, an extra slash for the first level indirection, and + * the terminating NUL. + */ +static void read_info_alternates(struct repository *r, + const char *relative_base, + int depth); +static int link_alt_odb_entry(struct repository *r, const struct strbuf *entry, + const char *relative_base, int depth, const char *normalized_objdir) +{ + struct object_directory *ent; + struct strbuf pathbuf = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + khiter_t pos; + int ret = -1; + + if (!is_absolute_path(entry->buf) && relative_base) { + strbuf_realpath(&pathbuf, relative_base, 1); + strbuf_addch(&pathbuf, '/'); + } + strbuf_addbuf(&pathbuf, entry); + + if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { + error(_("unable to normalize alternate object path: %s"), + pathbuf.buf); + goto error; + } + strbuf_swap(&pathbuf, &tmp); + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') + strbuf_setlen(&pathbuf, pathbuf.len - 1); + + if (!alt_odb_usable(r->objects, &pathbuf, normalized_objdir, &pos)) + goto error; + + CALLOC_ARRAY(ent, 1); + /* pathbuf.buf is already in r->objects->odb_by_path */ + ent->path = strbuf_detach(&pathbuf, NULL); + + /* add the alternate entry */ + *r->objects->odb_tail = ent; + r->objects->odb_tail = &(ent->next); + ent->next = NULL; + assert(r->objects->odb_by_path); + kh_value(r->objects->odb_by_path, pos) = ent; + + /* recursively add alternates */ + read_info_alternates(r, ent->path, depth + 1); + ret = 0; + error: + strbuf_release(&tmp); + strbuf_release(&pathbuf); + return ret; +} + +static const char *parse_alt_odb_entry(const char *string, + int sep, + struct strbuf *out) +{ + const char *end; + + strbuf_reset(out); + + if (*string == '#') { + /* comment; consume up to next separator */ + end = strchrnul(string, sep); + } else if (*string == '"' && !unquote_c_style(out, string, &end)) { + /* + * quoted path; unquote_c_style has copied the + * data for us and set "end". Broken quoting (e.g., + * an entry that doesn't end with a quote) falls + * back to the unquoted case below. + */ + } else { + /* normal, unquoted path */ + end = strchrnul(string, sep); + strbuf_add(out, string, end - string); + } + + if (*end) + end++; + return end; +} + +static void link_alt_odb_entries(struct repository *r, const char *alt, + int sep, const char *relative_base, int depth) +{ + struct strbuf objdirbuf = STRBUF_INIT; + struct strbuf entry = STRBUF_INIT; + + if (!alt || !*alt) + return; + + if (depth > 5) { + error(_("%s: ignoring alternate object stores, nesting too deep"), + relative_base); + return; + } + + strbuf_realpath(&objdirbuf, r->objects->odb->path, 1); + + while (*alt) { + alt = parse_alt_odb_entry(alt, sep, &entry); + if (!entry.len) + continue; + link_alt_odb_entry(r, &entry, + relative_base, depth, objdirbuf.buf); + } + strbuf_release(&entry); + strbuf_release(&objdirbuf); +} + +static void read_info_alternates(struct repository *r, + const char *relative_base, + int depth) +{ + char *path; + struct strbuf buf = STRBUF_INIT; + + path = xstrfmt("%s/info/alternates", relative_base); + if (strbuf_read_file(&buf, path, 1024) < 0) { + warn_on_fopen_errors(path); + free(path); + return; + } + + link_alt_odb_entries(r, buf.buf, '\n', relative_base, depth); + strbuf_release(&buf); + free(path); +} + +void add_to_alternates_file(const char *reference) +{ + struct lock_file lock = LOCK_INIT; + char *alts = repo_git_path(the_repository, "objects/info/alternates"); + FILE *in, *out; + int found = 0; + + hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR); + out = fdopen_lock_file(&lock, "w"); + if (!out) + die_errno(_("unable to fdopen alternates lockfile")); + + in = fopen(alts, "r"); + if (in) { + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, in) != EOF) { + if (!strcmp(reference, line.buf)) { + found = 1; + break; + } + fprintf_or_die(out, "%s\n", line.buf); + } + + strbuf_release(&line); + fclose(in); + } + else if (errno != ENOENT) + die_errno(_("unable to read alternates file")); + + if (found) { + rollback_lock_file(&lock); + } else { + fprintf_or_die(out, "%s\n", reference); + if (commit_lock_file(&lock)) + die_errno(_("unable to move new alternates file into place")); + if (the_repository->objects->loaded_alternates) + link_alt_odb_entries(the_repository, reference, + '\n', NULL, 0); + } + free(alts); +} + +void add_to_alternates_memory(const char *reference) +{ + /* + * Make sure alternates are initialized, or else our entry may be + * overwritten when they are. + */ + prepare_alt_odb(the_repository); + + link_alt_odb_entries(the_repository, reference, + '\n', NULL, 0); +} + +struct object_directory *set_temporary_primary_odb(const char *dir, int will_destroy) +{ + struct object_directory *new_odb; + + /* + * Make sure alternates are initialized, or else our entry may be + * overwritten when they are. + */ + prepare_alt_odb(the_repository); + + /* + * Make a new primary odb and link the old primary ODB in as an + * alternate + */ + new_odb = xcalloc(1, sizeof(*new_odb)); + new_odb->path = xstrdup(dir); + + /* + * Disable ref updates while a temporary odb is active, since + * the objects in the database may roll back. + */ + new_odb->disable_ref_updates = 1; + new_odb->will_destroy = will_destroy; + new_odb->next = the_repository->objects->odb; + the_repository->objects->odb = new_odb; + return new_odb->next; +} + +static void free_object_directory(struct object_directory *odb) +{ + free(odb->path); + odb_clear_loose_cache(odb); + loose_object_map_clear(&odb->loose_map); + free(odb); +} + +void restore_primary_odb(struct object_directory *restore_odb, const char *old_path) +{ + struct object_directory *cur_odb = the_repository->objects->odb; + + if (strcmp(old_path, cur_odb->path)) + BUG("expected %s as primary object store; found %s", + old_path, cur_odb->path); + + if (cur_odb->next != restore_odb) + BUG("we expect the old primary object store to be the first alternate"); + + the_repository->objects->odb = restore_odb; + free_object_directory(cur_odb); +} + +/* + * Compute the exact path an alternate is at and returns it. In case of + * error NULL is returned and the human readable error is added to `err` + * `path` may be relative and should point to $GIT_DIR. + * `err` must not be null. + */ +char *compute_alternate_path(const char *path, struct strbuf *err) +{ + char *ref_git = NULL; + const char *repo; + int seen_error = 0; + + ref_git = real_pathdup(path, 0); + if (!ref_git) { + seen_error = 1; + strbuf_addf(err, _("path '%s' does not exist"), path); + goto out; + } + + repo = read_gitfile(ref_git); + if (!repo) + repo = read_gitfile(mkpath("%s/.git", ref_git)); + if (repo) { + free(ref_git); + ref_git = xstrdup(repo); + } + + if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = mkpathdup("%s/.git", ref_git); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) { + struct strbuf sb = STRBUF_INIT; + seen_error = 1; + if (get_common_dir(&sb, ref_git)) { + strbuf_addf(err, + _("reference repository '%s' as a linked " + "checkout is not supported yet."), + path); + goto out; + } + + strbuf_addf(err, _("reference repository '%s' is not a " + "local repository."), path); + goto out; + } + + if (!access(mkpath("%s/shallow", ref_git), F_OK)) { + strbuf_addf(err, _("reference repository '%s' is shallow"), + path); + seen_error = 1; + goto out; + } + + if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) { + strbuf_addf(err, + _("reference repository '%s' is grafted"), + path); + seen_error = 1; + goto out; + } + +out: + if (seen_error) { + FREE_AND_NULL(ref_git); + } + + return ref_git; +} + +struct object_directory *find_odb(struct repository *r, const char *obj_dir) +{ + struct object_directory *odb; + char *obj_dir_real = real_pathdup(obj_dir, 1); + struct strbuf odb_path_real = STRBUF_INIT; + + prepare_alt_odb(r); + for (odb = r->objects->odb; odb; odb = odb->next) { + strbuf_realpath(&odb_path_real, odb->path, 1); + if (!strcmp(obj_dir_real, odb_path_real.buf)) + break; + } + + free(obj_dir_real); + strbuf_release(&odb_path_real); + + if (!odb) + die(_("could not find object directory matching %s"), obj_dir); + return odb; +} + +static void fill_alternate_refs_command(struct child_process *cmd, + const char *repo_path) +{ + const char *value; + + if (!git_config_get_value("core.alternateRefsCommand", &value)) { + cmd->use_shell = 1; + + strvec_push(&cmd->args, value); + strvec_push(&cmd->args, repo_path); + } else { + cmd->git_cmd = 1; + + strvec_pushf(&cmd->args, "--git-dir=%s", repo_path); + strvec_push(&cmd->args, "for-each-ref"); + strvec_push(&cmd->args, "--format=%(objectname)"); + + if (!git_config_get_value("core.alternateRefsPrefixes", &value)) { + strvec_push(&cmd->args, "--"); + strvec_split(&cmd->args, value); + } + } + + strvec_pushv(&cmd->env, (const char **)local_repo_env); + cmd->out = -1; +} + +static void read_alternate_refs(const char *path, + alternate_ref_fn *cb, + void *data) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf line = STRBUF_INIT; + FILE *fh; + + fill_alternate_refs_command(&cmd, path); + + if (start_command(&cmd)) + return; + + fh = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&line, fh) != EOF) { + struct object_id oid; + const char *p; + + if (parse_oid_hex(line.buf, &oid, &p) || *p) { + warning(_("invalid line while parsing alternate refs: %s"), + line.buf); + break; + } + + cb(&oid, data); + } + + fclose(fh); + finish_command(&cmd); + strbuf_release(&line); +} + +struct alternate_refs_data { + alternate_ref_fn *fn; + void *data; +}; + +static int refs_from_alternate_cb(struct object_directory *e, + void *data) +{ + struct strbuf path = STRBUF_INIT; + size_t base_len; + struct alternate_refs_data *cb = data; + + if (!strbuf_realpath(&path, e->path, 0)) + goto out; + if (!strbuf_strip_suffix(&path, "/objects")) + goto out; + base_len = path.len; + + /* Is this a git repository with refs? */ + strbuf_addstr(&path, "/refs"); + if (!is_directory(path.buf)) + goto out; + strbuf_setlen(&path, base_len); + + read_alternate_refs(path.buf, cb->fn, cb->data); + +out: + strbuf_release(&path); + return 0; +} + +void for_each_alternate_ref(alternate_ref_fn fn, void *data) +{ + struct alternate_refs_data cb; + cb.fn = fn; + cb.data = data; + foreach_alt_odb(refs_from_alternate_cb, &cb); +} + +int foreach_alt_odb(alt_odb_fn fn, void *cb) +{ + struct object_directory *ent; + int r = 0; + + prepare_alt_odb(the_repository); + for (ent = the_repository->objects->odb->next; ent; ent = ent->next) { + r = fn(ent, cb); + if (r) + break; + } + return r; +} + +void prepare_alt_odb(struct repository *r) +{ + if (r->objects->loaded_alternates) + return; + + link_alt_odb_entries(r, r->objects->alternate_db, PATH_SEP, NULL, 0); + + read_info_alternates(r, r->objects->odb->path, 0); + r->objects->loaded_alternates = 1; +} + +int has_alt_odb(struct repository *r) +{ + prepare_alt_odb(r); + return !!r->objects->odb->next; +} + +int obj_read_use_lock = 0; +pthread_mutex_t obj_read_mutex; + +void enable_obj_read_lock(void) +{ + if (obj_read_use_lock) + return; + + obj_read_use_lock = 1; + init_recursive_mutex(&obj_read_mutex); +} + +void disable_obj_read_lock(void) +{ + if (!obj_read_use_lock) + return; + + obj_read_use_lock = 0; + pthread_mutex_destroy(&obj_read_mutex); +} + +int fetch_if_missing = 1; + +static int do_oid_object_info_extended(struct repository *r, + const struct object_id *oid, + struct object_info *oi, unsigned flags) +{ + static struct object_info blank_oi = OBJECT_INFO_INIT; + const struct cached_object *co; + struct pack_entry e; + int rtype; + const struct object_id *real = oid; + int already_retried = 0; + + + if (flags & OBJECT_INFO_LOOKUP_REPLACE) + real = lookup_replace_object(r, oid); + + if (is_null_oid(real)) + return -1; + + if (!oi) + oi = &blank_oi; + + co = find_cached_object(r->objects, real); + if (co) { + if (oi->typep) + *(oi->typep) = co->type; + if (oi->sizep) + *(oi->sizep) = co->size; + if (oi->disk_sizep) + *(oi->disk_sizep) = 0; + if (oi->delta_base_oid) + oidclr(oi->delta_base_oid, the_repository->hash_algo); + if (oi->type_name) + strbuf_addstr(oi->type_name, type_name(co->type)); + if (oi->contentp) + *oi->contentp = xmemdupz(co->buf, co->size); + oi->whence = OI_CACHED; + return 0; + } + + while (1) { + if (find_pack_entry(r, real, &e)) + break; + + /* Most likely it's a loose object. */ + if (!loose_object_info(r, real, oi, flags)) + return 0; + + /* Not a loose object; someone else may have just packed it. */ + if (!(flags & OBJECT_INFO_QUICK)) { + reprepare_packed_git(r); + if (find_pack_entry(r, real, &e)) + break; + } + + /* + * If r is the_repository, this might be an attempt at + * accessing a submodule object as if it were in the_repository + * (having called add_submodule_odb() on that submodule's ODB). + * If any such ODBs exist, register them and try again. + */ + if (r == the_repository && + register_all_submodule_odb_as_alternates()) + /* We added some alternates; retry */ + continue; + + /* Check if it is a missing object */ + if (fetch_if_missing && repo_has_promisor_remote(r) && + !already_retried && + !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) { + promisor_remote_get_direct(r, real, 1); + already_retried = 1; + continue; + } + + if (flags & OBJECT_INFO_DIE_IF_CORRUPT) { + const struct packed_git *p; + if ((flags & OBJECT_INFO_LOOKUP_REPLACE) && !oideq(real, oid)) + die(_("replacement %s not found for %s"), + oid_to_hex(real), oid_to_hex(oid)); + if ((p = has_packed_and_bad(r, real))) + die(_("packed object %s (stored in %s) is corrupt"), + oid_to_hex(real), p->pack_name); + } + return -1; + } + + if (oi == &blank_oi) + /* + * We know that the caller doesn't actually need the + * information below, so return early. + */ + return 0; + rtype = packed_object_info(r, e.p, e.offset, oi); + if (rtype < 0) { + mark_bad_packed_object(e.p, real); + return do_oid_object_info_extended(r, real, oi, 0); + } else if (oi->whence == OI_PACKED) { + oi->u.packed.offset = e.offset; + oi->u.packed.pack = e.p; + oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA || + rtype == OBJ_OFS_DELTA); + } + + return 0; +} + +static int oid_object_info_convert(struct repository *r, + const struct object_id *input_oid, + struct object_info *input_oi, unsigned flags) +{ + const struct git_hash_algo *input_algo = &hash_algos[input_oid->algo]; + int do_die = flags & OBJECT_INFO_DIE_IF_CORRUPT; + struct strbuf type_name = STRBUF_INIT; + struct object_id oid, delta_base_oid; + struct object_info new_oi, *oi; + unsigned long size; + void *content; + int ret; + + if (repo_oid_to_algop(r, input_oid, the_hash_algo, &oid)) { + if (do_die) + die(_("missing mapping of %s to %s"), + oid_to_hex(input_oid), the_hash_algo->name); + return -1; + } + + /* Is new_oi needed? */ + oi = input_oi; + if (input_oi && (input_oi->delta_base_oid || input_oi->sizep || + input_oi->contentp)) { + new_oi = *input_oi; + /* Does delta_base_oid need to be converted? */ + if (input_oi->delta_base_oid) + new_oi.delta_base_oid = &delta_base_oid; + /* Will the attributes differ when converted? */ + if (input_oi->sizep || input_oi->contentp) { + new_oi.contentp = &content; + new_oi.sizep = &size; + new_oi.type_name = &type_name; + } + oi = &new_oi; + } + + ret = oid_object_info_extended(r, &oid, oi, flags); + if (ret) + return -1; + if (oi == input_oi) + return ret; + + if (new_oi.contentp) { + struct strbuf outbuf = STRBUF_INIT; + enum object_type type; + + type = type_from_string_gently(type_name.buf, type_name.len, + !do_die); + if (type == -1) + return -1; + if (type != OBJ_BLOB) { + ret = convert_object_file(the_repository, &outbuf, + the_hash_algo, input_algo, + content, size, type, !do_die); + free(content); + if (ret == -1) + return -1; + size = outbuf.len; + content = strbuf_detach(&outbuf, NULL); + } + if (input_oi->sizep) + *input_oi->sizep = size; + if (input_oi->contentp) + *input_oi->contentp = content; + else + free(content); + if (input_oi->type_name) + *input_oi->type_name = type_name; + else + strbuf_release(&type_name); + } + if (new_oi.delta_base_oid == &delta_base_oid) { + if (repo_oid_to_algop(r, &delta_base_oid, input_algo, + input_oi->delta_base_oid)) { + if (do_die) + die(_("missing mapping of %s to %s"), + oid_to_hex(&delta_base_oid), + input_algo->name); + return -1; + } + } + input_oi->whence = new_oi.whence; + input_oi->u = new_oi.u; + return ret; +} + +int oid_object_info_extended(struct repository *r, const struct object_id *oid, + struct object_info *oi, unsigned flags) +{ + int ret; + + if (oid->algo && (hash_algo_by_ptr(r->hash_algo) != oid->algo)) + return oid_object_info_convert(r, oid, oi, flags); + + obj_read_lock(); + ret = do_oid_object_info_extended(r, oid, oi, flags); + obj_read_unlock(); + return ret; +} + + +/* returns enum object_type or negative */ +int oid_object_info(struct repository *r, + const struct object_id *oid, + unsigned long *sizep) +{ + enum object_type type; + struct object_info oi = OBJECT_INFO_INIT; + + oi.typep = &type; + oi.sizep = sizep; + if (oid_object_info_extended(r, oid, &oi, + OBJECT_INFO_LOOKUP_REPLACE) < 0) + return -1; + return type; +} + +int pretend_object_file(struct repository *repo, + void *buf, unsigned long len, enum object_type type, + struct object_id *oid) +{ + struct cached_object_entry *co; + char *co_buf; + + hash_object_file(repo->hash_algo, buf, len, type, oid); + if (repo_has_object_file_with_flags(repo, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) || + find_cached_object(repo->objects, oid)) + return 0; + + ALLOC_GROW(repo->objects->cached_objects, + repo->objects->cached_object_nr + 1, repo->objects->cached_object_alloc); + co = &repo->objects->cached_objects[repo->objects->cached_object_nr++]; + co->value.size = len; + co->value.type = type; + co_buf = xmalloc(len); + memcpy(co_buf, buf, len); + co->value.buf = co_buf; + oidcpy(&co->oid, oid); + return 0; +} + +/* + * This function dies on corrupt objects; the callers who want to + * deal with them should arrange to call oid_object_info_extended() and give + * error messages themselves. + */ +void *repo_read_object_file(struct repository *r, + const struct object_id *oid, + enum object_type *type, + unsigned long *size) +{ + struct object_info oi = OBJECT_INFO_INIT; + unsigned flags = OBJECT_INFO_DIE_IF_CORRUPT | OBJECT_INFO_LOOKUP_REPLACE; + void *data; + + oi.typep = type; + oi.sizep = size; + oi.contentp = &data; + if (oid_object_info_extended(r, oid, &oi, flags)) + return NULL; + + return data; +} + +void *read_object_with_reference(struct repository *r, + const struct object_id *oid, + enum object_type required_type, + unsigned long *size, + struct object_id *actual_oid_return) +{ + enum object_type type; + void *buffer; + unsigned long isize; + struct object_id actual_oid; + + oidcpy(&actual_oid, oid); + while (1) { + int ref_length = -1; + const char *ref_type = NULL; + + buffer = repo_read_object_file(r, &actual_oid, &type, &isize); + if (!buffer) + return NULL; + if (type == required_type) { + *size = isize; + if (actual_oid_return) + oidcpy(actual_oid_return, &actual_oid); + return buffer; + } + /* Handle references */ + else if (type == OBJ_COMMIT) + ref_type = "tree "; + else if (type == OBJ_TAG) + ref_type = "object "; + else { + free(buffer); + return NULL; + } + ref_length = strlen(ref_type); + + if (ref_length + the_hash_algo->hexsz > isize || + memcmp(buffer, ref_type, ref_length) || + get_oid_hex((char *) buffer + ref_length, &actual_oid)) { + free(buffer); + return NULL; + } + free(buffer); + /* Now we have the ID of the referred-to object in + * actual_oid. Check again. */ + } +} + +int has_object(struct repository *r, const struct object_id *oid, + unsigned flags) +{ + int quick = !(flags & HAS_OBJECT_RECHECK_PACKED); + unsigned object_info_flags = OBJECT_INFO_SKIP_FETCH_OBJECT | + (quick ? OBJECT_INFO_QUICK : 0); + + if (!startup_info->have_repository) + return 0; + return oid_object_info_extended(r, oid, NULL, object_info_flags) >= 0; +} + +int repo_has_object_file_with_flags(struct repository *r, + const struct object_id *oid, int flags) +{ + if (!startup_info->have_repository) + return 0; + return oid_object_info_extended(r, oid, NULL, flags) >= 0; +} + +int repo_has_object_file(struct repository *r, + const struct object_id *oid) +{ + return repo_has_object_file_with_flags(r, oid, 0); +} + +void assert_oid_type(const struct object_id *oid, enum object_type expect) +{ + enum object_type type = oid_object_info(the_repository, oid, NULL); + if (type < 0) + die(_("%s is not a valid object"), oid_to_hex(oid)); + if (type != expect) + die(_("%s is not a valid '%s' object"), oid_to_hex(oid), + type_name(expect)); +} + +struct raw_object_store *raw_object_store_new(void) +{ + struct raw_object_store *o = xmalloc(sizeof(*o)); + + memset(o, 0, sizeof(*o)); + INIT_LIST_HEAD(&o->packed_git_mru); + hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0); + pthread_mutex_init(&o->replace_mutex, NULL); + return o; +} + +static void free_object_directories(struct raw_object_store *o) +{ + while (o->odb) { + struct object_directory *next; + + next = o->odb->next; + free_object_directory(o->odb); + o->odb = next; + } + kh_destroy_odb_path_map(o->odb_by_path); + o->odb_by_path = NULL; +} + +void raw_object_store_clear(struct raw_object_store *o) +{ + FREE_AND_NULL(o->alternate_db); + + oidmap_free(o->replace_map, 1); + FREE_AND_NULL(o->replace_map); + pthread_mutex_destroy(&o->replace_mutex); + + free_commit_graph(o->commit_graph); + o->commit_graph = NULL; + o->commit_graph_attempted = 0; + + free_object_directories(o); + o->odb_tail = NULL; + o->loaded_alternates = 0; + + for (size_t i = 0; i < o->cached_object_nr; i++) + free((char *) o->cached_objects[i].value.buf); + FREE_AND_NULL(o->cached_objects); + + INIT_LIST_HEAD(&o->packed_git_mru); + close_object_store(o); + + /* + * `close_object_store()` only closes the packfiles, but doesn't free + * them. We thus have to do this manually. + */ + for (struct packed_git *p = o->packed_git, *next; p; p = next) { + next = p->next; + free(p); + } + o->packed_git = NULL; + + hashmap_clear(&o->pack_map); +} diff --git a/object-store.h b/object-store.h index 1b3e3d7d01..46961dc954 100644 --- a/object-store.h +++ b/object-store.h @@ -1,11 +1,517 @@ #ifndef OBJECT_STORE_H #define OBJECT_STORE_H -#include "khash.h" -#include "dir.h" -#include "object-store-ll.h" +#include "hashmap.h" +#include "object.h" +#include "list.h" +#include "oidset.h" +#include "thread-utils.h" -KHASH_INIT(odb_path_map, const char * /* key: odb_path */, - struct object_directory *, 1, fspathhash, fspatheq) +struct oidmap; +struct oidtree; +struct strbuf; +struct repository; + +struct object_directory { + struct object_directory *next; + + /* + * Used to store the results of readdir(3) calls when we are OK + * sacrificing accuracy due to races for speed. That includes + * object existence with OBJECT_INFO_QUICK, as well as + * our search for unique abbreviated hashes. Don't use it for tasks + * requiring greater accuracy! + * + * Be sure to call odb_load_loose_cache() before using. + */ + uint32_t loose_objects_subdir_seen[8]; /* 256 bits */ + struct oidtree *loose_objects_cache; + + /* Map between object IDs for loose objects. */ + struct loose_object_map *loose_map; + + /* + * This is a temporary object store created by the tmp_objdir + * facility. Disable ref updates since the objects in the store + * might be discarded on rollback. + */ + int disable_ref_updates; + + /* + * This object store is ephemeral, so there is no need to fsync. + */ + int will_destroy; + + /* + * Path to the alternative object store. If this is a relative path, + * it is relative to the current working directory. + */ + char *path; +}; + +void prepare_alt_odb(struct repository *r); +int has_alt_odb(struct repository *r); +char *compute_alternate_path(const char *path, struct strbuf *err); +struct object_directory *find_odb(struct repository *r, const char *obj_dir); +typedef int alt_odb_fn(struct object_directory *, void *); +int foreach_alt_odb(alt_odb_fn, void*); +typedef void alternate_ref_fn(const struct object_id *oid, void *); +void for_each_alternate_ref(alternate_ref_fn, void *); + +/* + * Add the directory to the on-disk alternates file; the new entry will also + * take effect in the current process. + */ +void add_to_alternates_file(const char *dir); + +/* + * Add the directory to the in-memory list of alternates (along with any + * recursive alternates it points to), but do not modify the on-disk alternates + * file. + */ +void add_to_alternates_memory(const char *dir); + +/* + * Replace the current writable object directory with the specified temporary + * object directory; returns the former primary object directory. + */ +struct object_directory *set_temporary_primary_odb(const char *dir, int will_destroy); + +/* + * Restore a previous ODB replaced by set_temporary_main_odb. + */ +void restore_primary_odb(struct object_directory *restore_odb, const char *old_path); + +/* + * Populate and return the loose object cache array corresponding to the + * given object ID. + */ +struct oidtree *odb_loose_cache(struct object_directory *odb, + const struct object_id *oid); + +/* Empty the loose object cache for the specified object directory. */ +void odb_clear_loose_cache(struct object_directory *odb); + +struct packed_git { + struct hashmap_entry packmap_ent; + struct packed_git *next; + struct list_head mru; + struct pack_window *windows; + off_t pack_size; + const void *index_data; + size_t index_size; + uint32_t num_objects; + size_t crc_offset; + struct oidset bad_objects; + int index_version; + time_t mtime; + int pack_fd; + int index; /* for builtin/pack-objects.c */ + unsigned pack_local:1, + pack_keep:1, + pack_keep_in_core:1, + freshened:1, + do_not_close:1, + pack_promisor:1, + multi_pack_index:1, + is_cruft:1; + unsigned char hash[GIT_MAX_RAWSZ]; + struct revindex_entry *revindex; + const uint32_t *revindex_data; + const uint32_t *revindex_map; + size_t revindex_size; + /* + * mtimes_map points at the beginning of the memory mapped region of + * this pack's corresponding .mtimes file, and mtimes_size is the size + * of that .mtimes file + */ + const uint32_t *mtimes_map; + size_t mtimes_size; + + /* repo denotes the repository this packfile belongs to */ + struct repository *repo; + + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[FLEX_ARRAY]; /* more */ +}; + +struct multi_pack_index; + +static inline int pack_map_entry_cmp(const void *cmp_data UNUSED, + const struct hashmap_entry *entry, + const struct hashmap_entry *entry2, + const void *keydata) +{ + const char *key = keydata; + const struct packed_git *pg1, *pg2; + + pg1 = container_of(entry, const struct packed_git, packmap_ent); + pg2 = container_of(entry2, const struct packed_git, packmap_ent); + + return strcmp(pg1->pack_name, key ? key : pg2->pack_name); +} + +struct cached_object_entry; + +struct raw_object_store { + /* + * Set of all object directories; the main directory is first (and + * cannot be NULL after initialization). Subsequent directories are + * alternates. + */ + struct object_directory *odb; + struct object_directory **odb_tail; + struct kh_odb_path_map *odb_by_path; + + int loaded_alternates; + + /* + * A list of alternate object directories loaded from the environment; + * this should not generally need to be accessed directly, but will + * populate the "odb" list when prepare_alt_odb() is run. + */ + char *alternate_db; + + /* + * Objects that should be substituted by other objects + * (see git-replace(1)). + */ + struct oidmap *replace_map; + unsigned replace_map_initialized : 1; + pthread_mutex_t replace_mutex; /* protect object replace functions */ + + struct commit_graph *commit_graph; + unsigned commit_graph_attempted : 1; /* if loading has been attempted */ + + /* + * private data + * + * should only be accessed directly by packfile.c and midx.c + */ + struct multi_pack_index *multi_pack_index; + + /* + * private data + * + * should only be accessed directly by packfile.c + */ + + struct packed_git *packed_git; + /* A most-recently-used ordered version of the packed_git list. */ + struct list_head packed_git_mru; + + struct { + struct packed_git **packs; + unsigned flags; + } kept_pack_cache; + + /* + * This is meant to hold a *small* number of objects that you would + * want repo_read_object_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ + struct cached_object_entry *cached_objects; + size_t cached_object_nr, cached_object_alloc; + + /* + * A map of packfiles to packed_git structs for tracking which + * packs have been loaded already. + */ + struct hashmap pack_map; + + /* + * A fast, rough count of the number of objects in the repository. + * These two fields are not meant for direct access. Use + * repo_approximate_object_count() instead. + */ + unsigned long approximate_object_count; + unsigned approximate_object_count_valid : 1; + + /* + * Whether packed_git has already been populated with this repository's + * packs. + */ + unsigned packed_git_initialized : 1; +}; + +struct raw_object_store *raw_object_store_new(void); +void raw_object_store_clear(struct raw_object_store *o); + +/* + * Create a temporary file rooted in the object database directory, or + * die on failure. The filename is taken from "pattern", which should have the + * usual "XXXXXX" trailer, and the resulting filename is written into the + * "template" buffer. Returns the open descriptor. + */ +int odb_mkstemp(struct strbuf *temp_filename, const char *pattern); + +/* + * Create a pack .keep file named "name" (which should generally be the output + * of odb_pack_name). Returns a file descriptor opened for writing, or -1 on + * error. + */ +int odb_pack_keep(const char *name); + +/* + * Put in `buf` the name of the file in the local object database that + * would be used to store a loose object with the specified oid. + */ +const char *loose_object_path(struct repository *r, struct strbuf *buf, + const struct object_id *oid); + +void *map_loose_object(struct repository *r, const struct object_id *oid, + unsigned long *size); + +void *repo_read_object_file(struct repository *r, + const struct object_id *oid, + enum object_type *type, + unsigned long *size); + +/* Read and unpack an object file into memory, write memory to an object file */ +int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); + +void hash_object_file(const struct git_hash_algo *algo, const void *buf, + unsigned long len, enum object_type type, + struct object_id *oid); + +/* + * Add an object file to the in-memory object store, without writing it + * to disk. + * + * Callers are responsible for calling write_object_file to record the + * object in persistent storage before writing any other new objects + * that reference it. + */ +int pretend_object_file(struct repository *repo, + void *buf, unsigned long len, enum object_type type, + struct object_id *oid); + +struct object_info { + /* Request */ + enum object_type *typep; + unsigned long *sizep; + off_t *disk_sizep; + struct object_id *delta_base_oid; + struct strbuf *type_name; + void **contentp; + + /* Response */ + enum { + OI_CACHED, + OI_LOOSE, + OI_PACKED, + OI_DBCACHED + } whence; + union { + /* + * struct { + * ... Nothing to expose in this case + * } cached; + * struct { + * ... Nothing to expose in this case + * } loose; + */ + struct { + struct packed_git *pack; + off_t offset; + unsigned int is_delta; + } packed; + } u; +}; + +/* + * Initializer for a "struct object_info" that wants no items. You may + * also memset() the memory to all-zeroes. + */ +#define OBJECT_INFO_INIT { 0 } + +/* Invoke lookup_replace_object() on the given hash */ +#define OBJECT_INFO_LOOKUP_REPLACE 1 +/* Allow reading from a loose object file of unknown/bogus type */ +#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2 +/* Do not retry packed storage after checking packed and loose storage */ +#define OBJECT_INFO_QUICK 8 +/* + * Do not attempt to fetch the object if missing (even if fetch_is_missing is + * nonzero). + */ +#define OBJECT_INFO_SKIP_FETCH_OBJECT 16 +/* + * This is meant for bulk prefetching of missing blobs in a partial + * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK + */ +#define OBJECT_INFO_FOR_PREFETCH (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) + +/* Die if object corruption (not just an object being missing) was detected. */ +#define OBJECT_INFO_DIE_IF_CORRUPT 32 + +int oid_object_info_extended(struct repository *r, + const struct object_id *, + struct object_info *, unsigned flags); + +/* Retry packed storage after checking packed and loose storage */ +#define HAS_OBJECT_RECHECK_PACKED 1 + +/* + * Returns 1 if the object exists. This function will not lazily fetch objects + * in a partial clone. + */ +int has_object(struct repository *r, const struct object_id *oid, + unsigned flags); + +/* + * These macros and functions are deprecated. If checking existence for an + * object that is likely to be missing and/or whose absence is relatively + * inconsequential (or is consequential but the caller is prepared to handle + * it), use has_object(), which has better defaults (no lazy fetch in a partial + * clone and no rechecking of packed storage). In the unlikely event that a + * caller needs to assert existence of an object that it fully expects to + * exist, and wants to trigger a lazy fetch in a partial clone, use + * oid_object_info_extended() with a NULL struct object_info. + * + * These functions can be removed once all callers have migrated to + * has_object() and/or oid_object_info_extended(). + */ +int repo_has_object_file(struct repository *r, const struct object_id *oid); +int repo_has_object_file_with_flags(struct repository *r, + const struct object_id *oid, int flags); + +void assert_oid_type(const struct object_id *oid, enum object_type expect); + +/* + * Enabling the object read lock allows multiple threads to safely call the + * following functions in parallel: repo_read_object_file(), + * read_object_with_reference(), oid_object_info() and oid_object_info_extended(). + * + * obj_read_lock() and obj_read_unlock() may also be used to protect other + * section which cannot execute in parallel with object reading. Since the used + * lock is a recursive mutex, these sections can even contain calls to object + * reading functions. However, beware that in these cases zlib inflation won't + * be performed in parallel, losing performance. + * + * TODO: oid_object_info_extended()'s call stack has a recursive behavior. If + * any of its callees end up calling it, this recursive call won't benefit from + * parallel inflation. + */ +void enable_obj_read_lock(void); +void disable_obj_read_lock(void); + +extern int obj_read_use_lock; +extern pthread_mutex_t obj_read_mutex; + +static inline void obj_read_lock(void) +{ + if(obj_read_use_lock) + pthread_mutex_lock(&obj_read_mutex); +} + +static inline void obj_read_unlock(void) +{ + if(obj_read_use_lock) + pthread_mutex_unlock(&obj_read_mutex); +} + +/* + * Iterate over the files in the loose-object parts of the object + * directory "path", triggering the following callbacks: + * + * - loose_object is called for each loose object we find. + * + * - loose_cruft is called for any files that do not appear to be + * loose objects. Note that we only look in the loose object + * directories "objects/[0-9a-f]{2}/", so we will not report + * "objects/foobar" as cruft. + * + * - loose_subdir is called for each top-level hashed subdirectory + * of the object directory (e.g., "$OBJDIR/f0"). It is called + * after the objects in the directory are processed. + * + * Any callback that is NULL will be ignored. Callbacks returning non-zero + * will end the iteration. + * + * In the "buf" variant, "path" is a strbuf which will also be used as a + * scratch buffer, but restored to its original contents before + * the function returns. + */ +typedef int each_loose_object_fn(const struct object_id *oid, + const char *path, + void *data); +typedef int each_loose_cruft_fn(const char *basename, + const char *path, + void *data); +typedef int each_loose_subdir_fn(unsigned int nr, + const char *path, + void *data); +int for_each_file_in_obj_subdir(unsigned int subdir_nr, + struct strbuf *path, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data); +int for_each_loose_file_in_objdir(const char *path, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data); +int for_each_loose_file_in_objdir_buf(struct strbuf *path, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data); + +/* Flags for for_each_*_object() below. */ +enum for_each_object_flags { + /* Iterate only over local objects, not alternates. */ + FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0), + + /* Only iterate over packs obtained from the promisor remote. */ + FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1), + + /* + * Visit objects within a pack in packfile order rather than .idx order + */ + FOR_EACH_OBJECT_PACK_ORDER = (1<<2), + + /* Only iterate over packs that are not marked as kept in-core. */ + FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3), + + /* Only iterate over packs that do not have .keep files. */ + FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4), +}; + +/* + * Iterate over all accessible loose objects without respect to + * reachability. By default, this includes both local and alternate objects. + * The order in which objects are visited is unspecified. + * + * Any flags specific to packs are ignored. + */ +int for_each_loose_object(each_loose_object_fn, void *, + enum for_each_object_flags flags); + +/* + * Iterate over all accessible packed objects without respect to reachability. + * By default, this includes both local and alternate packs. + * + * Note that some objects may appear twice if they are found in multiple packs. + * Each pack is visited in an unspecified order. By default, objects within a + * pack are visited in pack-idx order (i.e., sorted by oid). + */ +typedef int each_packed_object_fn(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data); +int for_each_object_in_pack(struct packed_git *p, + each_packed_object_fn, void *data, + enum for_each_object_flags flags); +int for_each_packed_object(struct repository *repo, each_packed_object_fn cb, + void *data, enum for_each_object_flags flags); + +void *read_object_with_reference(struct repository *r, + const struct object_id *oid, + enum object_type required_type, + unsigned long *size, + struct object_id *oid_ret); #endif /* OBJECT_STORE_H */ @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -7,25 +6,23 @@ #include "object.h" #include "replace-object.h" #include "object-file.h" -#include "object-store.h" #include "blob.h" #include "statinfo.h" #include "tree.h" #include "commit.h" #include "tag.h" #include "alloc.h" -#include "packfile.h" #include "commit-graph.h" -#include "loose.h" -unsigned int get_max_object_index(void) +unsigned int get_max_object_index(const struct repository *repo) { - return the_repository->parsed_objects->obj_hash_size; + return repo->parsed_objects->obj_hash_size; } -struct object *get_indexed_object(unsigned int idx) +struct object *get_indexed_object(const struct repository *repo, + unsigned int idx) { - return the_repository->parsed_objects->obj_hash[idx]; + return repo->parsed_objects->obj_hash[idx]; } static const char *object_type_strings[] = { @@ -283,10 +280,11 @@ struct object *parse_object_buffer(struct repository *r, const struct object_id return obj; } -struct object *parse_object_or_die(const struct object_id *oid, +struct object *parse_object_or_die(struct repository *repo, + const struct object_id *oid, const char *name) { - struct object *o = parse_object(the_repository, oid); + struct object *o = parse_object(repo, oid); if (o) return o; @@ -491,45 +489,12 @@ void object_array_clear(struct object_array *array) array->nr = array->alloc = 0; } -/* - * Return true if array already contains an entry. - */ -static int contains_object(struct object_array *array, - const struct object *item, const char *name) -{ - unsigned nr = array->nr, i; - struct object_array_entry *object = array->objects; - - for (i = 0; i < nr; i++, object++) - if (item == object->item && !strcmp(object->name, name)) - return 1; - return 0; -} - -void object_array_remove_duplicates(struct object_array *array) -{ - unsigned nr = array->nr, src; - struct object_array_entry *objects = array->objects; - - array->nr = 0; - for (src = 0; src < nr; src++) { - if (!contains_object(array, objects[src].item, - objects[src].name)) { - if (src != array->nr) - objects[array->nr] = objects[src]; - array->nr++; - } else { - object_array_release_entry(&objects[src]); - } - } -} - -void clear_object_flags(unsigned flags) +void clear_object_flags(struct repository *repo, unsigned flags) { int i; - for (i=0; i < the_repository->parsed_objects->obj_hash_size; i++) { - struct object *obj = the_repository->parsed_objects->obj_hash[i]; + for (i = 0; i < repo->parsed_objects->obj_hash_size; i++) { + struct object *obj = repo->parsed_objects->obj_hash[i]; if (obj) obj->flags &= ~flags; } @@ -566,70 +531,6 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo) return o; } -struct raw_object_store *raw_object_store_new(void) -{ - struct raw_object_store *o = xmalloc(sizeof(*o)); - - memset(o, 0, sizeof(*o)); - INIT_LIST_HEAD(&o->packed_git_mru); - hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0); - pthread_mutex_init(&o->replace_mutex, NULL); - return o; -} - -void free_object_directory(struct object_directory *odb) -{ - free(odb->path); - odb_clear_loose_cache(odb); - loose_object_map_clear(&odb->loose_map); - free(odb); -} - -static void free_object_directories(struct raw_object_store *o) -{ - while (o->odb) { - struct object_directory *next; - - next = o->odb->next; - free_object_directory(o->odb); - o->odb = next; - } - kh_destroy_odb_path_map(o->odb_by_path); - o->odb_by_path = NULL; -} - -void raw_object_store_clear(struct raw_object_store *o) -{ - FREE_AND_NULL(o->alternate_db); - - oidmap_free(o->replace_map, 1); - FREE_AND_NULL(o->replace_map); - pthread_mutex_destroy(&o->replace_mutex); - - free_commit_graph(o->commit_graph); - o->commit_graph = NULL; - o->commit_graph_attempted = 0; - - free_object_directories(o); - o->odb_tail = NULL; - o->loaded_alternates = 0; - - INIT_LIST_HEAD(&o->packed_git_mru); - close_object_store(o); - - /* - * `close_object_store()` only closes the packfiles, but doesn't free - * them. We thus have to do this manually. - */ - for (struct packed_git *p = o->packed_git, *next; p; p = next) { - next = p->next; - free(p); - } - o->packed_git = NULL; - - hashmap_clear(&o->pack_map); -} - void parsed_object_pool_reset_commit_grafts(struct parsed_object_pool *o) { for (int i = 0; i < o->grafts_nr; i++) { @@ -169,12 +169,13 @@ int type_from_string_gently(const char *str, ssize_t, int gentle); /* * Return the current number of buckets in the object hashmap. */ -unsigned int get_max_object_index(void); +unsigned int get_max_object_index(const struct repository *repo); /* * Return the object from the specified bucket in the object hashmap. */ -struct object *get_indexed_object(unsigned int); +struct object *get_indexed_object(const struct repository *repo, + unsigned int); /* * This can be used to see if we have heard of the object before, but @@ -231,7 +232,8 @@ struct object *parse_object_with_flags(struct repository *r, * "name" parameter is not NULL, it is included in the error message * (otherwise, the hex object ID is given). */ -struct object *parse_object_or_die(const struct object_id *oid, const char *name); +struct object *parse_object_or_die(struct repository *repo, const struct object_id *oid, + const char *name); /* Given the result of read_sha1_file(), returns the object after * parsing it. eaten_p indicates if the object has a borrowed copy @@ -325,18 +327,12 @@ void object_array_filter(struct object_array *array, object_array_each_func_t want, void *cb_data); /* - * Remove from array all but the first entry with a given name. - * Warning: this function uses an O(N^2) algorithm. - */ -void object_array_remove_duplicates(struct object_array *array); - -/* * Remove any objects from the array, freeing all used memory; afterwards * the array is ready to store more objects with add_object_array(). */ void object_array_clear(struct object_array *array); -void clear_object_flags(unsigned flags); +void clear_object_flags(struct repository *repo, unsigned flags); /* * Clear the specified object flags from all in-core commit objects from diff --git a/oss-fuzz/fuzz-pack-idx.c b/oss-fuzz/fuzz-pack-idx.c index 3e190214d1..609a343ee3 100644 --- a/oss-fuzz/fuzz-pack-idx.c +++ b/oss-fuzz/fuzz-pack-idx.c @@ -1,5 +1,5 @@ #include "git-compat-util.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 8a30853d2e..7f400ee012 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -1,11 +1,10 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" #include "environment.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -51,6 +50,7 @@ void bitmap_writer_init(struct bitmap_writer *writer, struct repository *r, memset(writer, 0, sizeof(struct bitmap_writer)); if (writer->bitmaps) BUG("bitmap writer already initialized"); + writer->repo = r; writer->bitmaps = kh_init_oid_map(); writer->pseudo_merge_commits = kh_init_oid_map(); writer->to_pack = pdata; @@ -442,9 +442,9 @@ next: bb->commits[bb->commits_nr++] = r->item; } - trace2_data_intmax("pack-bitmap-write", the_repository, + trace2_data_intmax("pack-bitmap-write", writer->repo, "num_selected_commits", writer->selected_nr); - trace2_data_intmax("pack-bitmap-write", the_repository, + trace2_data_intmax("pack-bitmap-write", writer->repo, "num_maximal_commits", num_maximal); release_revisions(&revs); @@ -487,7 +487,7 @@ static int fill_bitmap_tree(struct bitmap_writer *writer, switch (object_type(entry.mode)) { case OBJ_TREE: if (fill_bitmap_tree(writer, bitmap, - lookup_tree(the_repository, &entry.oid)) < 0) + lookup_tree(writer->repo, &entry.oid)) < 0) return -1; break; case OBJ_BLOB: @@ -563,7 +563,7 @@ static int fill_bitmap_commit(struct bitmap_writer *writer, return -1; bitmap_set(ent->bitmap, pos); prio_queue_put(tree_queue, - repo_get_commit_tree(the_repository, c)); + repo_get_commit_tree(writer->repo, c)); } for (p = c->parents; p; p = p->next) { @@ -617,11 +617,11 @@ int bitmap_writer_build(struct bitmap_writer *writer) int closed = 1; /* until proven otherwise */ if (writer->show_progress) - writer->progress = start_progress(the_repository, + writer->progress = start_progress(writer->repo, "Building bitmaps", writer->selected_nr); trace2_region_enter("pack-bitmap-write", "building_bitmaps_total", - the_repository); + writer->repo); old_bitmap = prepare_bitmap_git(writer->to_pack->repo); if (old_bitmap) @@ -672,10 +672,10 @@ int bitmap_writer_build(struct bitmap_writer *writer) free(mapping); trace2_region_leave("pack-bitmap-write", "building_bitmaps_total", - the_repository); - trace2_data_intmax("pack-bitmap-write", the_repository, + writer->repo); + trace2_data_intmax("pack-bitmap-write", writer->repo, "building_bitmaps_reused", reused_bitmaps_nr); - trace2_data_intmax("pack-bitmap-write", the_repository, + trace2_data_intmax("pack-bitmap-write", writer->repo, "building_bitmaps_pseudo_merge_reused", reused_pseudo_merge_bitmaps_nr); @@ -738,7 +738,7 @@ void bitmap_writer_select_commits(struct bitmap_writer *writer, } if (writer->show_progress) - writer->progress = start_progress(the_repository, + writer->progress = start_progress(writer->repo, "Selecting bitmap commits", 0); for (;;) { @@ -987,7 +987,7 @@ static void write_lookup_table(struct bitmap_writer *writer, struct hashfile *f, for (i = 0; i < bitmap_writer_nr_selected_commits(writer); i++) table_inv[table[i]] = i; - trace2_region_enter("pack-bitmap-write", "writing_lookup_table", the_repository); + trace2_region_enter("pack-bitmap-write", "writing_lookup_table", writer->repo); for (i = 0; i < bitmap_writer_nr_selected_commits(writer); i++) { struct bitmapped_commit *selected = &writer->selected[table[i]]; uint32_t xor_offset = selected->xor_offset; @@ -1014,7 +1014,7 @@ static void write_lookup_table(struct bitmap_writer *writer, struct hashfile *f, hashwrite_be64(f, (uint64_t)offsets[table[i]]); hashwrite_be32(f, xor_row); } - trace2_region_leave("pack-bitmap-write", "writing_lookup_table", the_repository); + trace2_region_leave("pack-bitmap-write", "writing_lookup_table", writer->repo); free(table); free(table_inv); @@ -1035,7 +1035,7 @@ static void write_hash_cache(struct hashfile *f, void bitmap_writer_set_checksum(struct bitmap_writer *writer, const unsigned char *sha1) { - hashcpy(writer->pack_checksum, sha1, the_repository->hash_algo); + hashcpy(writer->pack_checksum, sha1, writer->repo->hash_algo); } void bitmap_writer_finish(struct bitmap_writer *writer, @@ -1057,15 +1057,15 @@ void bitmap_writer_finish(struct bitmap_writer *writer, if (writer->pseudo_merges_nr) options |= BITMAP_OPT_PSEUDO_MERGES; - f = hashfd(fd, tmp_file.buf); + f = hashfd(writer->repo->hash_algo, fd, tmp_file.buf); memcpy(header.magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE)); header.version = htons(default_version); header.options = htons(flags | options); header.entry_count = htonl(bitmap_writer_nr_selected_commits(writer)); - hashcpy(header.checksum, writer->pack_checksum, the_repository->hash_algo); + hashcpy(header.checksum, writer->pack_checksum, writer->repo->hash_algo); - hashwrite(f, &header, sizeof(header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz); + hashwrite(f, &header, sizeof(header) - GIT_MAX_RAWSZ + writer->repo->hash_algo->rawsz); dump_bitmap(f, writer->commits); dump_bitmap(f, writer->trees); dump_bitmap(f, writer->blobs); @@ -1105,7 +1105,7 @@ void bitmap_writer_finish(struct bitmap_writer *writer, finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); - if (adjust_shared_perm(the_repository, tmp_file.buf)) + if (adjust_shared_perm(writer->repo, tmp_file.buf)) die_errno("unable to make temporary bitmap file readable"); if (rename(tmp_file.buf, filename)) diff --git a/pack-bitmap.c b/pack-bitmap.c index 6f7fd94c36..b9f1d86604 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -17,8 +17,7 @@ #include "packfile.h" #include "repository.h" #include "trace2.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "list-objects-filter-options.h" #include "midx.h" #include "config.h" @@ -745,6 +744,21 @@ struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx) return NULL; } +int bitmap_index_contains_pack(struct bitmap_index *bitmap, struct packed_git *pack) +{ + for (; bitmap; bitmap = bitmap->base) { + if (bitmap_is_midx(bitmap)) { + for (size_t i = 0; i < bitmap->midx->num_packs; i++) + if (bitmap->midx->packs[i] == pack) + return 1; + } else if (bitmap->pack == pack) { + return 1; + } + } + + return 0; +} + struct include_data { struct bitmap_index *bitmap_git; struct bitmap *base; @@ -1409,7 +1423,7 @@ static struct bitmap *find_boundary_objects(struct bitmap_index *bitmap_git, revs->tag_objects = tmp_tags; reset_revision_walk(); - clear_object_flags(UNINTERESTING); + clear_object_flags(repo, UNINTERESTING); /* * Then add the boundary commit(s) as fill-in traversal tips. @@ -1625,7 +1639,7 @@ static void show_extended_objects(struct bitmap_index *bitmap_git, (obj->type == OBJ_TAG && !revs->tag_objects)) continue; - show_reach(&obj->oid, obj->type, 0, eindex->hashes[i], NULL, 0); + show_reach(&obj->oid, obj->type, 0, eindex->hashes[i], NULL, 0, NULL); } } @@ -1662,8 +1676,10 @@ static void init_type_iterator(struct ewah_or_iterator *it, static void show_objects_for_type( struct bitmap_index *bitmap_git, + struct bitmap *objects, enum object_type object_type, - show_reachable_fn show_reach) + show_reachable_fn show_reach, + void *payload) { size_t i = 0; uint32_t offset; @@ -1671,8 +1687,6 @@ static void show_objects_for_type( struct ewah_or_iterator it; eword_t filter; - struct bitmap *objects = bitmap_git->result; - init_type_iterator(&it, bitmap_git, object_type); for (i = 0; i < objects->word_alloc && @@ -1715,7 +1729,7 @@ static void show_objects_for_type( if (bitmap_git->hashes) hash = get_be32(bitmap_git->hashes + index_pos); - show_reach(&oid, object_type, 0, hash, pack, ofs); + show_reach(&oid, object_type, 0, hash, pack, ofs, payload); } } @@ -2024,6 +2038,50 @@ static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git, } } +int for_each_bitmapped_object(struct bitmap_index *bitmap_git, + struct list_objects_filter_options *filter, + show_reachable_fn show_reach, + void *payload) +{ + struct bitmap *filtered_bitmap = NULL; + uint32_t objects_nr; + size_t full_word_count; + int ret; + + if (!can_filter_bitmap(filter)) { + ret = -1; + goto out; + } + + objects_nr = bitmap_num_objects(bitmap_git); + full_word_count = objects_nr / BITS_IN_EWORD; + + /* We start from the all-1 bitmap and then filter down from there. */ + filtered_bitmap = bitmap_word_alloc(full_word_count + !!(objects_nr % BITS_IN_EWORD)); + memset(filtered_bitmap->words, 0xff, full_word_count * sizeof(*filtered_bitmap->words)); + for (size_t i = full_word_count * BITS_IN_EWORD; i < objects_nr; i++) + bitmap_set(filtered_bitmap, i); + + if (filter_bitmap(bitmap_git, NULL, filtered_bitmap, filter) < 0) { + ret = -1; + goto out; + } + + show_objects_for_type(bitmap_git, filtered_bitmap, + OBJ_COMMIT, show_reach, payload); + show_objects_for_type(bitmap_git, filtered_bitmap, + OBJ_TREE, show_reach, payload); + show_objects_for_type(bitmap_git, filtered_bitmap, + OBJ_BLOB, show_reach, payload); + show_objects_for_type(bitmap_git, filtered_bitmap, + OBJ_TAG, show_reach, payload); + + ret = 0; +out: + bitmap_free(filtered_bitmap); + return ret; +} + struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, int filter_provided_objects) { @@ -2060,7 +2118,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, struct object *object = revs->pending.objects[i].item; if (object->type == OBJ_NONE) - parse_object_or_die(&object->oid, NULL); + parse_object_or_die(revs->repo, &object->oid, NULL); while (object->type == OBJ_TAG) { struct tag *tag = (struct tag *) object; @@ -2070,7 +2128,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, else object_list_insert(object, &wants); - object = parse_object_or_die(get_tagged_oid(tag), NULL); + object = parse_object_or_die(revs->repo, get_tagged_oid(tag), NULL); object->flags |= (tag->object.flags & UNINTERESTING); } @@ -2518,13 +2576,17 @@ void traverse_bitmap_commit_list(struct bitmap_index *bitmap_git, { assert(bitmap_git->result); - show_objects_for_type(bitmap_git, OBJ_COMMIT, show_reachable); + show_objects_for_type(bitmap_git, bitmap_git->result, + OBJ_COMMIT, show_reachable, NULL); if (revs->tree_objects) - show_objects_for_type(bitmap_git, OBJ_TREE, show_reachable); + show_objects_for_type(bitmap_git, bitmap_git->result, + OBJ_TREE, show_reachable, NULL); if (revs->blob_objects) - show_objects_for_type(bitmap_git, OBJ_BLOB, show_reachable); + show_objects_for_type(bitmap_git, bitmap_git->result, + OBJ_BLOB, show_reachable, NULL); if (revs->tag_objects) - show_objects_for_type(bitmap_git, OBJ_TAG, show_reachable); + show_objects_for_type(bitmap_git, bitmap_git->result, + OBJ_TAG, show_reachable, NULL); show_extended_objects(bitmap_git, revs, show_reachable); } @@ -3216,7 +3278,8 @@ int bitmap_is_preferred_refname(struct repository *r, const char *refname) return 0; } -static int verify_bitmap_file(const char *name) +static int verify_bitmap_file(const struct git_hash_algo *algop, + const char *name) { struct stat st; unsigned char *data; @@ -3232,7 +3295,7 @@ static int verify_bitmap_file(const char *name) data = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - if (!hashfile_checksum_valid(data, st.st_size)) + if (!hashfile_checksum_valid(algop, data, st.st_size)) res = error(_("bitmap file '%s' has invalid checksum"), name); @@ -3247,14 +3310,14 @@ int verify_bitmap_files(struct repository *r) for (struct multi_pack_index *m = get_multi_pack_index(r); m; m = m->next) { char *midx_bitmap_name = midx_bitmap_filename(m); - res |= verify_bitmap_file(midx_bitmap_name); + res |= verify_bitmap_file(r->hash_algo, midx_bitmap_name); free(midx_bitmap_name); } for (struct packed_git *p = get_all_packs(r); p; p = p->next) { char *pack_bitmap_name = pack_bitmap_filename(p); - res |= verify_bitmap_file(pack_bitmap_name); + res |= verify_bitmap_file(r->hash_algo, pack_bitmap_name); free(pack_bitmap_name); } diff --git a/pack-bitmap.h b/pack-bitmap.h index dd0951088f..382d39499a 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -50,7 +50,8 @@ typedef int (*show_reachable_fn)( int flags, uint32_t hash, struct packed_git *found_pack, - off_t found_offset); + off_t found_offset, + void *payload); struct bitmap_index; @@ -66,6 +67,13 @@ struct bitmapped_pack { struct bitmap_index *prepare_bitmap_git(struct repository *r); struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx); + +/* + * Given a bitmap index, determine whether it contains the pack either directly + * or via the multi-pack-index. + */ +int bitmap_index_contains_pack(struct bitmap_index *bitmap, struct packed_git *pack); + void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits, uint32_t *trees, uint32_t *blobs, uint32_t *tags); void traverse_bitmap_commit_list(struct bitmap_index *, @@ -78,6 +86,18 @@ int test_bitmap_pseudo_merges(struct repository *r); int test_bitmap_pseudo_merge_commits(struct repository *r, uint32_t n); int test_bitmap_pseudo_merge_objects(struct repository *r, uint32_t n); +struct list_objects_filter_options; + +/* + * Filter bitmapped objects and iterate through all resulting objects, + * executing `show_reach` for each of them. Returns `-1` in case the filter is + * not supported, `0` otherwise. + */ +int for_each_bitmapped_object(struct bitmap_index *bitmap_git, + struct list_objects_filter_options *filter, + show_reachable_fn show_reach, + void *payload); + #define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \ "GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL" @@ -104,6 +124,7 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_i off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *); struct bitmap_writer { + struct repository *repo; struct ewah_bitmap *commits; struct ewah_bitmap *trees; struct ewah_bitmap *blobs; diff --git a/pack-check.c b/pack-check.c index d0aeb5ec41..874897d6cb 100644 --- a/pack-check.c +++ b/pack-check.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -9,7 +8,7 @@ #include "progress.h" #include "packfile.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" struct idx_entry { off_t offset; @@ -44,7 +43,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, } while (len); index_crc = p->index_data; - index_crc += 2 + 256 + (size_t)p->num_objects * (the_hash_algo->rawsz/4) + nr; + index_crc += 2 + 256 + (size_t)p->num_objects * (p->repo->hash_algo->rawsz/4) + nr; return data_crc != ntohl(*index_crc); } @@ -81,11 +80,11 @@ static int verify_packfile(struct repository *r, } while (offset < pack_sig_ofs); git_hash_final(hash, &ctx); pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL); - if (!hasheq(hash, pack_sig, the_repository->hash_algo)) + if (!hasheq(hash, pack_sig, r->hash_algo)) err = error("%s pack checksum mismatch", p->pack_name); if (!hasheq(index_base + index_size - r->hash_algo->hexsz, pack_sig, - the_repository->hash_algo)) + r->hash_algo)) err = error("%s pack checksum does not match its index", p->pack_name); unuse_pack(w_curs); @@ -131,7 +130,8 @@ static int verify_packfile(struct repository *r, type = unpack_object_header(p, w_curs, &curpos, &size); unuse_pack(w_curs); - if (type == OBJ_BLOB && big_file_threshold <= size) { + if (type == OBJ_BLOB && + repo_settings_get_big_file_threshold(r) <= size) { /* * Let stream_object_signature() check it with * the streaming interface; no point slurping @@ -180,7 +180,7 @@ int verify_pack_index(struct packed_git *p) return error("packfile %s index not opened", p->pack_name); /* Verify SHA1 sum of the index file */ - if (!hashfile_checksum_valid(p->index_data, p->index_size)) + if (!hashfile_checksum_valid(p->repo->hash_algo, p->index_data, p->index_size)) err = error("Packfile index for %s hash mismatch", p->pack_name); return err; diff --git a/pack-mtimes.c b/pack-mtimes.c index cdf30b8d2b..20900ca88d 100644 --- a/pack-mtimes.c +++ b/pack-mtimes.c @@ -1,8 +1,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "pack-mtimes.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "strbuf.h" diff --git a/pack-objects.h b/pack-objects.h index d73e3843c9..d1c4ae7f9b 100644 --- a/pack-objects.h +++ b/pack-objects.h @@ -1,7 +1,7 @@ #ifndef PACK_OBJECTS_H #define PACK_OBJECTS_H -#include "object-store-ll.h" +#include "object-store.h" #include "thread-utils.h" #include "pack.h" diff --git a/pack-revindex.c b/pack-revindex.c index d3faab6a37..ffcde48870 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -1,14 +1,12 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "gettext.h" #include "pack-revindex.h" -#include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "strbuf.h" #include "trace2.h" #include "parse.h" +#include "repository.h" #include "midx.h" #include "csum-file.h" @@ -137,7 +135,7 @@ static void create_pack_revindex(struct packed_git *p) const unsigned num_ent = p->num_objects; unsigned i; const char *index = p->index_data; - const unsigned hashsz = the_hash_algo->rawsz; + const unsigned hashsz = p->repo->hash_algo->rawsz; ALLOC_ARRAY(p->revindex, num_ent + 1); index += 4 * 256; @@ -193,7 +191,11 @@ static char *pack_revindex_filename(struct packed_git *p) } #define RIDX_HEADER_SIZE (12) -#define RIDX_MIN_SIZE (RIDX_HEADER_SIZE + (2 * the_hash_algo->rawsz)) + +static size_t ridx_min_size(const struct git_hash_algo *algo) +{ + return RIDX_HEADER_SIZE + (2 * algo->rawsz); +} struct revindex_header { uint32_t signature; @@ -201,7 +203,8 @@ struct revindex_header { uint32_t hash_id; }; -static int load_revindex_from_disk(char *revindex_name, +static int load_revindex_from_disk(const struct git_hash_algo *algo, + char *revindex_name, uint32_t num_objects, const uint32_t **data_p, size_t *len_p) { @@ -228,12 +231,12 @@ static int load_revindex_from_disk(char *revindex_name, revindex_size = xsize_t(st.st_size); - if (revindex_size < RIDX_MIN_SIZE) { + if (revindex_size < ridx_min_size(algo)) { ret = error(_("reverse-index file %s is too small"), revindex_name); goto cleanup; } - if (revindex_size - RIDX_MIN_SIZE != st_mult(sizeof(uint32_t), num_objects)) { + if (revindex_size - ridx_min_size(algo) != st_mult(sizeof(uint32_t), num_objects)) { ret = error(_("reverse-index file %s is corrupt"), revindex_name); goto cleanup; } @@ -279,7 +282,8 @@ int load_pack_revindex_from_disk(struct packed_git *p) revindex_name = pack_revindex_filename(p); - ret = load_revindex_from_disk(revindex_name, + ret = load_revindex_from_disk(p->repo->hash_algo, + revindex_name, p->num_objects, &p->revindex_map, &p->revindex_size); @@ -322,7 +326,8 @@ int verify_pack_revindex(struct packed_git *p) if (!p->revindex_map || !p->revindex_data) return res; - if (!hashfile_checksum_valid((const unsigned char *)p->revindex_map, p->revindex_size)) { + if (!hashfile_checksum_valid(p->repo->hash_algo, + (const unsigned char *)p->revindex_map, p->revindex_size)) { error(_("invalid checksum")); res = -1; } @@ -374,13 +379,13 @@ int load_midx_revindex(struct multi_pack_index *m) * not want to accidentally call munmap() in the middle of the * MIDX. */ - trace2_data_string("load_midx_revindex", the_repository, + trace2_data_string("load_midx_revindex", m->repo, "source", "midx"); m->revindex_data = (const uint32_t *)m->chunk_revindex; return 0; } - trace2_data_string("load_midx_revindex", the_repository, + trace2_data_string("load_midx_revindex", m->repo, "source", "rev"); if (m->has_chain) @@ -392,7 +397,8 @@ int load_midx_revindex(struct multi_pack_index *m) m->object_dir, get_midx_checksum(m), MIDX_EXT_REV); - ret = load_revindex_from_disk(revindex_name.buf, + ret = load_revindex_from_disk(m->repo->hash_algo, + revindex_name.buf, m->num_objects, &m->revindex_map, &m->revindex_len); @@ -424,7 +430,7 @@ int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos) { unsigned lo, hi; - if (load_pack_revindex(the_repository, p) < 0) + if (load_pack_revindex(p->repo, p) < 0) return -1; lo = 0; @@ -470,7 +476,7 @@ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos) if (p->revindex) return p->revindex[pos].offset; else if (pos == p->num_objects) - return p->pack_size - the_hash_algo->rawsz; + return p->pack_size - p->repo->hash_algo->rawsz; else return nth_packed_object_offset(p, pack_pos_to_index(p, pos)); } diff --git a/pack-write.c b/pack-write.c index 823e40b42f..6b06315f80 100644 --- a/pack-write.c +++ b/pack-write.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "environment.h" #include "gettext.h" @@ -56,7 +54,7 @@ static int need_large_offset(off_t offset, const struct pack_idx_option *opts) * The *sha1 contains the pack content SHA1 hash. * The objects array passed in will be sorted by SHA1 on exit. */ -const char *write_idx_file(const struct git_hash_algo *hash_algo, +const char *write_idx_file(struct repository *repo, const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *opts, const unsigned char *sha1) @@ -82,7 +80,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, if (opts->flags & WRITE_IDX_VERIFY) { assert(index_name); - f = hashfd_check(index_name); + f = hashfd_check(repo->hash_algo, index_name); } else { if (!index_name) { struct strbuf tmp_file = STRBUF_INIT; @@ -92,7 +90,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, unlink(index_name); fd = xopen(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600); } - f = hashfd(fd, index_name); + f = hashfd(repo->hash_algo, fd, index_name); } /* if last object's offset is >= 2^31 we should use index V2 */ @@ -131,7 +129,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, struct pack_idx_entry *obj = *list++; if (index_version < 2) hashwrite_be32(f, obj->offset); - hashwrite(f, obj->oid.hash, hash_algo->rawsz); + hashwrite(f, obj->oid.hash, repo->hash_algo->rawsz); if ((opts->flags & WRITE_IDX_STRICT) && (i && oideq(&list[-2]->oid, &obj->oid))) die("The same object %s appears twice in the pack", @@ -173,7 +171,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, } } - hashwrite(f, sha1, hash_algo->rawsz); + hashwrite(f, sha1, repo->hash_algo->rawsz); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, CSUM_HASH_IN_STREAM | CSUM_CLOSE | ((opts->flags & WRITE_IDX_VERIFY) ? 0 : CSUM_FSYNC)); @@ -217,7 +215,7 @@ static void write_rev_trailer(const struct git_hash_algo *hash_algo, hashwrite(f, hash, hash_algo->rawsz); } -char *write_rev_file(const struct git_hash_algo *hash_algo, +char *write_rev_file(struct repository *repo, const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, @@ -236,7 +234,7 @@ char *write_rev_file(const struct git_hash_algo *hash_algo, pack_order[i] = i; QSORT_S(pack_order, nr_objects, pack_order_cmp, objects); - ret = write_rev_file_order(hash_algo, rev_name, pack_order, nr_objects, + ret = write_rev_file_order(repo, rev_name, pack_order, nr_objects, hash, flags); free(pack_order); @@ -244,7 +242,7 @@ char *write_rev_file(const struct git_hash_algo *hash_algo, return ret; } -char *write_rev_file_order(const struct git_hash_algo *hash_algo, +char *write_rev_file_order(struct repository *repo, const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, @@ -268,7 +266,7 @@ char *write_rev_file_order(const struct git_hash_algo *hash_algo, fd = xopen(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600); path = xstrdup(rev_name); } - f = hashfd(fd, path); + f = hashfd(repo->hash_algo, fd, path); } else if (flags & WRITE_REV_VERIFY) { struct stat statbuf; if (stat(rev_name, &statbuf)) { @@ -278,18 +276,18 @@ char *write_rev_file_order(const struct git_hash_algo *hash_algo, } else die_errno(_("could not stat: %s"), rev_name); } - f = hashfd_check(rev_name); + f = hashfd_check(repo->hash_algo, rev_name); path = xstrdup(rev_name); } else { return NULL; } - write_rev_header(hash_algo, f); + write_rev_header(repo->hash_algo, f); write_rev_index_positions(f, pack_order, nr_objects); - write_rev_trailer(hash_algo, f, hash); + write_rev_trailer(repo->hash_algo, f, hash); - if (adjust_shared_perm(the_repository, path) < 0) + if (adjust_shared_perm(repo, path) < 0) die(_("failed to make %s readable"), path); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, @@ -330,7 +328,7 @@ static void write_mtimes_trailer(const struct git_hash_algo *hash_algo, hashwrite(f, hash, hash_algo->rawsz); } -static char *write_mtimes_file(const struct git_hash_algo *hash_algo, +static char *write_mtimes_file(struct repository *repo, struct packing_data *to_pack, struct pack_idx_entry **objects, uint32_t nr_objects, @@ -346,13 +344,13 @@ static char *write_mtimes_file(const struct git_hash_algo *hash_algo, fd = odb_mkstemp(&tmp_file, "pack/tmp_mtimes_XXXXXX"); mtimes_name = strbuf_detach(&tmp_file, NULL); - f = hashfd(fd, mtimes_name); + f = hashfd(repo->hash_algo, fd, mtimes_name); - write_mtimes_header(hash_algo, f); + write_mtimes_header(repo->hash_algo, f); write_mtimes_objects(f, to_pack, objects, nr_objects); - write_mtimes_trailer(hash_algo, f, hash); + write_mtimes_trailer(repo->hash_algo, f, hash); - if (adjust_shared_perm(the_repository, mtimes_name) < 0) + if (adjust_shared_perm(repo, mtimes_name) < 0) die(_("failed to make %s readable"), mtimes_name); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, @@ -527,14 +525,15 @@ int encode_in_pack_object_header(unsigned char *hdr, int hdr_len, return n; } -struct hashfile *create_tmp_packfile(char **pack_tmp_name) +struct hashfile *create_tmp_packfile(struct repository *repo, + char **pack_tmp_name) { struct strbuf tmpname = STRBUF_INIT; int fd; fd = odb_mkstemp(&tmpname, "pack/tmp_pack_XXXXXX"); *pack_tmp_name = strbuf_detach(&tmpname, NULL); - return hashfd(fd, *pack_tmp_name); + return hashfd(repo->hash_algo, fd, *pack_tmp_name); } static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source, @@ -555,7 +554,7 @@ void rename_tmp_packfile_idx(struct strbuf *name_buffer, rename_tmp_packfile(name_buffer, *idx_tmp_name, "idx"); } -void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, +void stage_tmp_packfiles(struct repository *repo, struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, @@ -568,19 +567,19 @@ void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, char *rev_tmp_name = NULL; char *mtimes_tmp_name = NULL; - if (adjust_shared_perm(the_repository, pack_tmp_name)) + if (adjust_shared_perm(repo, pack_tmp_name)) die_errno("unable to make temporary pack file readable"); - *idx_tmp_name = (char *)write_idx_file(hash_algo, NULL, written_list, + *idx_tmp_name = (char *)write_idx_file(repo, NULL, written_list, nr_written, pack_idx_opts, hash); - if (adjust_shared_perm(the_repository, *idx_tmp_name)) + if (adjust_shared_perm(repo, *idx_tmp_name)) die_errno("unable to make temporary index file readable"); - rev_tmp_name = write_rev_file(hash_algo, NULL, written_list, nr_written, + rev_tmp_name = write_rev_file(repo, NULL, written_list, nr_written, hash, pack_idx_opts->flags); if (pack_idx_opts->flags & WRITE_MTIMES) { - mtimes_tmp_name = write_mtimes_file(hash_algo, to_pack, + mtimes_tmp_name = write_mtimes_file(repo, to_pack, written_list, nr_written, hash); } @@ -87,7 +87,7 @@ struct progress; /* Note, the data argument could be NULL if object type is blob */ typedef int (*verify_fn)(const struct object_id *, enum object_type, unsigned long, void*, int*); -const char *write_idx_file(const struct git_hash_algo *hash_algo, +const char *write_idx_file(struct repository *repo, const char *index_name, struct pack_idx_entry **objects, int nr_objects, @@ -106,13 +106,13 @@ struct ref; void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought); -char *write_rev_file(const struct git_hash_algo *hash_algo, +char *write_rev_file(struct repository *repo, const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, const unsigned char *hash, unsigned flags); -char *write_rev_file_order(const struct git_hash_algo *hash_algo, +char *write_rev_file_order(struct repository *repo, const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, @@ -134,8 +134,9 @@ int read_pack_header(int fd, struct pack_header *); struct packing_data; -struct hashfile *create_tmp_packfile(char **pack_tmp_name); -void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, +struct hashfile *create_tmp_packfile(struct repository *repo, + char **pack_tmp_name); +void stage_tmp_packfiles(struct repository *repo, struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, diff --git a/packfile.c b/packfile.c index 9d09f8bc72..d91016f1c7 100644 --- a/packfile.c +++ b/packfile.c @@ -19,7 +19,7 @@ #include "tree-walk.h" #include "tree.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "midx.h" #include "commit-graph.h" #include "pack-revindex.h" diff --git a/parse-options-cb.c b/parse-options-cb.c index 166d35e0eb..50c8afe412 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -145,7 +145,7 @@ int parse_opt_object_id(const struct option *opt, const char *arg, int unset) struct object_id *target = opt->value; if (unset) { - oidcpy(target, null_oid()); + oidcpy(target, null_oid(the_hash_algo)); return 0; } if (!arg) diff --git a/parse-options.c b/parse-options.c index 35fbb3b0d6..a9a39ecaef 100644 --- a/parse-options.c +++ b/parse-options.c @@ -73,7 +73,7 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, enum opt_parsed flags, const char **argp) { - const char *s, *arg; + const char *arg; const int unset = flags & OPT_UNSET; int err; @@ -172,41 +172,93 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, return (*opt->ll_callback)(p, opt, p_arg, p_unset); } case OPTION_INTEGER: + { + intmax_t upper_bound = INTMAX_MAX >> (bitsizeof(intmax_t) - CHAR_BIT * opt->precision); + intmax_t lower_bound = -upper_bound - 1; + intmax_t value; + if (unset) { - *(int *)opt->value = 0; - return 0; - } - if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { - *(int *)opt->value = opt->defval; - return 0; - } - if (get_arg(p, opt, flags, &arg)) + value = 0; + } else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { + value = opt->defval; + } else if (get_arg(p, opt, flags, &arg)) { return -1; - if (!*arg) + } else if (!*arg) { return error(_("%s expects a numerical value"), optname(opt, flags)); - *(int *)opt->value = strtol(arg, (char **)&s, 10); - if (*s) - return error(_("%s expects a numerical value"), + } else if (!git_parse_signed(arg, &value, upper_bound)) { + if (errno == ERANGE) + return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), + arg, optname(opt, flags), lower_bound, upper_bound); + + return error(_("%s expects an integer value with an optional k/m/g suffix"), optname(opt, flags)); - return 0; + } - case OPTION_MAGNITUDE: - if (unset) { - *(unsigned long *)opt->value = 0; + if (value < lower_bound) + return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), + arg, optname(opt, flags), (intmax_t)lower_bound, (intmax_t)upper_bound); + + switch (opt->precision) { + case 1: + *(int8_t *)opt->value = value; return 0; - } - if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { - *(unsigned long *)opt->value = opt->defval; + case 2: + *(int16_t *)opt->value = value; + return 0; + case 4: + *(int32_t *)opt->value = value; return 0; + case 8: + *(int64_t *)opt->value = value; + return 0; + default: + BUG("invalid precision for option %s", + optname(opt, flags)); } - if (get_arg(p, opt, flags, &arg)) + } + case OPTION_UNSIGNED: + { + uintmax_t upper_bound = UINTMAX_MAX >> (bitsizeof(uintmax_t) - CHAR_BIT * opt->precision); + uintmax_t value; + + if (unset) { + value = 0; + } else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { + value = opt->defval; + } else if (get_arg(p, opt, flags, &arg)) { return -1; - if (!git_parse_ulong(arg, opt->value)) + } else if (!*arg) { + return error(_("%s expects a numerical value"), + optname(opt, flags)); + } else if (!git_parse_unsigned(arg, &value, upper_bound)) { + if (errno == ERANGE) + return error(_("value %s for %s not in range [%"PRIdMAX",%"PRIdMAX"]"), + arg, optname(opt, flags), (uintmax_t) 0, upper_bound); + return error(_("%s expects a non-negative integer value" " with an optional k/m/g suffix"), optname(opt, flags)); - return 0; + } + + switch (opt->precision) { + case 1: + *(uint8_t *)opt->value = value; + return 0; + case 2: + *(uint16_t *)opt->value = value; + return 0; + case 4: + *(uint32_t *)opt->value = value; + return 0; + case 8: + *(uint64_t *)opt->value = value; + return 0; + default: + BUG("invalid precision for option %s", + optname(opt, flags)); + } + } default: BUG("opt->type %d should not happen", opt->type); @@ -656,7 +708,7 @@ static void show_negated_gitcomp(const struct option *opts, int show_all, case OPTION_STRING: case OPTION_FILENAME: case OPTION_INTEGER: - case OPTION_MAGNITUDE: + case OPTION_UNSIGNED: case OPTION_CALLBACK: case OPTION_BIT: case OPTION_NEGBIT: @@ -708,7 +760,7 @@ static int show_gitcomp(const struct option *opts, int show_all) case OPTION_STRING: case OPTION_FILENAME: case OPTION_INTEGER: - case OPTION_MAGNITUDE: + case OPTION_UNSIGNED: case OPTION_CALLBACK: if (opts->flags & PARSE_OPT_NOARG) break; diff --git a/parse-options.h b/parse-options.h index 997ffbee80..91c3e3c29b 100644 --- a/parse-options.h +++ b/parse-options.h @@ -25,7 +25,7 @@ enum parse_opt_type { /* options with arguments (usually) */ OPTION_STRING, OPTION_INTEGER, - OPTION_MAGNITUDE, + OPTION_UNSIGNED, OPTION_CALLBACK, OPTION_LOWLEVEL_CALLBACK, OPTION_FILENAME @@ -92,6 +92,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv, * `value`:: * stores pointers to the values to be filled. * + * `precision`:: + * precision of the integer pointed to by `value` in number of bytes. Should + * typically be its `sizeof()`. + * * `argh`:: * token to explain the kind of argument this option wants. Does not * begin in capital letter, and does not end with a full stop. @@ -151,6 +155,7 @@ struct option { int short_name; const char *long_name; void *value; + size_t precision; const char *argh; const char *help; @@ -213,7 +218,8 @@ struct option { .type = OPTION_INTEGER, \ .short_name = (s), \ .long_name = (l), \ - .value = (v), \ + .value = (v) + BARF_UNLESS_SIGNED(*(v)), \ + .precision = sizeof(*v), \ .argh = N_("n"), \ .help = (h), \ .flags = (f), \ @@ -270,11 +276,12 @@ struct option { #define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0) #define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0) -#define OPT_MAGNITUDE(s, l, v, h) { \ - .type = OPTION_MAGNITUDE, \ +#define OPT_UNSIGNED(s, l, v, h) { \ + .type = OPTION_UNSIGNED, \ .short_name = (s), \ .long_name = (l), \ - .value = (v), \ + .value = (v) + BARF_UNLESS_UNSIGNED(*(v)), \ + .precision = sizeof(*v), \ .argh = N_("n"), \ .help = (h), \ .flags = PARSE_OPT_NONEG, \ @@ -38,7 +38,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) errno = EINVAL; return 0; } - if ((val < 0 && -max / factor > val) || + if ((val < 0 && (-max - 1) / factor > val) || (val > 0 && max / factor < val)) { errno = ERANGE; return 0; @@ -51,7 +51,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) return 0; } -static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) { if (value && *value) { char *end; @@ -2,6 +2,7 @@ #define PARSE_H int git_parse_signed(const char *value, intmax_t *ret, intmax_t max); +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max); int git_parse_ssize_t(const char *, ssize_t *); int git_parse_ulong(const char *, unsigned long *); int git_parse_int(const char *value, int *ret); @@ -15,7 +15,7 @@ #include "submodule-config.h" #include "path.h" #include "packfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "lockfile.h" #include "exec-cmd.h" @@ -902,6 +902,115 @@ void safe_create_dir(struct repository *repo, const char *dir, int share) die(_("Could not make %s writable by group"), dir); } +int safe_create_dir_in_gitdir(struct repository *repo, const char *path) +{ + if (mkdir(path, 0777)) { + int saved_errno = errno; + struct stat st; + struct strbuf sb = STRBUF_INIT; + + if (errno != EEXIST) + return -1; + /* + * Are we looking at a path in a symlinked worktree + * whose original repository does not yet have it? + * e.g. .git/rr-cache pointing at its original + * repository in which the user hasn't performed any + * conflict resolution yet? + */ + if (lstat(path, &st) || !S_ISLNK(st.st_mode) || + strbuf_readlink(&sb, path, st.st_size) || + !is_absolute_path(sb.buf) || + mkdir(sb.buf, 0777)) { + strbuf_release(&sb); + errno = saved_errno; + return -1; + } + strbuf_release(&sb); + } + return adjust_shared_perm(repo, path); +} + +static enum scld_error safe_create_leading_directories_1(struct repository *repo, + char *path) +{ + char *next_component = path + offset_1st_component(path); + enum scld_error ret = SCLD_OK; + + while (ret == SCLD_OK && next_component) { + struct stat st; + char *slash = next_component, slash_character; + + while (*slash && !is_dir_sep(*slash)) + slash++; + + if (!*slash) + break; + + next_component = slash + 1; + while (is_dir_sep(*next_component)) + next_component++; + if (!*next_component) + break; + + slash_character = *slash; + *slash = '\0'; + if (!stat(path, &st)) { + /* path exists */ + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + ret = SCLD_EXISTS; + } + } else if (mkdir(path, 0777)) { + if (errno == EEXIST && + !stat(path, &st) && S_ISDIR(st.st_mode)) + ; /* somebody created it since we checked */ + else if (errno == ENOENT) + /* + * Either mkdir() failed because + * somebody just pruned the containing + * directory, or stat() failed because + * the file that was in our way was + * just removed. Either way, inform + * the caller that it might be worth + * trying again: + */ + ret = SCLD_VANISHED; + else + ret = SCLD_FAILED; + } else if (repo && adjust_shared_perm(repo, path)) { + ret = SCLD_PERMS; + } + *slash = slash_character; + } + return ret; +} + +enum scld_error safe_create_leading_directories(struct repository *repo, + char *path) +{ + return safe_create_leading_directories_1(repo, path); +} + +enum scld_error safe_create_leading_directories_no_share(char *path) +{ + return safe_create_leading_directories_1(NULL, path); +} + +enum scld_error safe_create_leading_directories_const(struct repository *repo, + const char *path) +{ + int save_errno; + /* path points to cache entries, so xstrdup before messing with it */ + char *buf = xstrdup(path); + enum scld_error result = safe_create_leading_directories(repo, buf); + + save_errno = errno; + free(buf); + errno = save_errno; + return result; +} + static int have_same_root(const char *path1, const char *path2) { int is_abs1, is_abs2; @@ -221,6 +221,51 @@ char *xdg_cache_home(const char *filename); */ void safe_create_dir(struct repository *repo, const char *dir, int share); +/* + * Similar to `safe_create_dir()`, but with two differences: + * + * - It knows to resolve gitlink files for symlinked worktrees. + * + * - It always adjusts shared permissions. + * + * Returns a negative erorr code on error, 0 on success. + */ +int safe_create_dir_in_gitdir(struct repository *repo, const char *path); + +/* + * Create the directory containing the named path, using care to be + * somewhat safe against races. Return one of the scld_error values to + * indicate success/failure. On error, set errno to describe the + * problem. + * + * SCLD_VANISHED indicates that one of the ancestor directories of the + * path existed at one point during the function call and then + * suddenly vanished, probably because another process pruned the + * directory while we were working. To be robust against this kind of + * race, callers might want to try invoking the function again when it + * returns SCLD_VANISHED. + * + * safe_create_leading_directories() temporarily changes path while it + * is working but restores it before returning. + * safe_create_leading_directories_const() doesn't modify path, even + * temporarily. Both these variants adjust the permissions of the + * created directories to honor core.sharedRepository, so they are best + * suited for files inside the git dir. For working tree files, use + * safe_create_leading_directories_no_share() instead, as it ignores + * the core.sharedRepository setting. + */ +enum scld_error { + SCLD_OK = 0, + SCLD_FAILED = -1, + SCLD_PERMS = -2, + SCLD_EXISTS = -3, + SCLD_VANISHED = -4 +}; +enum scld_error safe_create_leading_directories(struct repository *repo, char *path); +enum scld_error safe_create_leading_directories_const(struct repository *repo, + const char *path); +enum scld_error safe_create_leading_directories_no_share(char *path); + # ifdef USE_THE_REPOSITORY_VARIABLE # include "strbuf.h" # include "repository.h" diff --git a/pathspec.c b/pathspec.c index 89663645e1..2b4e434bc0 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,5 +1,4 @@ #define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" #include "abspath.h" @@ -35,7 +34,7 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen, enum ps_skip_worktree_action sw_action) { - int num_unmatched = 0, i; + int num_unmatched = 0; /* * Since we are walking the index as if we were walking the directory, @@ -43,12 +42,12 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, * mistakenly think that the user gave a pathspec that did not match * anything. */ - for (i = 0; i < pathspec->nr; i++) + for (int i = 0; i < pathspec->nr; i++) if (!seen[i]) num_unmatched++; if (!num_unmatched) return; - for (i = 0; i < istate->cache_nr; i++) { + for (unsigned int i = 0; i < istate->cache_nr; i++) { const struct cache_entry *ce = istate->cache[i]; if (sw_action == PS_IGNORE_SKIP_WORKTREE && (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))) @@ -78,7 +77,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec) { struct index_state *istate = the_repository->index; char *seen = xcalloc(pathspec->nr, 1); - int i; + unsigned int i; for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; @@ -130,7 +129,7 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, if (element[1] != '(') { /* Process an element in shorthand form (e.g. ":!/<match>") */ strbuf_addstr(sb, ":("); - for (int i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + for (unsigned int i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if ((magic & pathspec_magic[i].bit) && pathspec_magic[i].mnemonic) { if (sb->buf[sb->len - 1] != '(') @@ -341,7 +340,7 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len, for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) { size_t len = strcspn_escaped(pos, ",)"); - int i; + unsigned int i; if (pos[len] == ',') nextat = pos + len + 1; /* handle ',' */ @@ -354,7 +353,7 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len, if (starts_with(pos, "prefix:")) { char *endptr; *prefix_len = strtol(pos + 7, &endptr, 10); - if (endptr - pos != len) + if ((size_t)(endptr - pos) != len) die(_("invalid parameter for pathspec magic 'prefix'")); continue; } @@ -400,7 +399,7 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) for (pos = elem + 1; *pos && *pos != ':'; pos++) { char ch = *pos; - int i; + unsigned int i; /* Special case alias for '!' */ if (ch == '^') { @@ -564,7 +563,7 @@ static int pathspec_item_cmp(const void *a_, const void *b_) void pathspec_magic_names(unsigned magic, struct strbuf *out) { - int i; + unsigned int i; for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { const struct pathspec_magic *m = pathspec_magic + i; if (!(magic & m->bit)) @@ -803,8 +802,8 @@ int match_pathspec_attrs(struct index_state *istate, int pathspec_needs_expanded_index(struct index_state *istate, const struct pathspec *pathspec) { - unsigned int i, pos; - int res = 0; + unsigned int pos; + int i, res = 0; char *skip_worktree_seen = NULL; /* @@ -845,7 +844,8 @@ int pathspec_needs_expanded_index(struct index_state *istate, * - not-in-cone/bar*: may need expanded index * - **.c: may need expanded index */ - if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && + if (strspn(item.original + item.nowildcard_len, "*") == + (unsigned int)(item.len - item.nowildcard_len) && path_in_cone_mode_sparse_checkout(item.original, istate)) continue; @@ -860,8 +860,10 @@ int pathspec_needs_expanded_index(struct index_state *istate, * directory name and the sparse directory is the first * component of the pathspec, need to expand the index. */ - if (item.nowildcard_len > ce_namelen(ce) && - !strncmp(item.original, ce->name, ce_namelen(ce))) { + if ((unsigned int)item.nowildcard_len > + ce_namelen(ce) && + !strncmp(item.original, ce->name, + ce_namelen(ce))) { res = 1; break; } diff --git a/promisor-remote.c b/promisor-remote.c index 5801ebfd9b..9d058586df 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -3,7 +3,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "promisor-remote.h" #include "config.h" #include "trace2.h" diff --git a/protocol-caps.c b/protocol-caps.c index 855f279c2f..9b8db37a21 100644 --- a/protocol-caps.c +++ b/protocol-caps.c @@ -6,7 +6,7 @@ #include "hash.h" #include "hex.h" #include "object.h" -#include "object-store-ll.h" +#include "object-store.h" #include "repository.h" #include "string-list.h" #include "strbuf.h" diff --git a/prune-packed.c b/prune-packed.c index 7dad2fc0c1..c1d95a519d 100644 --- a/prune-packed.c +++ b/prune-packed.c @@ -2,7 +2,7 @@ #include "git-compat-util.h" #include "gettext.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "progress.h" #include "prune-packed.h" diff --git a/range-diff.c b/range-diff.c index 9501c358a8..8a2dcbee32 100644 --- a/range-diff.c +++ b/range-diff.c @@ -467,7 +467,7 @@ static struct diff_filespec *get_filespec(const char *name, const char *p) { struct diff_filespec *spec = alloc_filespec(name); - fill_filespec(spec, null_oid(), 0, 0100644); + fill_filespec(spec, null_oid(the_hash_algo), 0, 0100644); spec->data = (char *)p; spec->size = strlen(p); spec->should_munmap = 0; diff --git a/reachable.c b/reachable.c index 9ee04c89ec..e5f56f4018 100644 --- a/reachable.c +++ b/reachable.c @@ -14,7 +14,7 @@ #include "list-objects.h" #include "packfile.h" #include "worktree.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pack-bitmap.h" #include "pack-mtimes.h" #include "config.h" @@ -45,7 +45,7 @@ static void add_one_file(const char *path, struct rev_info *revs) } strbuf_trim(&buf); if (!get_oid_hex(buf.buf, &oid)) { - object = parse_object_or_die(&oid, buf.buf); + object = parse_object_or_die(the_repository, &oid, buf.buf); add_pending_object(revs, object, ""); } strbuf_release(&buf); @@ -94,7 +94,7 @@ static int add_one_ref(const char *path, const char *referent UNUSED, const stru return 0; } - object = parse_object_or_die(oid, path); + object = parse_object_or_die(the_repository, oid, path); add_pending_object(revs, object, ""); return 0; @@ -218,7 +218,7 @@ static void add_recent_object(const struct object_id *oid, switch (type) { case OBJ_TAG: case OBJ_COMMIT: - obj = parse_object_or_die(oid, NULL); + obj = parse_object_or_die(the_repository, oid, NULL); break; case OBJ_TREE: obj = (struct object *)lookup_tree(the_repository, oid); @@ -341,7 +341,8 @@ static int mark_object_seen(const struct object_id *oid, int exclude UNUSED, uint32_t name_hash UNUSED, struct packed_git *found_pack UNUSED, - off_t found_offset UNUSED) + off_t found_offset UNUSED, + void *payload UNUSED) { struct object *obj = lookup_object_by_type(the_repository, oid, type); if (!obj) diff --git a/read-cache.c b/read-cache.c index e678c13e8f..73f83a7e7a 100644 --- a/read-cache.c +++ b/read-cache.c @@ -20,7 +20,7 @@ #include "refs.h" #include "dir.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "tree.h" #include "commit.h" @@ -706,11 +706,11 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int intent_only = flags & ADD_CACHE_INTENT; int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE| (intent_only ? ADD_CACHE_NEW_ONLY : 0)); - unsigned hash_flags = pretend ? 0 : HASH_WRITE_OBJECT; + unsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT; struct object_id oid; if (flags & ADD_CACHE_RENORMALIZE) - hash_flags |= HASH_RENORMALIZE; + hash_flags |= INDEX_RENORMALIZE; if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode)) return error(_("%s: can only add regular files, symbolic links or git-directories"), path); @@ -1735,7 +1735,7 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size) end = (unsigned char *)hdr + size; start = end - the_hash_algo->rawsz; oidread(&oid, start, the_repository->hash_algo); - if (oideq(&oid, null_oid())) + if (oideq(&oid, null_oid(the_hash_algo))) return 0; the_hash_algo->init_fn(&c); @@ -2686,8 +2686,8 @@ static int ce_write_entry(struct hashfile *f, struct cache_entry *ce, int common, to_remove, prefix_size; unsigned char to_remove_vi[16]; for (common = 0; - (ce->name[common] && - common < previous_name->len && + (common < previous_name->len && + ce->name[common] && ce->name[common] == previous_name->buf[common]); common++) ; /* still matching */ @@ -2848,7 +2848,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, struct strbuf sb = STRBUF_INIT; int nr, nr_threads, ret; - f = hashfd(tempfile->fd, tempfile->filename.buf); + f = hashfd(the_repository->hash_algo, tempfile->fd, tempfile->filename.buf); prepare_repo_settings(r); f->skip_hash = r->settings.index_skip_hash; diff --git a/ref-filter.c b/ref-filter.c index 6da8d4c03b..7a274633cf 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -12,7 +12,7 @@ #include "refs.h" #include "wildmatch.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "repo-settings.h" #include "repository.h" diff --git a/ref-filter.h b/ref-filter.h index 013d4cfa64..c98c4fbd4c 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -114,11 +114,16 @@ struct ref_format { } /* Macros for checking --merged and --no-merged options */ -#define _OPT_MERGED_NO_MERGED(option, filter, h) \ - { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \ - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \ - parse_opt_merge_filter, (intptr_t) "HEAD" \ - } +#define _OPT_MERGED_NO_MERGED(option, filter, h) { \ + .type = OPTION_CALLBACK, \ + .long_name = option, \ + .value = (filter), \ + .argh = N_("commit"), \ + .help = (h), \ + .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \ + .callback = parse_opt_merge_filter, \ + .defval = (intptr_t) "HEAD", \ +} #define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h) #define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h) @@ -2,13 +2,120 @@ #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" +#include "config.h" #include "gettext.h" -#include "object-store-ll.h" +#include "parse-options.h" +#include "object-store.h" #include "reflog.h" #include "refs.h" #include "revision.h" #include "tree.h" #include "tree-walk.h" +#include "wildmatch.h" + +static struct reflog_expire_entry_option *find_cfg_ent(struct reflog_expire_options *opts, + const char *pattern, size_t len) +{ + struct reflog_expire_entry_option *ent; + + if (!opts->entries_tail) + opts->entries_tail = &opts->entries; + + for (ent = opts->entries; ent; ent = ent->next) + if (!xstrncmpz(ent->pattern, pattern, len)) + return ent; + + FLEX_ALLOC_MEM(ent, pattern, pattern, len); + *opts->entries_tail = ent; + opts->entries_tail = &(ent->next); + return ent; +} + +int reflog_expire_config(const char *var, const char *value, + const struct config_context *ctx, void *cb) +{ + struct reflog_expire_options *opts = cb; + const char *pattern, *key; + size_t pattern_len; + timestamp_t expire; + int slot; + struct reflog_expire_entry_option *ent; + + if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0) + return git_default_config(var, value, ctx, cb); + + if (!strcmp(key, "reflogexpire")) { + slot = REFLOG_EXPIRE_TOTAL; + if (git_config_expiry_date(&expire, var, value)) + return -1; + } else if (!strcmp(key, "reflogexpireunreachable")) { + slot = REFLOG_EXPIRE_UNREACH; + if (git_config_expiry_date(&expire, var, value)) + return -1; + } else + return git_default_config(var, value, ctx, cb); + + if (!pattern) { + switch (slot) { + case REFLOG_EXPIRE_TOTAL: + opts->default_expire_total = expire; + break; + case REFLOG_EXPIRE_UNREACH: + opts->default_expire_unreachable = expire; + break; + } + return 0; + } + + ent = find_cfg_ent(opts, pattern, pattern_len); + if (!ent) + return -1; + switch (slot) { + case REFLOG_EXPIRE_TOTAL: + ent->expire_total = expire; + break; + case REFLOG_EXPIRE_UNREACH: + ent->expire_unreachable = expire; + break; + } + return 0; +} + +void reflog_expire_options_set_refname(struct reflog_expire_options *cb, + const char *ref) +{ + struct reflog_expire_entry_option *ent; + + if (cb->explicit_expiry == (REFLOG_EXPIRE_TOTAL|REFLOG_EXPIRE_UNREACH)) + return; /* both given explicitly -- nothing to tweak */ + + for (ent = cb->entries; ent; ent = ent->next) { + if (!wildmatch(ent->pattern, ref, 0)) { + if (!(cb->explicit_expiry & REFLOG_EXPIRE_TOTAL)) + cb->expire_total = ent->expire_total; + if (!(cb->explicit_expiry & REFLOG_EXPIRE_UNREACH)) + cb->expire_unreachable = ent->expire_unreachable; + return; + } + } + + /* + * If unconfigured, make stash never expire + */ + if (!strcmp(ref, "refs/stash")) { + if (!(cb->explicit_expiry & REFLOG_EXPIRE_TOTAL)) + cb->expire_total = 0; + if (!(cb->explicit_expiry & REFLOG_EXPIRE_UNREACH)) + cb->expire_unreachable = 0; + return; + } + + /* Nothing matched -- use the default value */ + if (!(cb->explicit_expiry & REFLOG_EXPIRE_TOTAL)) + cb->expire_total = cb->default_expire_total; + if (!(cb->explicit_expiry & REFLOG_EXPIRE_UNREACH)) + cb->expire_unreachable = cb->default_expire_unreachable; +} /* Remember to update object flag allocation in object.h */ #define INCOMPLETE (1u<<10) @@ -252,15 +359,15 @@ int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, struct expire_reflog_policy_cb *cb = cb_data; struct commit *old_commit, *new_commit; - if (timestamp < cb->cmd.expire_total) + if (timestamp < cb->opts.expire_total) return 1; old_commit = new_commit = NULL; - if (cb->cmd.stalefix && + if (cb->opts.stalefix && (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid))) return 1; - if (timestamp < cb->cmd.expire_unreachable) { + if (timestamp < cb->opts.expire_unreachable) { switch (cb->unreachable_expire_kind) { case UE_ALWAYS: return 1; @@ -272,7 +379,7 @@ int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, } } - if (cb->cmd.recno && --(cb->cmd.recno) == 0) + if (cb->opts.recno && --(cb->opts.recno) == 0) return 1; return 0; @@ -331,7 +438,7 @@ void reflog_expiry_prepare(const char *refname, struct commit_list *elem; struct commit *commit = NULL; - if (!cb->cmd.expire_unreachable || is_head(refname)) { + if (!cb->opts.expire_unreachable || is_head(refname)) { cb->unreachable_expire_kind = UE_HEAD; } else { commit = lookup_commit_reference_gently(the_repository, @@ -341,7 +448,7 @@ void reflog_expiry_prepare(const char *refname, cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS; } - if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) + if (cb->opts.expire_unreachable <= cb->opts.expire_total) cb->unreachable_expire_kind = UE_ALWAYS; switch (cb->unreachable_expire_kind) { @@ -358,7 +465,7 @@ void reflog_expiry_prepare(const char *refname, /* For reflog_expiry_cleanup() below */ cb->tip_commit = commit; } - cb->mark_limit = cb->cmd.expire_total; + cb->mark_limit = cb->opts.expire_total; mark_reachable(cb); } @@ -390,7 +497,7 @@ int count_reflog_ent(struct object_id *ooid UNUSED, timestamp_t timestamp, int tz UNUSED, const char *message UNUSED, void *cb_data) { - struct cmd_reflog_expire_cb *cb = cb_data; + struct reflog_expire_options *cb = cb_data; if (!cb->expire_total || timestamp < cb->expire_total) cb->recno++; return 0; @@ -398,7 +505,7 @@ int count_reflog_ent(struct object_id *ooid UNUSED, int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose) { - struct cmd_reflog_expire_cb cmd = { 0 }; + struct reflog_expire_options opts = { 0 }; int status = 0; reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; const char *spec = strstr(rev, "@{"); @@ -421,17 +528,17 @@ int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose) recno = strtoul(spec + 2, &ep, 10); if (*ep == '}') { - cmd.recno = -recno; + opts.recno = -recno; refs_for_each_reflog_ent(get_main_ref_store(the_repository), - ref, count_reflog_ent, &cmd); + ref, count_reflog_ent, &opts); } else { - cmd.expire_total = approxidate(spec + 2); + opts.expire_total = approxidate(spec + 2); refs_for_each_reflog_ent(get_main_ref_store(the_repository), - ref, count_reflog_ent, &cmd); - cmd.expire_total = 0; + ref, count_reflog_ent, &opts); + opts.expire_total = 0; } - cb.cmd = cmd; + cb.opts = opts; status |= refs_reflog_expire(get_main_ref_store(the_repository), ref, flags, reflog_expiry_prepare, @@ -2,13 +2,44 @@ #define REFLOG_H #include "refs.h" -struct cmd_reflog_expire_cb { +#define REFLOG_EXPIRE_TOTAL (1 << 0) +#define REFLOG_EXPIRE_UNREACH (1 << 1) + +struct reflog_expire_entry_option { + struct reflog_expire_entry_option *next; + timestamp_t expire_total; + timestamp_t expire_unreachable; + char pattern[FLEX_ARRAY]; +}; + +struct reflog_expire_options { + struct reflog_expire_entry_option *entries, **entries_tail; int stalefix; int explicit_expiry; + timestamp_t default_expire_total; timestamp_t expire_total; + timestamp_t default_expire_unreachable; timestamp_t expire_unreachable; int recno; }; +#define REFLOG_EXPIRE_OPTIONS_INIT(now) { \ + .default_expire_total = now - 30 * 24 * 3600, \ + .default_expire_unreachable = now - 90 * 24 * 3600, \ +} + +/* + * Parse the reflog expire configuration. This should be used with + * `repo_config()`. + */ +int reflog_expire_config(const char *var, const char *value, + const struct config_context *ctx, void *cb); + +/* + * Adapt the options so that they apply to the given refname. This applies any + * per-reference reflog expiry configuration that may exist to the options. + */ +void reflog_expire_options_set_refname(struct reflog_expire_options *cb, + const char *refname); struct expire_reflog_policy_cb { enum { @@ -18,7 +49,7 @@ struct expire_reflog_policy_cb { } unreachable_expire_kind; struct commit_list *mark_list; unsigned long mark_limit; - struct cmd_reflog_expire_cb cmd; + struct reflog_expire_options opts; struct commit *tip_commit; struct commit_list *tips; unsigned int dry_run:1; @@ -19,7 +19,7 @@ #include "run-command.h" #include "hook.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "object.h" #include "path.h" #include "submodule.h" @@ -664,7 +664,8 @@ char *repo_default_branch_name(struct repository *r, int quiet) if (!ret) { ret = xstrdup("master"); if (!quiet) - advise(_(default_branch_name_advice), ret); + advise_if_enabled(ADVICE_DEFAULT_BRANCH_NAME, + _(default_branch_name_advice), ret); } full_ref = xstrfmt("refs/heads/%s", ret); @@ -1175,6 +1176,11 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; tr->flags = flags; + string_list_init_dup(&tr->refnames); + + if (flags & REF_TRANSACTION_ALLOW_FAILURE) + CALLOC_ARRAY(tr->rejections, 1); + return tr; } @@ -1205,10 +1211,45 @@ void ref_transaction_free(struct ref_transaction *transaction) free((char *)transaction->updates[i]->old_target); free(transaction->updates[i]); } + + if (transaction->rejections) + free(transaction->rejections->update_indices); + free(transaction->rejections); + + string_list_clear(&transaction->refnames, 0); free(transaction->updates); free(transaction); } +int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, + size_t update_idx, + enum ref_transaction_error err) +{ + if (update_idx >= transaction->nr) + BUG("trying to set rejection on invalid update index"); + + if (!(transaction->flags & REF_TRANSACTION_ALLOW_FAILURE)) + return 0; + + if (!transaction->rejections) + BUG("transaction not inititalized with failure support"); + + /* + * Don't accept generic errors, since these errors are not user + * input related. + */ + if (err == REF_TRANSACTION_ERROR_GENERIC) + return 0; + + transaction->updates[update_idx]->rejection_err = err; + ALLOC_GROW(transaction->rejections->update_indices, + transaction->rejections->nr + 1, + transaction->rejections->alloc); + transaction->rejections->update_indices[transaction->rejections->nr++] = update_idx; + + return 1; +} + struct ref_update *ref_transaction_add_update( struct ref_transaction *transaction, const char *refname, unsigned int flags, @@ -1218,6 +1259,7 @@ struct ref_update *ref_transaction_add_update( const char *committer_info, const char *msg) { + struct string_list_item *item; struct ref_update *update; if (transaction->state != REF_TRANSACTION_OPEN) @@ -1233,6 +1275,7 @@ struct ref_update *ref_transaction_add_update( transaction->updates[transaction->nr++] = update; update->flags = flags; + update->rejection_err = 0; update->new_target = xstrdup_or_null(new_target); update->old_target = xstrdup_or_null(old_target); @@ -1245,6 +1288,16 @@ struct ref_update *ref_transaction_add_update( update->msg = normalize_reflog_message(msg); } + /* + * This list is generally used by the backends to avoid duplicates. + * But we do support multiple log updates for a given refname within + * a single transaction. + */ + if (!(update->flags & REF_LOG_ONLY)) { + item = string_list_append(&transaction->refnames, refname); + item->util = update; + } + return update; } @@ -1377,7 +1430,7 @@ int ref_transaction_create(struct ref_transaction *transaction, return 1; } return ref_transaction_update(transaction, refname, new_oid, - null_oid(), new_target, NULL, flags, + null_oid(the_hash_algo), new_target, NULL, flags, msg, err); } @@ -1396,7 +1449,7 @@ int ref_transaction_delete(struct ref_transaction *transaction, if (old_target && !(flags & REF_NO_DEREF)) BUG("delete cannot operate on symrefs with deref mode"); return ref_transaction_update(transaction, refname, - null_oid(), old_oid, + null_oid(the_hash_algo), old_oid, NULL, old_target, flags, msg, err); } @@ -2180,7 +2233,7 @@ struct ref_store *repo_get_submodule_ref_store(struct repository *repo, subrepo = xmalloc(sizeof(*subrepo)); if (repo_submodule_init(subrepo, repo, submodule, - null_oid())) { + null_oid(the_hash_algo))) { free(subrepo); goto done; } @@ -2278,7 +2331,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref, REF_NO_DEREF, logmsg, &err)) goto error_return; prepret = ref_transaction_prepare(transaction, &err); - if (prepret && prepret != TRANSACTION_CREATE_EXISTS) + if (prepret && prepret != REF_TRANSACTION_ERROR_CREATE_EXISTS) goto error_return; } else { if (ref_transaction_update(transaction, ref, NULL, NULL, @@ -2296,7 +2349,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref, } } - if (prepret == TRANSACTION_CREATE_EXISTS) + if (prepret == REF_TRANSACTION_ERROR_CREATE_EXISTS) goto cleanup; if (ref_transaction_commit(transaction, &err)) @@ -2310,8 +2363,13 @@ cleanup: return ret; } -int ref_update_reject_duplicates(struct string_list *refnames, - struct strbuf *err) +/* + * Write an error to `err` and return a nonzero value iff the same + * refname appears multiple times in `refnames`. `refnames` must be + * sorted on entry to this function. + */ +static int ref_update_reject_duplicates(struct string_list *refnames, + struct strbuf *err) { size_t i, n = refnames->nr; @@ -2365,14 +2423,14 @@ static int run_transaction_hook(struct ref_transaction *transaction, strbuf_reset(&buf); if (!(update->flags & REF_HAVE_OLD)) - strbuf_addf(&buf, "%s ", oid_to_hex(null_oid())); + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); else if (update->old_target) strbuf_addf(&buf, "ref:%s ", update->old_target); else strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid)); if (!(update->flags & REF_HAVE_NEW)) - strbuf_addf(&buf, "%s ", oid_to_hex(null_oid())); + strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); else if (update->new_target) strbuf_addf(&buf, "ref:%s ", update->new_target); else @@ -2425,6 +2483,10 @@ int ref_transaction_prepare(struct ref_transaction *transaction, return -1; } + string_list_sort(&transaction->refnames); + if (ref_update_reject_duplicates(&transaction->refnames, err)) + return REF_TRANSACTION_ERROR_GENERIC; + ret = refs->be->transaction_prepare(refs, transaction, err); if (ret) return ret; @@ -2495,19 +2557,21 @@ int ref_transaction_commit(struct ref_transaction *transaction, return ret; } -int refs_verify_refnames_available(struct ref_store *refs, - const struct string_list *refnames, - const struct string_list *extras, - const struct string_list *skip, - unsigned int initial_transaction, - struct strbuf *err) +enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs, + const struct string_list *refnames, + const struct string_list *extras, + const struct string_list *skip, + struct ref_transaction *transaction, + unsigned int initial_transaction, + struct strbuf *err) { struct strbuf dirname = STRBUF_INIT; struct strbuf referent = STRBUF_INIT; struct string_list_item *item; struct ref_iterator *iter = NULL; + struct strset conflicting_dirnames; struct strset dirnames; - int ret = -1; + int ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; /* * For the sake of comments in this function, suppose that @@ -2516,9 +2580,11 @@ int refs_verify_refnames_available(struct ref_store *refs, assert(err); + strset_init(&conflicting_dirnames); strset_init(&dirnames); for_each_string_list_item(item, refnames) { + const size_t *update_idx = (size_t *)item->util; const char *refname = item->string; const char *extra_refname; struct object_id oid; @@ -2549,21 +2615,37 @@ int refs_verify_refnames_available(struct ref_store *refs, /* * If we've already seen the directory we don't need to - * process it again. Skip it to avoid checking checking - * common prefixes like "refs/heads/" repeatedly. + * process it again. Skip it to avoid checking common + * prefixes like "refs/heads/" repeatedly. */ if (!strset_add(&dirnames, dirname.buf)) continue; if (!initial_transaction && - !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, - &type, &ignore_errno)) { + (strset_contains(&conflicting_dirnames, dirname.buf) || + !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, + &type, &ignore_errno))) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) { + strset_remove(&dirnames, dirname.buf); + strset_add(&conflicting_dirnames, dirname.buf); + continue; + } + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), dirname.buf, refname); goto cleanup; } if (extras && string_list_has_string(extras, dirname.buf)) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) { + strset_remove(&dirnames, dirname.buf); + continue; + } + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), refname, dirname.buf); goto cleanup; @@ -2596,6 +2678,11 @@ int refs_verify_refnames_available(struct ref_store *refs, string_list_has_string(skip, iter->refname)) continue; + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) + continue; + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), iter->refname, refname); goto cleanup; @@ -2607,6 +2694,11 @@ int refs_verify_refnames_available(struct ref_store *refs, extra_refname = find_descendant_ref(dirname.buf, extras, skip); if (extra_refname) { + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT)) + continue; + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), refname, extra_refname); goto cleanup; @@ -2618,17 +2710,19 @@ int refs_verify_refnames_available(struct ref_store *refs, cleanup: strbuf_release(&referent); strbuf_release(&dirname); + strset_clear(&conflicting_dirnames); strset_clear(&dirnames); ref_iterator_free(iter); return ret; } -int refs_verify_refname_available(struct ref_store *refs, - const char *refname, - const struct string_list *extras, - const struct string_list *skip, - unsigned int initial_transaction, - struct strbuf *err) +enum ref_transaction_error refs_verify_refname_available( + struct ref_store *refs, + const char *refname, + const struct string_list *extras, + const struct string_list *skip, + unsigned int initial_transaction, + struct strbuf *err) { struct string_list_item item = { .string = (char *) refname }; struct string_list refnames = { @@ -2637,7 +2731,7 @@ int refs_verify_refname_available(struct ref_store *refs, }; return refs_verify_refnames_available(refs, &refnames, extras, skip, - initial_transaction, err); + NULL, initial_transaction, err); } struct do_for_each_reflog_help { @@ -2725,6 +2819,28 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, } } +void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction, + ref_transaction_for_each_rejected_update_fn cb, + void *cb_data) +{ + if (!transaction->rejections) + return; + + for (size_t i = 0; i < transaction->rejections->nr; i++) { + size_t update_index = transaction->rejections->update_indices[i]; + struct ref_update *update = transaction->updates[update_index]; + + if (!update->rejection_err) + continue; + + cb(update->refname, + (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL, + (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL, + update->old_target, update->new_target, + update->rejection_err, cb_data); + } +} + int refs_delete_refs(struct ref_store *refs, const char *logmsg, struct string_list *refnames, unsigned int flags) { @@ -2816,8 +2932,9 @@ int ref_update_has_null_new_value(struct ref_update *update) return !update->new_target && is_null_oid(&update->new_oid); } -int ref_update_check_old_target(const char *referent, struct ref_update *update, - struct strbuf *err) +enum ref_transaction_error ref_update_check_old_target(const char *referent, + struct ref_update *update, + struct strbuf *err) { if (!update->old_target) BUG("called without old_target set"); @@ -2825,17 +2942,18 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update, if (!strcmp(referent, update->old_target)) return 0; - if (!strcmp(referent, "")) + if (!strcmp(referent, "")) { strbuf_addf(err, "verifying symref target: '%s': " "reference is missing but expected %s", ref_update_original_update_refname(update), update->old_target); - else - strbuf_addf(err, "verifying symref target: '%s': " - "is at %s but expected %s", + return REF_TRANSACTION_ERROR_NONEXISTENT_REF; + } + + strbuf_addf(err, "verifying symref target: '%s': is at %s but expected %s", ref_update_original_update_refname(update), referent, update->old_target); - return -1; + return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; } struct migration_data { @@ -2857,7 +2975,7 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con if (ret < 0) goto done; - ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(), + ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo), symref_target.buf, NULL, REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf); if (ret < 0) @@ -16,6 +16,23 @@ struct worktree; enum ref_storage_format ref_storage_format_by_name(const char *name); const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format); +enum ref_transaction_error { + /* Default error code */ + REF_TRANSACTION_ERROR_GENERIC = -1, + /* Ref name conflict like A vs A/B */ + REF_TRANSACTION_ERROR_NAME_CONFLICT = -2, + /* Ref to be created already exists */ + REF_TRANSACTION_ERROR_CREATE_EXISTS = -3, + /* ref expected but doesn't exist */ + REF_TRANSACTION_ERROR_NONEXISTENT_REF = -4, + /* Provided old_oid or old_target of reference doesn't match actual */ + REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -5, + /* Provided new_oid or new_target is invalid */ + REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -6, + /* Expected ref to be symref, but is a regular ref */ + REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -7, +}; + /* * Resolve a reference, recursively following symbolic references. * @@ -117,24 +134,12 @@ int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname, * * extras and skip must be sorted. */ -int refs_verify_refname_available(struct ref_store *refs, - const char *refname, - const struct string_list *extras, - const struct string_list *skip, - unsigned int initial_transaction, - struct strbuf *err); - -/* - * Same as `refs_verify_refname_available()`, but checking for a list of - * refnames instead of only a single item. This is more efficient in the case - * where one needs to check multiple refnames. - */ -int refs_verify_refnames_available(struct ref_store *refs, - const struct string_list *refnames, - const struct string_list *extras, - const struct string_list *skip, - unsigned int initial_transaction, - struct strbuf *err); +enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, + const char *refname, + const struct string_list *extras, + const struct string_list *skip, + unsigned int initial_transaction, + struct strbuf *err); int refs_ref_exists(struct ref_store *refs, const char *refname); @@ -650,6 +655,13 @@ enum ref_transaction_flag { * either be absent or null_oid. */ REF_TRANSACTION_FLAG_INITIAL = (1 << 0), + + /* + * The transaction mechanism by default fails all updates if any conflict + * is detected. This flag allows transactions to partially apply updates + * while rejecting updates which do not match the expected state. + */ + REF_TRANSACTION_ALLOW_FAILURE = (1 << 1), }; /* @@ -830,13 +842,6 @@ int ref_transaction_verify(struct ref_transaction *transaction, unsigned int flags, struct strbuf *err); -/* Naming conflict (for example, the ref names A and A/B conflict). */ -#define TRANSACTION_NAME_CONFLICT -1 -/* When only creation was requested, but the ref already exists. */ -#define TRANSACTION_CREATE_EXISTS -2 -/* All other errors. */ -#define TRANSACTION_GENERIC_ERROR -3 - /* * Perform the preparatory stages of committing `transaction`. Acquire * any needed locks, check preconditions, etc.; basically, do as much @@ -888,6 +893,21 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction, void *cb_data); /* + * Execute the given callback function for each of the reference updates which + * have been rejected in the given transaction. + */ +typedef void ref_transaction_for_each_rejected_update_fn(const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, + const char *old_target, + const char *new_target, + enum ref_transaction_error err, + void *cb_data); +void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction, + ref_transaction_for_each_rejected_update_fn cb, + void *cb_data); + +/* * Free `*transaction` and all associated data. */ void ref_transaction_free(struct ref_transaction *transaction); diff --git a/refs/debug.c b/refs/debug.c index 5390fa9c18..485e3079d7 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -227,7 +227,7 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname, struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; int res = 0; - oidcpy(oid, null_oid()); + oidcpy(oid, null_oid(ref_store->repo->hash_algo)); res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent, type, failure_errno); diff --git a/refs/files-backend.c b/refs/files-backend.c index ff54a4bb7e..4d1f65a57a 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -663,7 +663,7 @@ static void unlock_ref(struct ref_lock *lock) * broken, lock the reference anyway but clear old_oid. * * Return 0 on success. On failure, write an error message to err and - * return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR. + * return REF_TRANSACTION_ERROR_NAME_CONFLICT or REF_TRANSACTION_ERROR_GENERIC. * * Implementation note: This function is basically * @@ -676,19 +676,22 @@ static void unlock_ref(struct ref_lock *lock) * avoided, namely if we were successfully able to read the ref * - Generate informative error messages in the case of failure */ -static int lock_raw_ref(struct files_ref_store *refs, - const char *refname, int mustexist, - struct string_list *refnames_to_check, - const struct string_list *extras, - struct ref_lock **lock_p, - struct strbuf *referent, - unsigned int *type, - struct strbuf *err) -{ +static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, + struct ref_update *update, + size_t update_idx, + int mustexist, + struct string_list *refnames_to_check, + const struct string_list *extras, + struct ref_lock **lock_p, + struct strbuf *referent, + struct strbuf *err) +{ + enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC; + const char *refname = update->refname; + unsigned int *type = &update->type; struct ref_lock *lock; struct strbuf ref_file = STRBUF_INIT; int attempts_remaining = 3; - int ret = TRANSACTION_GENERIC_ERROR; int failure_errno; assert(err); @@ -705,7 +708,7 @@ static int lock_raw_ref(struct files_ref_store *refs, files_ref_path(refs, &ref_file, refname); retry: - switch (safe_create_leading_directories(ref_file.buf)) { + switch (safe_create_leading_directories(the_repository, ref_file.buf)) { case SCLD_OK: break; /* success */ case SCLD_EXISTS: @@ -728,13 +731,14 @@ retry: strbuf_reset(err); strbuf_addf(err, "unable to resolve reference '%s'", refname); + ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF; } else { /* * The error message set by * refs_verify_refname_available() is * OK. */ - ret = TRANSACTION_NAME_CONFLICT; + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; } } else { /* @@ -783,11 +787,14 @@ retry: if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent, type, &failure_errno)) { + struct string_list_item *item; + if (failure_errno == ENOENT) { if (mustexist) { /* Garden variety missing reference. */ strbuf_addf(err, "unable to resolve reference '%s'", refname); + ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF; goto error_return; } else { /* @@ -820,6 +827,7 @@ retry: /* Garden variety missing reference. */ strbuf_addf(err, "unable to resolve reference '%s'", refname); + ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF; goto error_return; } else if (remove_dir_recursively(&ref_file, REMOVE_DIR_EMPTY_ONLY)) { @@ -830,7 +838,7 @@ retry: * The error message set by * verify_refname_available() is OK. */ - ret = TRANSACTION_NAME_CONFLICT; + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; goto error_return; } else { /* @@ -860,7 +868,9 @@ retry: * make sure there is no existing packed ref that conflicts * with refname. This check is deferred so that we can batch it. */ - string_list_append(refnames_to_check, refname); + item = string_list_append(refnames_to_check, refname); + item->util = xmalloc(sizeof(update_idx)); + memcpy(item->util, &update_idx, sizeof(update_idx)); } ret = 0; @@ -1109,7 +1119,7 @@ retry_fn: strbuf_addstr(&path_copy, path); do { - scld_result = safe_create_leading_directories(path_copy.buf); + scld_result = safe_create_leading_directories(the_repository, path_copy.buf); if (scld_result == SCLD_OK) goto retry_fn; } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); @@ -1265,7 +1275,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) ref_transaction_add_update( transaction, r->name, REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING, - null_oid(), &r->oid, NULL, NULL, NULL, NULL); + null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1517,10 +1527,11 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname) return ret; } -static int write_ref_to_lockfile(struct files_ref_store *refs, - struct ref_lock *lock, - const struct object_id *oid, - int skip_oid_verification, struct strbuf *err); +static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, + struct ref_lock *lock, + const struct object_id *oid, + int skip_oid_verification, + struct strbuf *err); static int commit_ref_update(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, const char *logmsg, @@ -1926,10 +1937,11 @@ static int files_log_ref_write(struct files_ref_store *refs, * Write oid into the open lockfile, then close the lockfile. On * errors, rollback the lockfile, fill in *err and return -1. */ -static int write_ref_to_lockfile(struct files_ref_store *refs, - struct ref_lock *lock, - const struct object_id *oid, - int skip_oid_verification, struct strbuf *err) +static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs, + struct ref_lock *lock, + const struct object_id *oid, + int skip_oid_verification, + struct strbuf *err) { static char term = '\n'; struct object *o; @@ -1943,7 +1955,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs, "trying to write ref '%s' with nonexistent object %s", lock->ref_name, oid_to_hex(oid)); unlock_ref(lock); - return -1; + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; } if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { strbuf_addf( @@ -1951,7 +1963,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs, "trying to write non-commit object %s to branch '%s'", oid_to_hex(oid), lock->ref_name); unlock_ref(lock); - return -1; + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; } } fd = get_lock_file_fd(&lock->lk); @@ -1962,7 +1974,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs, strbuf_addf(err, "couldn't write '%s'", get_lock_file_path(&lock->lk)); unlock_ref(lock); - return -1; + return REF_TRANSACTION_ERROR_GENERIC; } return 0; } @@ -2376,13 +2388,11 @@ static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_st * If update is a direct update of head_ref (the reference pointed to * by HEAD), then add an extra REF_LOG_ONLY update for HEAD. */ -static int split_head_update(struct ref_update *update, - struct ref_transaction *transaction, - const char *head_ref, - struct string_list *affected_refnames, - struct strbuf *err) +static enum ref_transaction_error split_head_update(struct ref_update *update, + struct ref_transaction *transaction, + const char *head_ref, + struct strbuf *err) { - struct string_list_item *item; struct ref_update *new_update; if ((update->flags & REF_LOG_ONLY) || @@ -2399,13 +2409,13 @@ static int split_head_update(struct ref_update *update, * transaction. This check is O(lg N) in the transaction * size, but it happens at most once per transaction. */ - if (string_list_has_string(affected_refnames, "HEAD")) { + if (string_list_has_string(&transaction->refnames, "HEAD")) { /* An entry already existed */ strbuf_addf(err, "multiple updates for 'HEAD' (including one " "via its referent '%s') are not allowed", update->refname); - return TRANSACTION_NAME_CONFLICT; + return REF_TRANSACTION_ERROR_NAME_CONFLICT; } new_update = ref_transaction_add_update( @@ -2421,8 +2431,6 @@ static int split_head_update(struct ref_update *update, */ if (strcmp(new_update->refname, "HEAD")) BUG("%s unexpectedly not 'HEAD'", new_update->refname); - item = string_list_insert(affected_refnames, new_update->refname); - item->util = new_update; return 0; } @@ -2435,13 +2443,11 @@ static int split_head_update(struct ref_update *update, * Note that the new update will itself be subject to splitting when * the iteration gets to it. */ -static int split_symref_update(struct ref_update *update, - const char *referent, - struct ref_transaction *transaction, - struct string_list *affected_refnames, - struct strbuf *err) +static enum ref_transaction_error split_symref_update(struct ref_update *update, + const char *referent, + struct ref_transaction *transaction, + struct strbuf *err) { - struct string_list_item *item; struct ref_update *new_update; unsigned int new_flags; @@ -2451,13 +2457,13 @@ static int split_symref_update(struct ref_update *update, * size, but it happens at most once per symref in a * transaction. */ - if (string_list_has_string(affected_refnames, referent)) { + if (string_list_has_string(&transaction->refnames, referent)) { /* An entry already exists */ strbuf_addf(err, "multiple updates for '%s' (including one " "via symref '%s') are not allowed", referent, update->refname); - return TRANSACTION_NAME_CONFLICT; + return REF_TRANSACTION_ERROR_NAME_CONFLICT; } new_flags = update->flags; @@ -2489,19 +2495,6 @@ static int split_symref_update(struct ref_update *update, update->flags |= REF_LOG_ONLY | REF_NO_DEREF; update->flags &= ~REF_HAVE_OLD; - /* - * Add the referent. This insertion is O(N) in the transaction - * size, but it happens at most once per symref in a - * transaction. Make sure to add new_update->refname, which will - * be valid as long as affected_refnames is in use, and NOT - * referent, which might soon be freed by our caller. - */ - item = string_list_insert(affected_refnames, new_update->refname); - if (item->util) - BUG("%s unexpectedly found in affected_refnames", - new_update->refname); - item->util = new_update; - return 0; } @@ -2511,11 +2504,10 @@ static int split_symref_update(struct ref_update *update, * everything is OK, return 0; otherwise, write an error message to * err and return -1. */ -static int check_old_oid(struct ref_update *update, struct object_id *oid, - struct strbuf *err) +static enum ref_transaction_error check_old_oid(struct ref_update *update, + struct object_id *oid, + struct strbuf *err) { - int ret = TRANSACTION_GENERIC_ERROR; - if (!(update->flags & REF_HAVE_OLD) || oideq(oid, &update->old_oid)) return 0; @@ -2524,21 +2516,20 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid, strbuf_addf(err, "cannot lock ref '%s': " "reference already exists", ref_update_original_update_refname(update)); - ret = TRANSACTION_CREATE_EXISTS; - } - else if (is_null_oid(oid)) + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } else if (is_null_oid(oid)) { strbuf_addf(err, "cannot lock ref '%s': " "reference is missing but expected %s", ref_update_original_update_refname(update), oid_to_hex(&update->old_oid)); - else - strbuf_addf(err, "cannot lock ref '%s': " - "is at %s but expected %s", - ref_update_original_update_refname(update), - oid_to_hex(oid), - oid_to_hex(&update->old_oid)); + return REF_TRANSACTION_ERROR_NONEXISTENT_REF; + } - return ret; + strbuf_addf(err, "cannot lock ref '%s': is at %s but expected %s", + ref_update_original_update_refname(update), oid_to_hex(oid), + oid_to_hex(&update->old_oid)); + + return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; } struct files_transaction_backend_data { @@ -2560,18 +2551,18 @@ struct files_transaction_backend_data { * - If it is an update of head_ref, add a corresponding REF_LOG_ONLY * update of HEAD. */ -static int lock_ref_for_update(struct files_ref_store *refs, - struct ref_update *update, - struct ref_transaction *transaction, - const char *head_ref, - struct string_list *refnames_to_check, - struct string_list *affected_refnames, - struct strbuf *err) +static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *refs, + struct ref_update *update, + size_t update_idx, + struct ref_transaction *transaction, + const char *head_ref, + struct string_list *refnames_to_check, + struct strbuf *err) { struct strbuf referent = STRBUF_INIT; int mustexist = ref_update_expects_existing_old_ref(update); struct files_transaction_backend_data *backend_data; - int ret = 0; + enum ref_transaction_error ret = 0; struct ref_lock *lock; files_assert_main_repository(refs, "lock_ref_for_update"); @@ -2582,8 +2573,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, update->flags |= REF_DELETING; if (head_ref) { - ret = split_head_update(update, transaction, head_ref, - affected_refnames, err); + ret = split_head_update(update, transaction, head_ref, err); if (ret) goto out; } @@ -2592,10 +2582,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, if (lock) { lock->count++; } else { - ret = lock_raw_ref(refs, update->refname, mustexist, - refnames_to_check, affected_refnames, - &lock, &referent, - &update->type, err); + ret = lock_raw_ref(refs, update, update_idx, mustexist, + refnames_to_check, &transaction->refnames, + &lock, &referent, err); if (ret) { char *reason; @@ -2625,22 +2614,17 @@ static int lock_ref_for_update(struct files_ref_store *refs, strbuf_addf(err, "cannot lock ref '%s': " "error reading reference", ref_update_original_update_refname(update)); - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto out; } } - if (update->old_target) { - if (ref_update_check_old_target(referent.buf, update, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto out; - } - } else { + if (update->old_target) + ret = ref_update_check_old_target(referent.buf, update, err); + else ret = check_old_oid(update, &lock->old_oid, err); - if (ret) { - goto out; - } - } + if (ret) + goto out; } else { /* * Create a new update for the reference this @@ -2649,9 +2633,8 @@ static int lock_ref_for_update(struct files_ref_store *refs, * of processing the split-off update, so we * don't have to do it here. */ - ret = split_symref_update(update, - referent.buf, transaction, - affected_refnames, err); + ret = split_symref_update(update, referent.buf, + transaction, err); if (ret) goto out; } @@ -2668,7 +2651,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, "but is a regular ref"), ref_update_original_update_refname(update), update->old_target); - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF; goto out; } else { ret = check_old_oid(update, &lock->old_oid, err); @@ -2692,14 +2675,14 @@ static int lock_ref_for_update(struct files_ref_store *refs, if (update->new_target && !(update->flags & REF_LOG_ONLY)) { if (create_symref_lock(lock, update->new_target, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto out; } if (close_ref_gently(lock)) { strbuf_addf(err, "couldn't close '%s.lock'", update->refname); - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto out; } @@ -2717,25 +2700,27 @@ static int lock_ref_for_update(struct files_ref_store *refs, * The reference already has the desired * value, so we don't need to write it. */ - } else if (write_ref_to_lockfile( - refs, lock, &update->new_oid, - update->flags & REF_SKIP_OID_VERIFICATION, - err)) { - char *write_err = strbuf_detach(err, NULL); - - /* - * The lock was freed upon failure of - * write_ref_to_lockfile(): - */ - update->backend_data = NULL; - strbuf_addf(err, - "cannot update ref '%s': %s", - update->refname, write_err); - free(write_err); - ret = TRANSACTION_GENERIC_ERROR; - goto out; } else { - update->flags |= REF_NEEDS_COMMIT; + ret = write_ref_to_lockfile( + refs, lock, &update->new_oid, + update->flags & REF_SKIP_OID_VERIFICATION, + err); + if (ret) { + char *write_err = strbuf_detach(err, NULL); + + /* + * The lock was freed upon failure of + * write_ref_to_lockfile(): + */ + update->backend_data = NULL; + strbuf_addf(err, + "cannot update ref '%s': %s", + update->refname, write_err); + free(write_err); + goto out; + } else { + update->flags |= REF_NEEDS_COMMIT; + } } } if (!(update->flags & REF_NEEDS_COMMIT)) { @@ -2747,7 +2732,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, if (close_ref_gently(lock)) { strbuf_addf(err, "couldn't close '%s.lock'", update->refname); - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto out; } } @@ -2806,7 +2791,6 @@ static int files_transaction_prepare(struct ref_store *ref_store, "ref_transaction_prepare"); size_t i; int ret = 0; - struct string_list affected_refnames = STRING_LIST_INIT_NODUP; struct string_list refnames_to_check = STRING_LIST_INIT_NODUP; char *head_ref = NULL; int head_type; @@ -2825,36 +2809,14 @@ static int files_transaction_prepare(struct ref_store *ref_store, transaction->backend_data = backend_data; /* - * Fail if a refname appears more than once in the - * transaction. (If we end up splitting up any updates using - * split_symref_update() or split_head_update(), those - * functions will check that the new updates don't have the - * same refname as any existing ones.) Also fail if any of the - * updates use REF_IS_PRUNING without REF_NO_DEREF. + * Fail if any of the updates use REF_IS_PRUNING without REF_NO_DEREF. */ for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - struct string_list_item *item; if ((update->flags & REF_IS_PRUNING) && !(update->flags & REF_NO_DEREF)) BUG("REF_IS_PRUNING set without REF_NO_DEREF"); - - if (update->flags & REF_LOG_ONLY) - continue; - - item = string_list_append(&affected_refnames, update->refname); - /* - * We store a pointer to update in item->util, but at - * the moment we never use the value of this field - * except to check whether it is non-NULL. - */ - item->util = update; - } - string_list_sort(&affected_refnames); - if (ref_update_reject_duplicates(&affected_refnames, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; } /* @@ -2894,11 +2856,18 @@ static int files_transaction_prepare(struct ref_store *ref_store, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - ret = lock_ref_for_update(refs, update, transaction, + ret = lock_ref_for_update(refs, update, i, transaction, head_ref, &refnames_to_check, - &affected_refnames, err); - if (ret) + err); + if (ret) { + if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { + strbuf_reset(err); + ret = 0; + + continue; + } goto cleanup; + } if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY) && @@ -2912,7 +2881,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, refs->packed_ref_store, transaction->flags, err); if (!packed_transaction) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } @@ -2943,14 +2912,15 @@ static int files_transaction_prepare(struct ref_store *ref_store, * So instead, we accept the race for now. */ if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check, - &affected_refnames, NULL, 0, err)) { - ret = TRANSACTION_NAME_CONFLICT; + &transaction->refnames, NULL, transaction, + 0, err)) { + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; goto cleanup; } if (packed_transaction) { if (packed_refs_lock(refs->packed_ref_store, 0, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } backend_data->packed_refs_locked = 1; @@ -2981,7 +2951,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ backend_data->packed_transaction = NULL; if (ref_transaction_abort(packed_transaction, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } @@ -2989,8 +2959,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, cleanup: free(head_ref); - string_list_clear(&affected_refnames, 0); - string_list_clear(&refnames_to_check, 0); + string_list_clear(&refnames_to_check, 1); if (ret) files_transaction_cleanup(refs, transaction); @@ -3064,17 +3033,6 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, if (transaction->state != REF_TRANSACTION_PREPARED) BUG("commit called for transaction that is not prepared"); - /* Fail if a refname appears more than once in the transaction: */ - for (i = 0; i < transaction->nr; i++) - if (!(transaction->updates[i]->flags & REF_LOG_ONLY)) - string_list_append(&affected_refnames, - transaction->updates[i]->refname); - string_list_sort(&affected_refnames); - if (ref_update_reject_duplicates(&affected_refnames, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - /* * It's really undefined to call this function in an active * repository or when there are existing references: we are @@ -3088,13 +3046,13 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, * that we are creating already exists. */ if (refs_for_each_rawref(&refs->base, ref_present, - &affected_refnames)) + &transaction->refnames)) BUG("initial ref transaction called with existing refs"); packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, transaction->flags, err); if (!packed_transaction) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } @@ -3117,7 +3075,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, if (!loose_transaction) { loose_transaction = ref_store_transaction_begin(&refs->base, 0, err); if (!loose_transaction) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } @@ -3142,19 +3100,20 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, } if (packed_refs_lock(refs->packed_ref_store, 0, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } if (refs_verify_refnames_available(&refs->base, &refnames_to_check, - &affected_refnames, NULL, 1, err)) { + &affected_refnames, NULL, transaction, + 1, err)) { packed_refs_unlock(refs->packed_ref_store); - ret = TRANSACTION_NAME_CONFLICT; + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; goto cleanup; } if (ref_transaction_commit(packed_transaction, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } packed_refs_unlock(refs->packed_ref_store); @@ -3162,7 +3121,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, if (loose_transaction) { if (ref_transaction_prepare(loose_transaction, err) || ref_transaction_commit(loose_transaction, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } @@ -3208,10 +3167,13 @@ static int files_transaction_finish(struct ref_store *ref_store, struct ref_update *update = transaction->updates[i]; struct ref_lock *lock = update->backend_data; + if (update->rejection_err) + continue; + if (update->flags & REF_NEEDS_COMMIT || update->flags & REF_LOG_ONLY) { if (parse_and_write_reflog(refs, update, lock, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } @@ -3230,7 +3192,7 @@ static int files_transaction_finish(struct ref_store *ref_store, strbuf_addf(err, "couldn't set '%s'", lock->ref_name); unlock_ref(lock); update->backend_data = NULL; - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } @@ -3286,7 +3248,7 @@ static int files_transaction_finish(struct ref_store *ref_store, strbuf_reset(&sb); files_ref_path(refs, &sb, lock->ref_name); if (unlink_or_msg(sb.buf, err)) { - ret = TRANSACTION_GENERIC_ERROR; + ret = REF_TRANSACTION_ERROR_GENERIC; goto cleanup; } } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index b4289a7d9c..3ad1ed0787 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -980,9 +980,9 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) continue; while (prefix && *prefix) { - if (*refname < *prefix) + if ((unsigned char)*refname < (unsigned char)*prefix) BUG("packed-refs backend yielded reference preceding its prefix"); - else if (*refname > *prefix) + else if ((unsigned char)*refname > (unsigned char)*prefix) return ITER_DONE; prefix++; refname++; @@ -1351,10 +1351,12 @@ static int packed_ref_store_remove_on_disk(struct ref_store *ref_store, * The packfile must be locked before calling this function and will * remain locked when it is done. */ -static int write_with_updates(struct packed_ref_store *refs, - struct string_list *updates, - struct strbuf *err) +static enum ref_transaction_error write_with_updates(struct packed_ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) { + enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC; + struct string_list *updates = &transaction->refnames; struct ref_iterator *iter = NULL; size_t i; int ok; @@ -1378,7 +1380,7 @@ static int write_with_updates(struct packed_ref_store *refs, strbuf_addf(err, "unable to create file %s: %s", sb.buf, strerror(errno)); strbuf_release(&sb); - return -1; + return REF_TRANSACTION_ERROR_GENERIC; } strbuf_release(&sb); @@ -1434,6 +1436,14 @@ static int write_with_updates(struct packed_ref_store *refs, strbuf_addf(err, "cannot update ref '%s': " "reference already exists", update->refname); + ret = REF_TRANSACTION_ERROR_CREATE_EXISTS; + + if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { + strbuf_reset(err); + ret = 0; + continue; + } + goto error; } else if (!oideq(&update->old_oid, iter->oid)) { strbuf_addf(err, "cannot update ref '%s': " @@ -1441,6 +1451,14 @@ static int write_with_updates(struct packed_ref_store *refs, update->refname, oid_to_hex(iter->oid), oid_to_hex(&update->old_oid)); + ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; + + if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { + strbuf_reset(err); + ret = 0; + continue; + } + goto error; } } @@ -1477,6 +1495,14 @@ static int write_with_updates(struct packed_ref_store *refs, "reference is missing but expected %s", update->refname, oid_to_hex(&update->old_oid)); + ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF; + + if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { + strbuf_reset(err); + ret = 0; + continue; + } + goto error; } } @@ -1534,7 +1560,7 @@ static int write_with_updates(struct packed_ref_store *refs, strerror(errno)); strbuf_release(&sb); delete_tempfile(&refs->tempfile); - return -1; + return REF_TRANSACTION_ERROR_GENERIC; } return 0; @@ -1542,11 +1568,12 @@ static int write_with_updates(struct packed_ref_store *refs, write_error: strbuf_addf(err, "error writing to %s: %s", get_tempfile_path(refs->tempfile), strerror(errno)); + ret = REF_TRANSACTION_ERROR_GENERIC; error: ref_iterator_free(iter); delete_tempfile(&refs->tempfile); - return -1; + return ret; } int is_packed_transaction_needed(struct ref_store *ref_store, @@ -1647,8 +1674,6 @@ int is_packed_transaction_needed(struct ref_store *ref_store, struct packed_transaction_backend_data { /* True iff the transaction owns the packed-refs lock. */ int own_lock; - - struct string_list updates; }; static void packed_transaction_cleanup(struct packed_ref_store *refs, @@ -1657,8 +1682,6 @@ static void packed_transaction_cleanup(struct packed_ref_store *refs, struct packed_transaction_backend_data *data = transaction->backend_data; if (data) { - string_list_clear(&data->updates, 0); - if (is_tempfile_active(refs->tempfile)) delete_tempfile(&refs->tempfile); @@ -1683,8 +1706,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store, REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB, "ref_transaction_prepare"); struct packed_transaction_backend_data *data; - size_t i; - int ret = TRANSACTION_GENERIC_ERROR; + enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC; /* * Note that we *don't* skip transactions with zero updates, @@ -1696,34 +1718,17 @@ static int packed_transaction_prepare(struct ref_store *ref_store, */ CALLOC_ARRAY(data, 1); - string_list_init_nodup(&data->updates); transaction->backend_data = data; - /* - * Stick the updates in a string list by refname so that we - * can sort them: - */ - for (i = 0; i < transaction->nr; i++) { - struct ref_update *update = transaction->updates[i]; - struct string_list_item *item = - string_list_append(&data->updates, update->refname); - - /* Store a pointer to update in item->util: */ - item->util = update; - } - string_list_sort(&data->updates); - - if (ref_update_reject_duplicates(&data->updates, err)) - goto failure; - if (!is_lock_file_locked(&refs->lock)) { if (packed_refs_lock(ref_store, 0, err)) goto failure; data->own_lock = 1; } - if (write_with_updates(refs, &data->updates, err)) + ret = write_with_updates(refs, transaction, err); + if (ret) goto failure; transaction->state = REF_TRANSACTION_PREPARED; @@ -1755,7 +1760,7 @@ static int packed_transaction_finish(struct ref_store *ref_store, ref_store, REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB, "ref_transaction_finish"); - int ret = TRANSACTION_GENERIC_ERROR; + int ret = REF_TRANSACTION_ERROR_GENERIC; char *packed_refs_path; clear_snapshot(refs); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index e5862757a7..f868870851 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -3,6 +3,7 @@ #include "refs.h" #include "iterator.h" +#include "string-list.h" struct fsck_options; struct ref_transaction; @@ -123,6 +124,12 @@ struct ref_update { uint64_t index; /* + * Used in batched reference updates to mark if a given update + * was rejected. + */ + enum ref_transaction_error rejection_err; + + /* * If this ref_update was split off of a symref update via * split_symref_update(), then this member points at that * update. This is used for two purposes: @@ -142,12 +149,11 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, unsigned int *type, int *failure_errno); /* - * Write an error to `err` and return a nonzero value iff the same - * refname appears multiple times in `refnames`. `refnames` must be - * sorted on entry to this function. + * Mark a given update as rejected with a given reason. */ -int ref_update_reject_duplicates(struct string_list *refnames, - struct strbuf *err); +int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, + size_t update_idx, + enum ref_transaction_error err); /* * Add a ref_update with the specified properties to transaction, and @@ -191,6 +197,18 @@ enum ref_transaction_state { }; /* + * Data structure to hold indices of updates which were rejected, for batched + * reference updates. While the updates themselves hold the rejection error, + * this structure allows a transaction to iterate only over the rejected + * updates. + */ +struct ref_transaction_rejections { + size_t *update_indices; + size_t alloc; + size_t nr; +}; + +/* * Data structure for holding a reference transaction, which can * consist of checks and updates to multiple references, carried out * as atomically as possible. This structure is opaque to callers. @@ -198,9 +216,11 @@ enum ref_transaction_state { struct ref_transaction { struct ref_store *ref_store; struct ref_update **updates; + struct string_list refnames; size_t alloc; size_t nr; enum ref_transaction_state state; + struct ref_transaction_rejections *rejections; void *backend_data; unsigned int flags; uint64_t max_index; @@ -776,8 +796,9 @@ int ref_update_has_null_new_value(struct ref_update *update); * If everything is OK, return 0; otherwise, write an error message to * err and return -1. */ -int ref_update_check_old_target(const char *referent, struct ref_update *update, - struct strbuf *err); +enum ref_transaction_error ref_update_check_old_target(const char *referent, + struct ref_update *update, + struct strbuf *err); /* * Check if the ref must exist, this means that the old_oid or @@ -785,4 +806,20 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update, */ int ref_update_expects_existing_old_ref(struct ref_update *update); +/* + * Same as `refs_verify_refname_available()`, but checking for a list of + * refnames instead of only a single item. This is more efficient in the case + * where one needs to check multiple refnames. + * + * If using batched updates, then individual updates are marked rejected, + * reference backends are then in charge of not committing those updates. + */ +enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs, + const struct string_list *refnames, + const struct string_list *extras, + const struct string_list *skip, + struct ref_transaction *transaction, + unsigned int initial_transaction, + struct strbuf *err); + #endif /* REFS_REFS_INTERNAL_H */ diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index ae434cd248..4c3817f4ec 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1069,6 +1069,244 @@ static int queue_transaction_update(struct reftable_ref_store *refs, return 0; } +static enum ref_transaction_error prepare_single_update(struct reftable_ref_store *refs, + struct reftable_transaction_data *tx_data, + struct ref_transaction *transaction, + struct reftable_backend *be, + struct ref_update *u, + size_t update_idx, + struct string_list *refnames_to_check, + unsigned int head_type, + struct strbuf *head_referent, + struct strbuf *referent, + struct strbuf *err) +{ + enum ref_transaction_error ret = 0; + struct object_id current_oid = {0}; + const char *rewritten_ref; + + /* + * There is no need to reload the respective backends here as + * we have already reloaded them when preparing the transaction + * update. And given that the stacks have been locked there + * shouldn't have been any concurrent modifications of the + * stack. + */ + ret = backend_for(&be, refs, u->refname, &rewritten_ref, 0); + if (ret) + return REF_TRANSACTION_ERROR_GENERIC; + + /* Verify that the new object ID is valid. */ + if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && + !(u->flags & REF_SKIP_OID_VERIFICATION) && + !(u->flags & REF_LOG_ONLY)) { + struct object *o = parse_object(refs->base.repo, &u->new_oid); + if (!o) { + strbuf_addf(err, + _("trying to write ref '%s' with nonexistent object %s"), + u->refname, oid_to_hex(&u->new_oid)); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + + if (o->type != OBJ_COMMIT && is_branch(u->refname)) { + strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), + oid_to_hex(&u->new_oid), u->refname); + return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; + } + } + + /* + * When we update the reference that HEAD points to we enqueue + * a second log-only update for HEAD so that its reflog is + * updated accordingly. + */ + if (head_type == REF_ISSYMREF && + !(u->flags & REF_LOG_ONLY) && + !(u->flags & REF_UPDATE_VIA_HEAD) && + !strcmp(rewritten_ref, head_referent->buf)) { + /* + * First make sure that HEAD is not already in the + * transaction. This check is O(lg N) in the transaction + * size, but it happens at most once per transaction. + */ + if (string_list_has_string(&transaction->refnames, "HEAD")) { + /* An entry already existed */ + strbuf_addf(err, + _("multiple updates for 'HEAD' (including one " + "via its referent '%s') are not allowed"), + u->refname); + return REF_TRANSACTION_ERROR_NAME_CONFLICT; + } + + ref_transaction_add_update( + transaction, "HEAD", + u->flags | REF_LOG_ONLY | REF_NO_DEREF, + &u->new_oid, &u->old_oid, NULL, NULL, NULL, + u->msg); + } + + ret = reftable_backend_read_ref(be, rewritten_ref, + ¤t_oid, referent, &u->type); + if (ret < 0) + return REF_TRANSACTION_ERROR_GENERIC; + if (ret > 0 && !ref_update_expects_existing_old_ref(u)) { + struct string_list_item *item; + /* + * The reference does not exist, and we either have no + * old object ID or expect the reference to not exist. + * We can thus skip below safety checks as well as the + * symref splitting. But we do want to verify that + * there is no conflicting reference here so that we + * can output a proper error message instead of failing + * at a later point. + */ + item = string_list_append(refnames_to_check, u->refname); + item->util = xmalloc(sizeof(update_idx)); + memcpy(item->util, &update_idx, sizeof(update_idx)); + + /* + * There is no need to write the reference deletion + * when the reference in question doesn't exist. + */ + if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) { + ret = queue_transaction_update(refs, tx_data, u, + ¤t_oid, err); + if (ret) + return REF_TRANSACTION_ERROR_GENERIC; + } + + return 0; + } + if (ret > 0) { + /* The reference does not exist, but we expected it to. */ + strbuf_addf(err, _("cannot lock ref '%s': " + + + "unable to resolve reference '%s'"), + ref_update_original_update_refname(u), u->refname); + return REF_TRANSACTION_ERROR_NONEXISTENT_REF; + } + + if (u->type & REF_ISSYMREF) { + /* + * The reftable stack is locked at this point already, + * so it is safe to call `refs_resolve_ref_unsafe()` + * here without causing races. + */ + const char *resolved = refs_resolve_ref_unsafe(&refs->base, u->refname, 0, + ¤t_oid, NULL); + + if (u->flags & REF_NO_DEREF) { + if (u->flags & REF_HAVE_OLD && !resolved) { + strbuf_addf(err, _("cannot lock ref '%s': " + "error reading reference"), u->refname); + return REF_TRANSACTION_ERROR_GENERIC; + } + } else { + struct ref_update *new_update; + int new_flags; + + new_flags = u->flags; + if (!strcmp(rewritten_ref, "HEAD")) + new_flags |= REF_UPDATE_VIA_HEAD; + + if (string_list_has_string(&transaction->refnames, referent->buf)) { + strbuf_addf(err, + _("multiple updates for '%s' (including one " + "via symref '%s') are not allowed"), + referent->buf, u->refname); + return REF_TRANSACTION_ERROR_NAME_CONFLICT; + } + + /* + * If we are updating a symref (eg. HEAD), we should also + * update the branch that the symref points to. + * + * This is generic functionality, and would be better + * done in refs.c, but the current implementation is + * intertwined with the locking in files-backend.c. + */ + new_update = ref_transaction_add_update( + transaction, referent->buf, new_flags, + u->new_target ? NULL : &u->new_oid, + u->old_target ? NULL : &u->old_oid, + u->new_target, u->old_target, + u->committer_info, u->msg); + + new_update->parent_update = u; + + /* + * Change the symbolic ref update to log only. Also, it + * doesn't need to check its old OID value, as that will be + * done when new_update is processed. + */ + u->flags |= REF_LOG_ONLY | REF_NO_DEREF; + u->flags &= ~REF_HAVE_OLD; + } + } + + /* + * Verify that the old object matches our expectations. Note + * that the error messages here do not make a lot of sense in + * the context of the reftable backend as we never lock + * individual refs. But the error messages match what the files + * backend returns, which keeps our tests happy. + */ + if (u->old_target) { + if (!(u->type & REF_ISSYMREF)) { + strbuf_addf(err, _("cannot lock ref '%s': " + "expected symref with target '%s': " + "but is a regular ref"), + ref_update_original_update_refname(u), + u->old_target); + return REF_TRANSACTION_ERROR_EXPECTED_SYMREF; + } + + ret = ref_update_check_old_target(referent->buf, u, err); + if (ret) + return ret; + } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { + if (is_null_oid(&u->old_oid)) { + strbuf_addf(err, _("cannot lock ref '%s': " + "reference already exists"), + ref_update_original_update_refname(u)); + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } else if (is_null_oid(¤t_oid)) { + strbuf_addf(err, _("cannot lock ref '%s': " + "reference is missing but expected %s"), + ref_update_original_update_refname(u), + oid_to_hex(&u->old_oid)); + return REF_TRANSACTION_ERROR_NONEXISTENT_REF; + } else { + strbuf_addf(err, _("cannot lock ref '%s': " + "is at %s but expected %s"), + ref_update_original_update_refname(u), + oid_to_hex(¤t_oid), + oid_to_hex(&u->old_oid)); + return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; + } + } + + /* + * If all of the following conditions are true: + * + * - We're not about to write a symref. + * - We're not about to write a log-only entry. + * - Old and new object ID are different. + * + * Then we're essentially doing a no-op update that can be + * skipped. This is not only for the sake of efficiency, but + * also skips writing unneeded reflog entries. + */ + if ((u->type & REF_ISSYMREF) || + (u->flags & REF_LOG_ONLY) || + (u->flags & REF_HAVE_NEW && !oideq(¤t_oid, &u->new_oid))) + if (queue_transaction_update(refs, tx_data, u, ¤t_oid, err)) + return REF_TRANSACTION_ERROR_GENERIC; + + return 0; +} + static int reftable_be_transaction_prepare(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) @@ -1076,7 +1314,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare"); struct strbuf referent = STRBUF_INIT, head_referent = STRBUF_INIT; - struct string_list affected_refnames = STRING_LIST_INIT_NODUP; struct string_list refnames_to_check = STRING_LIST_INIT_NODUP; struct reftable_transaction_data *tx_data = NULL; struct reftable_backend *be; @@ -1101,10 +1338,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, transaction->updates[i], err); if (ret) goto done; - - if (!(transaction->updates[i]->flags & REF_LOG_ONLY)) - string_list_append(&affected_refnames, - transaction->updates[i]->refname); } /* @@ -1117,17 +1350,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, } /* - * Fail if a refname appears more than once in the transaction. - * This code is taken from the files backend and is a good candidate to - * be moved into the generic layer. - */ - string_list_sort(&affected_refnames); - if (ref_update_reject_duplicates(&affected_refnames, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto done; - } - - /* * TODO: it's dubious whether we should reload the stack that "HEAD" * belongs to or not. In theory, it may happen that we only modify * stacks which are _not_ part of the "HEAD" stack. In that case we @@ -1149,241 +1371,24 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, ret = 0; for (i = 0; i < transaction->nr; i++) { - struct ref_update *u = transaction->updates[i]; - struct object_id current_oid = {0}; - const char *rewritten_ref; - - /* - * There is no need to reload the respective backends here as - * we have already reloaded them when preparing the transaction - * update. And given that the stacks have been locked there - * shouldn't have been any concurrent modifications of the - * stack. - */ - ret = backend_for(&be, refs, u->refname, &rewritten_ref, 0); - if (ret) - goto done; - - /* Verify that the new object ID is valid. */ - if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && - !(u->flags & REF_SKIP_OID_VERIFICATION) && - !(u->flags & REF_LOG_ONLY)) { - struct object *o = parse_object(refs->base.repo, &u->new_oid); - if (!o) { - strbuf_addf(err, - _("trying to write ref '%s' with nonexistent object %s"), - u->refname, oid_to_hex(&u->new_oid)); - ret = -1; - goto done; - } - - if (o->type != OBJ_COMMIT && is_branch(u->refname)) { - strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), - oid_to_hex(&u->new_oid), u->refname); - ret = -1; - goto done; - } - } - - /* - * When we update the reference that HEAD points to we enqueue - * a second log-only update for HEAD so that its reflog is - * updated accordingly. - */ - if (head_type == REF_ISSYMREF && - !(u->flags & REF_LOG_ONLY) && - !(u->flags & REF_UPDATE_VIA_HEAD) && - !strcmp(rewritten_ref, head_referent.buf)) { - struct ref_update *new_update; - - /* - * First make sure that HEAD is not already in the - * transaction. This check is O(lg N) in the transaction - * size, but it happens at most once per transaction. - */ - if (string_list_has_string(&affected_refnames, "HEAD")) { - /* An entry already existed */ - strbuf_addf(err, - _("multiple updates for 'HEAD' (including one " - "via its referent '%s') are not allowed"), - u->refname); - ret = TRANSACTION_NAME_CONFLICT; - goto done; - } - - new_update = ref_transaction_add_update( - transaction, "HEAD", - u->flags | REF_LOG_ONLY | REF_NO_DEREF, - &u->new_oid, &u->old_oid, NULL, NULL, NULL, - u->msg); - string_list_insert(&affected_refnames, new_update->refname); - } - - ret = reftable_backend_read_ref(be, rewritten_ref, - ¤t_oid, &referent, &u->type); - if (ret < 0) - goto done; - if (ret > 0 && !ref_update_expects_existing_old_ref(u)) { - /* - * The reference does not exist, and we either have no - * old object ID or expect the reference to not exist. - * We can thus skip below safety checks as well as the - * symref splitting. But we do want to verify that - * there is no conflicting reference here so that we - * can output a proper error message instead of failing - * at a later point. - */ - string_list_append(&refnames_to_check, u->refname); - - /* - * There is no need to write the reference deletion - * when the reference in question doesn't exist. - */ - if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) { - ret = queue_transaction_update(refs, tx_data, u, - ¤t_oid, err); - if (ret) - goto done; - } - - continue; - } - if (ret > 0) { - /* The reference does not exist, but we expected it to. */ - strbuf_addf(err, _("cannot lock ref '%s': " - "unable to resolve reference '%s'"), - ref_update_original_update_refname(u), u->refname); - ret = -1; - goto done; - } - - if (u->type & REF_ISSYMREF) { - /* - * The reftable stack is locked at this point already, - * so it is safe to call `refs_resolve_ref_unsafe()` - * here without causing races. - */ - const char *resolved = refs_resolve_ref_unsafe(&refs->base, u->refname, 0, - ¤t_oid, NULL); - - if (u->flags & REF_NO_DEREF) { - if (u->flags & REF_HAVE_OLD && !resolved) { - strbuf_addf(err, _("cannot lock ref '%s': " - "error reading reference"), u->refname); - ret = -1; - goto done; - } - } else { - struct ref_update *new_update; - int new_flags; - - new_flags = u->flags; - if (!strcmp(rewritten_ref, "HEAD")) - new_flags |= REF_UPDATE_VIA_HEAD; - - /* - * If we are updating a symref (eg. HEAD), we should also - * update the branch that the symref points to. - * - * This is generic functionality, and would be better - * done in refs.c, but the current implementation is - * intertwined with the locking in files-backend.c. - */ - new_update = ref_transaction_add_update( - transaction, referent.buf, new_flags, - u->new_target ? NULL : &u->new_oid, - u->old_target ? NULL : &u->old_oid, - u->new_target, u->old_target, - u->committer_info, u->msg); - - new_update->parent_update = u; - - /* - * Change the symbolic ref update to log only. Also, it - * doesn't need to check its old OID value, as that will be - * done when new_update is processed. - */ - u->flags |= REF_LOG_ONLY | REF_NO_DEREF; - u->flags &= ~REF_HAVE_OLD; - - if (string_list_has_string(&affected_refnames, new_update->refname)) { - strbuf_addf(err, - _("multiple updates for '%s' (including one " - "via symref '%s') are not allowed"), - referent.buf, u->refname); - ret = TRANSACTION_NAME_CONFLICT; - goto done; - } - string_list_insert(&affected_refnames, new_update->refname); - } - } - - /* - * Verify that the old object matches our expectations. Note - * that the error messages here do not make a lot of sense in - * the context of the reftable backend as we never lock - * individual refs. But the error messages match what the files - * backend returns, which keeps our tests happy. - */ - if (u->old_target) { - if (!(u->type & REF_ISSYMREF)) { - strbuf_addf(err, _("cannot lock ref '%s': " - "expected symref with target '%s': " - "but is a regular ref"), - ref_update_original_update_refname(u), - u->old_target); - ret = -1; - goto done; - } + ret = prepare_single_update(refs, tx_data, transaction, be, + transaction->updates[i], i, + &refnames_to_check, head_type, + &head_referent, &referent, err); + if (ret) { + if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { + strbuf_reset(err); + ret = 0; - if (ref_update_check_old_target(referent.buf, u, err)) { - ret = -1; - goto done; - } - } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { - ret = TRANSACTION_NAME_CONFLICT; - if (is_null_oid(&u->old_oid)) { - strbuf_addf(err, _("cannot lock ref '%s': " - "reference already exists"), - ref_update_original_update_refname(u)); - ret = TRANSACTION_CREATE_EXISTS; + continue; } - else if (is_null_oid(¤t_oid)) - strbuf_addf(err, _("cannot lock ref '%s': " - "reference is missing but expected %s"), - ref_update_original_update_refname(u), - oid_to_hex(&u->old_oid)); - else - strbuf_addf(err, _("cannot lock ref '%s': " - "is at %s but expected %s"), - ref_update_original_update_refname(u), - oid_to_hex(¤t_oid), - oid_to_hex(&u->old_oid)); goto done; } - - /* - * If all of the following conditions are true: - * - * - We're not about to write a symref. - * - We're not about to write a log-only entry. - * - Old and new object ID are different. - * - * Then we're essentially doing a no-op update that can be - * skipped. This is not only for the sake of efficiency, but - * also skips writing unneeded reflog entries. - */ - if ((u->type & REF_ISSYMREF) || - (u->flags & REF_LOG_ONLY) || - (u->flags & REF_HAVE_NEW && !oideq(¤t_oid, &u->new_oid))) { - ret = queue_transaction_update(refs, tx_data, u, - ¤t_oid, err); - if (ret) - goto done; - } } - ret = refs_verify_refnames_available(ref_store, &refnames_to_check, &affected_refnames, NULL, + ret = refs_verify_refnames_available(ref_store, &refnames_to_check, + &transaction->refnames, NULL, + transaction, transaction->flags & REF_TRANSACTION_FLAG_INITIAL, err); if (ret < 0) @@ -1393,7 +1398,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, transaction->state = REF_TRANSACTION_PREPARED; done: - assert(ret != REFTABLE_API_ERROR); if (ret < 0) { free_transaction_data(tx_data); transaction->state = REF_TRANSACTION_CLOSED; @@ -1401,10 +1405,9 @@ done: strbuf_addf(err, _("reftable: transaction prepare: %s"), reftable_error_str(ret)); } - string_list_clear(&affected_refnames, 0); strbuf_release(&referent); strbuf_release(&head_referent); - string_list_clear(&refnames_to_check, 0); + string_list_clear(&refnames_to_check, 1); return ret; } @@ -1463,6 +1466,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data struct reftable_transaction_update *tx_update = &arg->updates[i]; struct ref_update *u = tx_update->update; + if (u->rejection_err) + continue; + /* * Write a reflog entry when updating a ref to point to * something new in either of the following cases: diff --git a/reftable/basics.c b/reftable/basics.c index 8c4a4433e4..9988ebd635 100644 --- a/reftable/basics.c +++ b/reftable/basics.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #define REFTABLE_ALLOW_BANNED_ALLOCATORS #include "basics.h" diff --git a/reftable/basics.h b/reftable/basics.h index fd59cbb772..d8888c1262 100644 --- a/reftable/basics.h +++ b/reftable/basics.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef BASICS_H #define BASICS_H @@ -18,13 +18,6 @@ https://developers.google.com/open-source/licenses/bsd #define REFTABLE_UNUSED __attribute__((__unused__)) -struct reftable_buf { - size_t alloc; - size_t len; - char *buf; -}; -#define REFTABLE_BUF_INIT { 0 } - /* * Initialize the buffer such that it is ready for use. This is equivalent to * using REFTABLE_BUF_INIT for stack-allocated variables. diff --git a/reftable/block.c b/reftable/block.c index 251a8e9fd3..471faa1642 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -1,15 +1,16 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "block.h" #include "blocksource.h" #include "constants.h" +#include "iter.h" #include "record.h" #include "reftable-error.h" #include "system.h" @@ -160,7 +161,7 @@ int block_writer_finish(struct block_writer *w) * Log records are stored zlib-compressed. Note that the compression * also spans over the restart points we have just written. */ - if (block_writer_type(w) == BLOCK_TYPE_LOG) { + if (block_writer_type(w) == REFTABLE_BLOCK_TYPE_LOG) { int block_header_skip = 4 + w->header_off; uLongf src_len = w->next - block_header_skip, compressed_len; int ret; @@ -210,61 +211,86 @@ int block_writer_finish(struct block_writer *w) return w->next; } -int block_reader_init(struct block_reader *br, struct reftable_block *block, - uint32_t header_off, uint32_t table_block_size, - uint32_t hash_size) +static int read_block(struct reftable_block_source *source, + struct reftable_block_data *dest, uint64_t off, + uint32_t sz) +{ + size_t size = block_source_size(source); + block_source_release_data(dest); + if (off >= size) + return 0; + if (off + sz > size) + sz = size - off; + return block_source_read_data(source, dest, off, sz); +} + +int reftable_block_init(struct reftable_block *block, + struct reftable_block_source *source, + uint32_t offset, uint32_t header_size, + uint32_t table_block_size, uint32_t hash_size) { + uint32_t guess_block_size = table_block_size ? + table_block_size : DEFAULT_BLOCK_SIZE; uint32_t full_block_size = table_block_size; - uint8_t typ = block->data[header_off]; - uint32_t sz = reftable_get_be24(block->data + header_off + 1); - int err = 0; - uint16_t restart_count = 0; - uint32_t restart_start = 0; - uint8_t *restart_bytes = NULL; + uint16_t restart_count; + uint32_t restart_off; + uint32_t block_size; + uint8_t block_type; + int err; - reftable_block_done(&br->block); + err = read_block(source, &block->block_data, offset, guess_block_size); + if (err < 0) + goto done; - if (!reftable_is_block_type(typ)) { - err = REFTABLE_FORMAT_ERROR; + block_type = block->block_data.data[header_size]; + if (!reftable_is_block_type(block_type)) { + err = REFTABLE_FORMAT_ERROR; goto done; } - if (typ == BLOCK_TYPE_LOG) { - uint32_t block_header_skip = 4 + header_off; - uLong dst_len = sz - block_header_skip; - uLong src_len = block->len - block_header_skip; + block_size = reftable_get_be24(block->block_data.data + header_size + 1); + if (block_size > guess_block_size) { + err = read_block(source, &block->block_data, offset, block_size); + if (err < 0) + goto done; + } + + if (block_type == REFTABLE_BLOCK_TYPE_LOG) { + uint32_t block_header_skip = 4 + header_size; + uLong dst_len = block_size - block_header_skip; + uLong src_len = block->block_data.len - block_header_skip; /* Log blocks specify the *uncompressed* size in their header. */ - REFTABLE_ALLOC_GROW_OR_NULL(br->uncompressed_data, sz, - br->uncompressed_cap); - if (!br->uncompressed_data) { + REFTABLE_ALLOC_GROW_OR_NULL(block->uncompressed_data, block_size, + block->uncompressed_cap); + if (!block->uncompressed_data) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } /* Copy over the block header verbatim. It's not compressed. */ - memcpy(br->uncompressed_data, block->data, block_header_skip); + memcpy(block->uncompressed_data, block->block_data.data, block_header_skip); - if (!br->zstream) { - REFTABLE_CALLOC_ARRAY(br->zstream, 1); - if (!br->zstream) { + if (!block->zstream) { + REFTABLE_CALLOC_ARRAY(block->zstream, 1); + if (!block->zstream) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } - err = inflateInit(br->zstream); + err = inflateInit(block->zstream); } else { - err = inflateReset(br->zstream); + err = inflateReset(block->zstream); } if (err != Z_OK) { err = REFTABLE_ZLIB_ERROR; goto done; } - br->zstream->next_in = block->data + block_header_skip; - br->zstream->avail_in = src_len; - br->zstream->next_out = br->uncompressed_data + block_header_skip; - br->zstream->avail_out = dst_len; + block->zstream->next_in = block->block_data.data + block_header_skip; + block->zstream->avail_in = src_len; + block->zstream->next_out = block->uncompressed_data + block_header_skip; + block->zstream->avail_out = dst_len; /* * We know both input as well as output size, and we know that @@ -273,72 +299,71 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, * here to instruct zlib to inflate the data in one go, which * is more efficient than using `Z_NO_FLUSH`. */ - err = inflate(br->zstream, Z_FINISH); + err = inflate(block->zstream, Z_FINISH); if (err != Z_STREAM_END) { err = REFTABLE_ZLIB_ERROR; goto done; } err = 0; - if (br->zstream->total_out + block_header_skip != sz) { + if (block->zstream->total_out + block_header_skip != block_size) { err = REFTABLE_FORMAT_ERROR; goto done; } /* We're done with the input data. */ - reftable_block_done(block); - block->data = br->uncompressed_data; - block->len = sz; - full_block_size = src_len + block_header_skip - br->zstream->avail_in; + block_source_release_data(&block->block_data); + block->block_data.data = block->uncompressed_data; + block->block_data.len = block_size; + full_block_size = src_len + block_header_skip - block->zstream->avail_in; } else if (full_block_size == 0) { - full_block_size = sz; - } else if (sz < full_block_size && sz < block->len && - block->data[sz] != 0) { + full_block_size = block_size; + } else if (block_size < full_block_size && block_size < block->block_data.len && + block->block_data.data[block_size] != 0) { /* If the block is smaller than the full block size, it is padded (data followed by '\0') or the next block is unaligned. */ - full_block_size = sz; + full_block_size = block_size; } - restart_count = reftable_get_be16(block->data + sz - 2); - restart_start = sz - 2 - 3 * restart_count; - restart_bytes = block->data + restart_start; + restart_count = reftable_get_be16(block->block_data.data + block_size - 2); + restart_off = block_size - 2 - 3 * restart_count; - /* transfer ownership. */ - br->block = *block; - block->data = NULL; - block->len = 0; + block->block_type = block_type; + block->hash_size = hash_size; + block->restart_off = restart_off; + block->full_block_size = full_block_size; + block->header_off = header_size; + block->restart_count = restart_count; - br->hash_size = hash_size; - br->block_len = restart_start; - br->full_block_size = full_block_size; - br->header_off = header_off; - br->restart_count = restart_count; - br->restart_bytes = restart_bytes; + err = 0; done: + if (err < 0) + reftable_block_release(block); return err; } -void block_reader_release(struct block_reader *br) +void reftable_block_release(struct reftable_block *block) { - inflateEnd(br->zstream); - reftable_free(br->zstream); - reftable_free(br->uncompressed_data); - reftable_block_done(&br->block); + inflateEnd(block->zstream); + reftable_free(block->zstream); + reftable_free(block->uncompressed_data); + block_source_release_data(&block->block_data); + memset(block, 0, sizeof(*block)); } -uint8_t block_reader_type(const struct block_reader *r) +uint8_t reftable_block_type(const struct reftable_block *b) { - return r->block.data[r->header_off]; + return b->block_data.data[b->header_off]; } -int block_reader_first_key(const struct block_reader *br, struct reftable_buf *key) +int reftable_block_first_key(const struct reftable_block *block, struct reftable_buf *key) { - int off = br->header_off + 4, n; + int off = block->header_off + 4, n; struct string_view in = { - .buf = br->block.data + off, - .len = br->block_len - off, + .buf = block->block_data.data + off, + .len = block->restart_off - off, }; uint8_t extra = 0; @@ -353,33 +378,36 @@ int block_reader_first_key(const struct block_reader *br, struct reftable_buf *k return 0; } -static uint32_t block_reader_restart_offset(const struct block_reader *br, size_t idx) +static uint32_t block_restart_offset(const struct reftable_block *b, size_t idx) +{ + return reftable_get_be24(b->block_data.data + b->restart_off + 3 * idx); +} + +void block_iter_init(struct block_iter *it, const struct reftable_block *block) { - return reftable_get_be24(br->restart_bytes + 3 * idx); + it->block = block; + block_iter_seek_start(it); } -void block_iter_seek_start(struct block_iter *it, const struct block_reader *br) +void block_iter_seek_start(struct block_iter *it) { - it->block = br->block.data; - it->block_len = br->block_len; - it->hash_size = br->hash_size; reftable_buf_reset(&it->last_key); - it->next_off = br->header_off + 4; + it->next_off = it->block->header_off + 4; } struct restart_needle_less_args { int error; struct reftable_buf needle; - const struct block_reader *reader; + const struct reftable_block *block; }; static int restart_needle_less(size_t idx, void *_args) { struct restart_needle_less_args *args = _args; - uint32_t off = block_reader_restart_offset(args->reader, idx); + uint32_t off = block_restart_offset(args->block, idx); struct string_view in = { - .buf = args->reader->block.data + off, - .len = args->reader->block_len - off, + .buf = args->block->block_data.data + off, + .len = args->block->restart_off - off, }; uint64_t prefix_len, suffix_len; uint8_t extra; @@ -412,14 +440,14 @@ static int restart_needle_less(size_t idx, void *_args) int block_iter_next(struct block_iter *it, struct reftable_record *rec) { struct string_view in = { - .buf = (unsigned char *) it->block + it->next_off, - .len = it->block_len - it->next_off, + .buf = (unsigned char *) it->block->block_data.data + it->next_off, + .len = it->block->restart_off - it->next_off, }; struct string_view start = in; uint8_t extra = 0; int n = 0; - if (it->next_off >= it->block_len) + if (it->next_off >= it->block->restart_off) return 1; n = reftable_decode_key(&it->last_key, &extra, in); @@ -429,7 +457,7 @@ int block_iter_next(struct block_iter *it, struct reftable_record *rec) return REFTABLE_FORMAT_ERROR; string_view_consume(&in, n); - n = reftable_record_decode(rec, it->last_key, extra, in, it->hash_size, + n = reftable_record_decode(rec, it->last_key, extra, in, it->block->hash_size, &it->scratch); if (n < 0) return -1; @@ -444,8 +472,6 @@ void block_iter_reset(struct block_iter *it) reftable_buf_reset(&it->last_key); it->next_off = 0; it->block = NULL; - it->block_len = 0; - it->hash_size = 0; } void block_iter_close(struct block_iter *it) @@ -454,12 +480,11 @@ void block_iter_close(struct block_iter *it) reftable_buf_release(&it->scratch); } -int block_iter_seek_key(struct block_iter *it, const struct block_reader *br, - struct reftable_buf *want) +int block_iter_seek_key(struct block_iter *it, struct reftable_buf *want) { struct restart_needle_less_args args = { .needle = *want, - .reader = br, + .block = it->block, }; struct reftable_record rec; int err = 0; @@ -477,7 +502,7 @@ int block_iter_seek_key(struct block_iter *it, const struct block_reader *br, * restart point. While that works alright, we would end up scanning * too many record. */ - i = binsearch(br->restart_count, &restart_needle_less, &args); + i = binsearch(it->block->restart_count, &restart_needle_less, &args); if (args.error) { err = REFTABLE_FORMAT_ERROR; goto done; @@ -502,21 +527,18 @@ int block_iter_seek_key(struct block_iter *it, const struct block_reader *br, * starting from the preceding restart point. */ if (i > 0) - it->next_off = block_reader_restart_offset(br, i - 1); + it->next_off = block_restart_offset(it->block, i - 1); else - it->next_off = br->header_off + 4; - it->block = br->block.data; - it->block_len = br->block_len; - it->hash_size = br->hash_size; + it->next_off = it->block->header_off + 4; - err = reftable_record_init(&rec, block_reader_type(br)); + err = reftable_record_init(&rec, reftable_block_type(it->block)); if (err < 0) goto done; /* * We're looking for the last entry less than the wanted key so that * the next call to `block_reader_next()` would yield the wanted - * record. We thus don't want to position our reader at the sought + * record. We thus don't want to position our iterator at the sought * after record, but one before. To do so, we have to go one entry too * far and then back up. */ @@ -561,6 +583,61 @@ done: return err; } +static int block_iter_seek_void(void *it, struct reftable_record *want) +{ + struct reftable_buf buf = REFTABLE_BUF_INIT; + struct block_iter *bi = it; + int err; + + if (bi->block->block_type != want->type) + return REFTABLE_API_ERROR; + + err = reftable_record_key(want, &buf); + if (err < 0) + goto out; + + err = block_iter_seek_key(it, &buf); + if (err < 0) + goto out; + + err = 0; + +out: + reftable_buf_release(&buf); + return err; +} + +static int block_iter_next_void(void *it, struct reftable_record *rec) +{ + return block_iter_next(it, rec); +} + +static void block_iter_close_void(void *it) +{ + block_iter_close(it); +} + +static struct reftable_iterator_vtable block_iter_vtable = { + .seek = &block_iter_seek_void, + .next = &block_iter_next_void, + .close = &block_iter_close_void, +}; + +int reftable_block_init_iterator(const struct reftable_block *b, + struct reftable_iterator *it) +{ + struct block_iter *bi; + + REFTABLE_CALLOC_ARRAY(bi, 1); + block_iter_init(bi, b); + + assert(!it->ops); + it->iter_arg = bi; + it->ops = &block_iter_vtable; + + return 0; +} + void block_writer_release(struct block_writer *bw) { deflateEnd(bw->zstream); @@ -571,14 +648,3 @@ void block_writer_release(struct block_writer *bw) reftable_buf_release(&bw->last_key); /* the block is not owned. */ } - -void reftable_block_done(struct reftable_block *blockp) -{ - struct reftable_block_source source = blockp->source; - if (blockp && source.ops) - source.ops->return_block(source.arg, blockp); - blockp->data = NULL; - blockp->len = 0; - blockp->source.ops = NULL; - blockp->source.arg = NULL; -} diff --git a/reftable/block.h b/reftable/block.h index 64732eba7d..d6dfaae33e 100644 --- a/reftable/block.h +++ b/reftable/block.h @@ -1,16 +1,17 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef BLOCK_H #define BLOCK_H #include "basics.h" #include "record.h" +#include "reftable-block.h" #include "reftable-blocksource.h" /* @@ -18,7 +19,7 @@ https://developers.google.com/open-source/licenses/bsd * allocation overhead. */ struct block_writer { - z_stream *zstream; + struct z_stream_s *zstream; unsigned char *compressed; size_t compressed_cap; @@ -62,53 +63,11 @@ int block_writer_finish(struct block_writer *w); /* clears out internally allocated block_writer members. */ void block_writer_release(struct block_writer *bw); -struct z_stream; - -/* Read a block. */ -struct block_reader { - /* offset of the block header; nonzero for the first block in a - * reftable. */ - uint32_t header_off; - - /* the memory block */ - struct reftable_block block; - uint32_t hash_size; - - /* Uncompressed data for log entries. */ - z_stream *zstream; - unsigned char *uncompressed_data; - size_t uncompressed_cap; - - /* size of the data, excluding restart data. */ - uint32_t block_len; - uint8_t *restart_bytes; - uint16_t restart_count; - - /* size of the data in the file. For log blocks, this is the compressed - * size. */ - uint32_t full_block_size; -}; - -/* initializes a block reader. */ -int block_reader_init(struct block_reader *br, struct reftable_block *bl, - uint32_t header_off, uint32_t table_block_size, - uint32_t hash_size); - -void block_reader_release(struct block_reader *br); - -/* Returns the block type (eg. 'r' for refs) */ -uint8_t block_reader_type(const struct block_reader *r); - -/* Decodes the first key in the block */ -int block_reader_first_key(const struct block_reader *br, struct reftable_buf *key); - -/* Iterate over entries in a block */ +/* Iterator for records contained in a single block. */ struct block_iter { /* offset within the block of the next entry to read. */ uint32_t next_off; - const unsigned char *block; - size_t block_len; - uint32_t hash_size; + const struct reftable_block *block; /* key for last entry we read. */ struct reftable_buf last_key; @@ -120,12 +79,23 @@ struct block_iter { .scratch = REFTABLE_BUF_INIT, \ } -/* Position `it` at start of the block */ -void block_iter_seek_start(struct block_iter *it, const struct block_reader *br); +/* + * Initialize the block iterator with the given block. The iterator will be + * positioned at the first record contained in the block. The block must remain + * valid until the end of the iterator's lifetime. It is valid to re-initialize + * iterators multiple times. + */ +void block_iter_init(struct block_iter *it, const struct reftable_block *block); -/* Position `it` to the `want` key in the block */ -int block_iter_seek_key(struct block_iter *it, const struct block_reader *br, - struct reftable_buf *want); +/* Position the initialized iterator at the first record of its block. */ +void block_iter_seek_start(struct block_iter *it); + +/* + * Position the initialized iterator at the desired record key. It is not an + * error in case the record cannot be found. If so, a subsequent call to + * `block_iter_next()` will indicate that the iterator is exhausted. + */ +int block_iter_seek_key(struct block_iter *it, struct reftable_buf *want); /* return < 0 for error, 0 for OK, > 0 for EOF. */ int block_iter_next(struct block_iter *it, struct reftable_record *rec); @@ -142,7 +112,4 @@ size_t header_size(int version); /* size of file footer, depending on format version */ size_t footer_size(int version); -/* returns a block to its source. */ -void reftable_block_done(struct reftable_block *ret); - #endif diff --git a/reftable/blocksource.c b/reftable/blocksource.c index 78c1be2337..573c81287f 100644 --- a/reftable/blocksource.c +++ b/reftable/blocksource.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "system.h" @@ -13,7 +13,42 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-blocksource.h" #include "reftable-error.h" -static void reftable_buf_return_block(void *b REFTABLE_UNUSED, struct reftable_block *dest) +void block_source_release_data(struct reftable_block_data *data) +{ + struct reftable_block_source source = data->source; + if (data && source.ops) + source.ops->release_data(source.arg, data); + data->data = NULL; + data->len = 0; + data->source.ops = NULL; + data->source.arg = NULL; +} + +void block_source_close(struct reftable_block_source *source) +{ + if (!source->ops) { + return; + } + + source->ops->close(source->arg); + source->ops = NULL; +} + +ssize_t block_source_read_data(struct reftable_block_source *source, + struct reftable_block_data *dest, uint64_t off, + uint32_t size) +{ + ssize_t result = source->ops->read_data(source->arg, dest, off, size); + dest->source = *source; + return result; +} + +uint64_t block_source_size(struct reftable_block_source *source) +{ + return source->ops->size(source->arg); +} + +static void reftable_buf_release_data(void *b REFTABLE_UNUSED, struct reftable_block_data *dest) { if (dest->len) memset(dest->data, 0xff, dest->len); @@ -24,8 +59,8 @@ static void reftable_buf_close(void *b REFTABLE_UNUSED) { } -static ssize_t reftable_buf_read_block(void *v, struct reftable_block *dest, - uint64_t off, uint32_t size) +static ssize_t reftable_buf_read_data(void *v, struct reftable_block_data *dest, + uint64_t off, uint32_t size) { struct reftable_buf *b = v; assert(off + size <= b->len); @@ -44,8 +79,8 @@ static uint64_t reftable_buf_size(void *b) static struct reftable_block_source_vtable reftable_buf_vtable = { .size = &reftable_buf_size, - .read_block = &reftable_buf_read_block, - .return_block = &reftable_buf_return_block, + .read_data = &reftable_buf_read_data, + .release_data = &reftable_buf_release_data, .close = &reftable_buf_close, }; @@ -67,7 +102,7 @@ static uint64_t file_size(void *b) return ((struct file_block_source *)b)->size; } -static void file_return_block(void *b REFTABLE_UNUSED, struct reftable_block *dest REFTABLE_UNUSED) +static void file_release_data(void *b REFTABLE_UNUSED, struct reftable_block_data *dest REFTABLE_UNUSED) { } @@ -78,8 +113,8 @@ static void file_close(void *v) reftable_free(b); } -static ssize_t file_read_block(void *v, struct reftable_block *dest, uint64_t off, - uint32_t size) +static ssize_t file_read_data(void *v, struct reftable_block_data *dest, uint64_t off, + uint32_t size) { struct file_block_source *b = v; assert(off + size <= b->size); @@ -90,8 +125,8 @@ static ssize_t file_read_block(void *v, struct reftable_block *dest, uint64_t of static struct reftable_block_source_vtable file_vtable = { .size = &file_size, - .read_block = &file_read_block, - .return_block = &file_return_block, + .read_data = &file_read_data, + .release_data = &file_release_data, .close = &file_close, }; diff --git a/reftable/blocksource.h b/reftable/blocksource.h index a84a3ccd89..a110e05958 100644 --- a/reftable/blocksource.h +++ b/reftable/blocksource.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef BLOCKSOURCE_H #define BLOCKSOURCE_H @@ -12,9 +12,34 @@ https://developers.google.com/open-source/licenses/bsd #include "system.h" struct reftable_block_source; +struct reftable_block_data; struct reftable_buf; -/* Create an in-memory block source for reading reftables */ +/* + * Close the block source and the underlying resource. This is a no-op in case + * the block source is zero-initialized. + */ +void block_source_close(struct reftable_block_source *source); + +/* + * Read a block of length `size` from the source at the given `off`. + */ +ssize_t block_source_read_data(struct reftable_block_source *source, + struct reftable_block_data *dest, uint64_t off, + uint32_t size); + +/* + * Return the total length of the underlying resource. + */ +uint64_t block_source_size(struct reftable_block_source *source); + +/* + * Return a block to its original source, releasing any resources associated + * with it. + */ +void block_source_release_data(struct reftable_block_data *data); + +/* Create an in-memory block source for reading reftables. */ void block_source_from_buf(struct reftable_block_source *bs, struct reftable_buf *buf); diff --git a/reftable/constants.h b/reftable/constants.h index f6beb843eb..e3b1aaa516 100644 --- a/reftable/constants.h +++ b/reftable/constants.h @@ -1,19 +1,15 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef CONSTANTS_H #define CONSTANTS_H -#define BLOCK_TYPE_LOG 'g' -#define BLOCK_TYPE_INDEX 'i' -#define BLOCK_TYPE_REF 'r' -#define BLOCK_TYPE_OBJ 'o' -#define BLOCK_TYPE_ANY 0 +#include "reftable-constants.h" #define MAX_RESTARTS ((1 << 16) - 1) #define DEFAULT_BLOCK_SIZE 4096 diff --git a/reftable/error.c b/reftable/error.c index 660d029617..c7cab2dbc4 100644 --- a/reftable/error.c +++ b/reftable/error.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "system.h" #include "reftable-error.h" diff --git a/reftable/iter.c b/reftable/iter.c index f520382e70..2ecc52b336 100644 --- a/reftable/iter.c +++ b/reftable/iter.c @@ -1,19 +1,20 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "iter.h" #include "system.h" #include "block.h" +#include "blocksource.h" #include "constants.h" -#include "reader.h" #include "reftable-error.h" +#include "table.h" int iterator_seek(struct reftable_iterator *it, struct reftable_record *want) { @@ -113,7 +114,7 @@ static void indexed_table_ref_iter_close(void *p) { struct indexed_table_ref_iter *it = p; block_iter_close(&it->cur); - reftable_block_done(&it->block_reader.block); + block_source_release_data(&it->block.block_data); reftable_free(it->offsets); reftable_buf_release(&it->oid); } @@ -127,11 +128,10 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) return 1; } - reftable_block_done(&it->block_reader.block); + block_source_release_data(&it->block.block_data); off = it->offsets[it->offset_idx++]; - err = reader_init_block_reader(it->r, &it->block_reader, off, - BLOCK_TYPE_REF); + err = table_init_block(it->table, &it->block, off, REFTABLE_BLOCK_TYPE_REF); if (err < 0) { return err; } @@ -139,7 +139,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) /* indexed block does not exist. */ return REFTABLE_FORMAT_ERROR; } - block_iter_seek_start(&it->cur, &it->block_reader); + block_iter_init(&it->cur, &it->block); return 0; } @@ -181,7 +181,7 @@ static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec) } int indexed_table_ref_iter_new(struct indexed_table_ref_iter **dest, - struct reftable_reader *r, uint8_t *oid, + struct reftable_table *t, uint8_t *oid, int oid_len, uint64_t *offsets, int offset_len) { struct indexed_table_ref_iter empty = INDEXED_TABLE_REF_ITER_INIT; @@ -195,7 +195,7 @@ int indexed_table_ref_iter_new(struct indexed_table_ref_iter **dest, } *itr = empty; - itr->r = r; + itr->table = t; err = reftable_buf_add(&itr->oid, oid, oid_len); if (err < 0) @@ -246,7 +246,7 @@ int reftable_iterator_seek_ref(struct reftable_iterator *it, const char *name) { struct reftable_record want = { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u.ref = { .refname = (char *)name, }, @@ -258,7 +258,7 @@ int reftable_iterator_next_ref(struct reftable_iterator *it, struct reftable_ref_record *ref) { struct reftable_record rec = { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u = { .ref = *ref }, @@ -272,7 +272,7 @@ int reftable_iterator_seek_log_at(struct reftable_iterator *it, const char *name, uint64_t update_index) { struct reftable_record want = { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u.log = { .refname = (char *)name, .update_index = update_index, @@ -291,7 +291,7 @@ int reftable_iterator_next_log(struct reftable_iterator *it, struct reftable_log_record *log) { struct reftable_record rec = { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u = { .log = *log, }, diff --git a/reftable/iter.h b/reftable/iter.h index 40f98893b8..cc920970a5 100644 --- a/reftable/iter.h +++ b/reftable/iter.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef ITER_H #define ITER_H @@ -59,7 +59,7 @@ void iterator_from_filtering_ref_iterator(struct reftable_iterator *, * but using the object index. */ struct indexed_table_ref_iter { - struct reftable_reader *r; + struct reftable_table *table; struct reftable_buf oid; /* mutable */ @@ -68,7 +68,7 @@ struct indexed_table_ref_iter { /* Points to the next offset to read. */ int offset_idx; int offset_len; - struct block_reader block_reader; + struct reftable_block block; struct block_iter cur; int is_finished; }; @@ -83,7 +83,7 @@ void iterator_from_indexed_table_ref_iter(struct reftable_iterator *it, /* Takes ownership of `offsets` */ int indexed_table_ref_iter_new(struct indexed_table_ref_iter **dest, - struct reftable_reader *r, uint8_t *oid, + struct reftable_table *t, uint8_t *oid, int oid_len, uint64_t *offsets, int offset_len); #endif diff --git a/reftable/merged.c b/reftable/merged.c index 4ff1553772..733de07454 100644 --- a/reftable/merged.c +++ b/reftable/merged.c @@ -1,21 +1,21 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "merged.h" #include "constants.h" #include "iter.h" #include "pq.h" -#include "reader.h" #include "record.h" #include "reftable-merged.h" #include "reftable-error.h" #include "system.h" +#include "table.h" struct merged_subiter { struct reftable_iterator iter; @@ -192,7 +192,7 @@ static void iterator_from_merged_iter(struct reftable_iterator *it, } int reftable_merged_table_new(struct reftable_merged_table **dest, - struct reftable_reader **readers, size_t n, + struct reftable_table **tables, size_t n, enum reftable_hash hash_id) { struct reftable_merged_table *m = NULL; @@ -200,10 +200,10 @@ int reftable_merged_table_new(struct reftable_merged_table **dest, uint64_t first_min = 0; for (size_t i = 0; i < n; i++) { - uint64_t min = reftable_reader_min_update_index(readers[i]); - uint64_t max = reftable_reader_max_update_index(readers[i]); + uint64_t min = reftable_table_min_update_index(tables[i]); + uint64_t max = reftable_table_max_update_index(tables[i]); - if (reftable_reader_hash_id(readers[i]) != hash_id) { + if (reftable_table_hash_id(tables[i]) != hash_id) { return REFTABLE_FORMAT_ERROR; } if (i == 0 || min < first_min) { @@ -218,8 +218,8 @@ int reftable_merged_table_new(struct reftable_merged_table **dest, if (!m) return REFTABLE_OUT_OF_MEMORY_ERROR; - m->readers = readers; - m->readers_len = n; + m->tables = tables; + m->tables_len = n; m->min = first_min; m->max = last_max; m->hash_id = hash_id; @@ -254,20 +254,20 @@ int merged_table_init_iter(struct reftable_merged_table *mt, struct merged_iter *mi = NULL; int ret; - if (mt->readers_len) { - REFTABLE_CALLOC_ARRAY(subiters, mt->readers_len); + if (mt->tables_len) { + REFTABLE_CALLOC_ARRAY(subiters, mt->tables_len); if (!subiters) { ret = REFTABLE_OUT_OF_MEMORY_ERROR; goto out; } } - for (size_t i = 0; i < mt->readers_len; i++) { + for (size_t i = 0; i < mt->tables_len; i++) { ret = reftable_record_init(&subiters[i].rec, typ); if (ret < 0) goto out; - ret = reader_init_iter(mt->readers[i], &subiters[i].iter, typ); + ret = table_init_iter(mt->tables[i], &subiters[i].iter, typ); if (ret < 0) goto out; } @@ -280,14 +280,14 @@ int merged_table_init_iter(struct reftable_merged_table *mt, mi->advance_index = -1; mi->suppress_deletions = mt->suppress_deletions; mi->subiters = subiters; - mi->subiters_len = mt->readers_len; + mi->subiters_len = mt->tables_len; iterator_from_merged_iter(it, mi); ret = 0; out: if (ret < 0) { - for (size_t i = 0; subiters && i < mt->readers_len; i++) { + for (size_t i = 0; subiters && i < mt->tables_len; i++) { reftable_iterator_destroy(&subiters[i].iter); reftable_record_release(&subiters[i].rec); } @@ -301,13 +301,13 @@ out: int reftable_merged_table_init_ref_iterator(struct reftable_merged_table *mt, struct reftable_iterator *it) { - return merged_table_init_iter(mt, it, BLOCK_TYPE_REF); + return merged_table_init_iter(mt, it, REFTABLE_BLOCK_TYPE_REF); } int reftable_merged_table_init_log_iterator(struct reftable_merged_table *mt, struct reftable_iterator *it) { - return merged_table_init_iter(mt, it, BLOCK_TYPE_LOG); + return merged_table_init_iter(mt, it, REFTABLE_BLOCK_TYPE_LOG); } enum reftable_hash reftable_merged_table_hash_id(struct reftable_merged_table *mt) diff --git a/reftable/merged.h b/reftable/merged.h index 0b7d939e92..4317e5f5f6 100644 --- a/reftable/merged.h +++ b/reftable/merged.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef MERGED_H #define MERGED_H @@ -13,8 +13,8 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-basics.h" struct reftable_merged_table { - struct reftable_reader **readers; - size_t readers_len; + struct reftable_table **tables; + size_t tables_len; enum reftable_hash hash_id; /* If unset, produce deletions. This is useful for compaction. For the diff --git a/reftable/pq.c b/reftable/pq.c index 82394a972d..9a79f5c5ee 100644 --- a/reftable/pq.c +++ b/reftable/pq.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "pq.h" diff --git a/reftable/pq.h b/reftable/pq.h index ff39016445..42310670b0 100644 --- a/reftable/pq.h +++ b/reftable/pq.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef PQ_H #define PQ_H diff --git a/reftable/reader.h b/reftable/reader.h deleted file mode 100644 index bb72108a6f..0000000000 --- a/reftable/reader.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ - -#ifndef READER_H -#define READER_H - -#include "block.h" -#include "record.h" -#include "reftable-iterator.h" -#include "reftable-reader.h" - -uint64_t block_source_size(struct reftable_block_source *source); - -ssize_t block_source_read_block(struct reftable_block_source *source, - struct reftable_block *dest, uint64_t off, - uint32_t size); -void block_source_close(struct reftable_block_source *source); - -/* metadata for a block type */ -struct reftable_reader_offsets { - int is_present; - uint64_t offset; - uint64_t index_offset; -}; - -/* The state for reading a reftable file. */ -struct reftable_reader { - /* for convenience, associate a name with the instance. */ - char *name; - struct reftable_block_source source; - - /* Size of the file, excluding the footer. */ - uint64_t size; - - /* The hash function used for ref records. */ - enum reftable_hash hash_id; - - uint32_t block_size; - uint64_t min_update_index; - uint64_t max_update_index; - /* Length of the OID keys in the 'o' section */ - int object_id_len; - int version; - - struct reftable_reader_offsets ref_offsets; - struct reftable_reader_offsets obj_offsets; - struct reftable_reader_offsets log_offsets; - - uint64_t refcount; -}; - -const char *reader_name(struct reftable_reader *r); - -int reader_init_iter(struct reftable_reader *r, - struct reftable_iterator *it, - uint8_t typ); - -/* initialize a block reader to read from `r` */ -int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br, - uint64_t next_off, uint8_t want_typ); - -#endif diff --git a/reftable/record.c b/reftable/record.c index c0080024ed..fcd387ba5d 100644 --- a/reftable/record.c +++ b/reftable/record.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ /* record.c - methods for different types of records. */ @@ -69,10 +69,10 @@ int put_var_int(struct string_view *dest, uint64_t value) int reftable_is_block_type(uint8_t typ) { switch (typ) { - case BLOCK_TYPE_REF: - case BLOCK_TYPE_LOG: - case BLOCK_TYPE_OBJ: - case BLOCK_TYPE_INDEX: + case REFTABLE_BLOCK_TYPE_REF: + case REFTABLE_BLOCK_TYPE_LOG: + case REFTABLE_BLOCK_TYPE_OBJ: + case REFTABLE_BLOCK_TYPE_INDEX: return 1; } return 0; @@ -459,7 +459,7 @@ static int reftable_ref_record_cmp_void(const void *_a, const void *_b) static struct reftable_record_vtable reftable_ref_record_vtable = { .key = &reftable_ref_record_key, - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .copy_from = &reftable_ref_record_copy_from, .val_type = &reftable_ref_record_val_type, .encode = &reftable_ref_record_encode, @@ -659,7 +659,7 @@ static int reftable_obj_record_cmp_void(const void *_a, const void *_b) static struct reftable_record_vtable reftable_obj_record_vtable = { .key = &reftable_obj_record_key, - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .copy_from = &reftable_obj_record_copy_from, .val_type = &reftable_obj_record_val_type, .encode = &reftable_obj_record_encode, @@ -1030,7 +1030,7 @@ static int reftable_log_record_is_deletion_void(const void *p) static struct reftable_record_vtable reftable_log_record_vtable = { .key = &reftable_log_record_key, - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .copy_from = &reftable_log_record_copy_from, .val_type = &reftable_log_record_val_type, .encode = &reftable_log_record_encode, @@ -1132,7 +1132,7 @@ static int reftable_index_record_cmp(const void *_a, const void *_b) static struct reftable_record_vtable reftable_index_record_vtable = { .key = &reftable_index_record_key, - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .copy_from = &reftable_index_record_copy_from, .val_type = &reftable_index_record_val_type, .encode = &reftable_index_record_encode, @@ -1275,13 +1275,13 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log) static void *reftable_record_data(struct reftable_record *rec) { switch (rec->type) { - case BLOCK_TYPE_REF: + case REFTABLE_BLOCK_TYPE_REF: return &rec->u.ref; - case BLOCK_TYPE_LOG: + case REFTABLE_BLOCK_TYPE_LOG: return &rec->u.log; - case BLOCK_TYPE_INDEX: + case REFTABLE_BLOCK_TYPE_INDEX: return &rec->u.idx; - case BLOCK_TYPE_OBJ: + case REFTABLE_BLOCK_TYPE_OBJ: return &rec->u.obj; } abort(); @@ -1291,13 +1291,13 @@ static struct reftable_record_vtable * reftable_record_vtable(struct reftable_record *rec) { switch (rec->type) { - case BLOCK_TYPE_REF: + case REFTABLE_BLOCK_TYPE_REF: return &reftable_ref_record_vtable; - case BLOCK_TYPE_LOG: + case REFTABLE_BLOCK_TYPE_LOG: return &reftable_log_record_vtable; - case BLOCK_TYPE_INDEX: + case REFTABLE_BLOCK_TYPE_INDEX: return &reftable_index_record_vtable; - case BLOCK_TYPE_OBJ: + case REFTABLE_BLOCK_TYPE_OBJ: return &reftable_obj_record_vtable; } abort(); @@ -1309,11 +1309,11 @@ int reftable_record_init(struct reftable_record *rec, uint8_t typ) rec->type = typ; switch (typ) { - case BLOCK_TYPE_REF: - case BLOCK_TYPE_LOG: - case BLOCK_TYPE_OBJ: + case REFTABLE_BLOCK_TYPE_REF: + case REFTABLE_BLOCK_TYPE_LOG: + case REFTABLE_BLOCK_TYPE_OBJ: return 0; - case BLOCK_TYPE_INDEX: + case REFTABLE_BLOCK_TYPE_INDEX: reftable_buf_init(&rec->u.idx.last_key); return 0; default: diff --git a/reftable/record.h b/reftable/record.h index 867810a932..7953f352a3 100644 --- a/reftable/record.h +++ b/reftable/record.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef RECORD_H #define RECORD_H diff --git a/reftable/reftable-basics.h b/reftable/reftable-basics.h index e0397ed583..6d73f19c85 100644 --- a/reftable/reftable-basics.h +++ b/reftable/reftable-basics.h @@ -4,13 +4,21 @@ * Use of this source code is governed by a BSD-style * license that can be found in the LICENSE file or at * https://developers.google.com/open-source/licenses/bsd -*/ + */ #ifndef REFTABLE_BASICS_H #define REFTABLE_BASICS_H #include <stddef.h> +/* A buffer that contains arbitrary byte slices. */ +struct reftable_buf { + size_t alloc; + size_t len; + char *buf; +}; +#define REFTABLE_BUF_INIT { 0 } + /* * Hash functions understood by the reftable library. Note that the values are * arbitrary and somewhat random such that we can easily detect cases where the diff --git a/reftable/reftable-block.h b/reftable/reftable-block.h new file mode 100644 index 0000000000..04c3b518c8 --- /dev/null +++ b/reftable/reftable-block.h @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ + +#ifndef REFTABLE_BLOCK_H +#define REFTABLE_BLOCK_H + +#include <stdint.h> + +#include "reftable-basics.h" +#include "reftable-blocksource.h" +#include "reftable-iterator.h" + +struct z_stream_s; + +/* + * A block part of a reftable. Contains records as well as some metadata + * describing them. + */ +struct reftable_block { + /* + * Offset of the block header; nonzero for the first block in a + * reftable. + */ + uint32_t header_off; + + /* The memory block. */ + struct reftable_block_data block_data; + uint32_t hash_size; + + /* Uncompressed data for log entries. */ + struct z_stream_s *zstream; + unsigned char *uncompressed_data; + size_t uncompressed_cap; + + /* + * Restart point data. Restart points are located after the block's + * record data. + */ + uint16_t restart_count; + uint32_t restart_off; + + /* + * Size of the data in the file. For log blocks, this is the compressed + * size. + */ + uint32_t full_block_size; + uint8_t block_type; +}; + +/* Initialize a reftable block from the given block source. */ +int reftable_block_init(struct reftable_block *b, + struct reftable_block_source *source, + uint32_t offset, uint32_t header_size, + uint32_t table_block_size, uint32_t hash_size); + +/* Release resources allocated by the block. */ +void reftable_block_release(struct reftable_block *b); + +/* Initialize a generic record iterator from the given block. */ +int reftable_block_init_iterator(const struct reftable_block *b, + struct reftable_iterator *it); + +/* Returns the block type (eg. 'r' for refs). */ +uint8_t reftable_block_type(const struct reftable_block *b); + +/* Decodes the first key in the block. */ +int reftable_block_first_key(const struct reftable_block *b, struct reftable_buf *key); + +#endif /* REFTABLE_BLOCK_H */ diff --git a/reftable/reftable-blocksource.h b/reftable/reftable-blocksource.h index 6b326aa5ea..f5ba867bd6 100644 --- a/reftable/reftable-blocksource.h +++ b/reftable/reftable-blocksource.h @@ -1,17 +1,18 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_BLOCKSOURCE_H #define REFTABLE_BLOCKSOURCE_H #include <stdint.h> -/* block_source is a generic wrapper for a seekable readable file. +/* + * Generic wrapper for a seekable readable file. */ struct reftable_block_source { struct reftable_block_source_vtable *ops; @@ -20,7 +21,7 @@ struct reftable_block_source { /* a contiguous segment of bytes. It keeps track of its generating block_source * so it can return itself into the pool. */ -struct reftable_block { +struct reftable_block_data { uint8_t *data; size_t len; struct reftable_block_source source; @@ -28,20 +29,20 @@ struct reftable_block { /* block_source_vtable are the operations that make up block_source */ struct reftable_block_source_vtable { - /* returns the size of a block source */ + /* Returns the size of a block source. */ uint64_t (*size)(void *source); /* * Reads a segment from the block source. It is an error to read beyond * the end of the block. */ - ssize_t (*read_block)(void *source, struct reftable_block *dest, - uint64_t off, uint32_t size); + ssize_t (*read_data)(void *source, struct reftable_block_data *dest, + uint64_t off, uint32_t size); - /* mark the block as read; may return the data back to malloc */ - void (*return_block)(void *source, struct reftable_block *blockp); + /* Mark the block as read; may release the data. */ + void (*release_data)(void *source, struct reftable_block_data *data); - /* release all resources associated with the block source */ + /* Release all resources associated with the block source. */ void (*close)(void *source); }; diff --git a/reftable/reftable-constants.h b/reftable/reftable-constants.h new file mode 100644 index 0000000000..4ae9ba4bac --- /dev/null +++ b/reftable/reftable-constants.h @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ + +#ifndef REFTABLE_CONSTANTS_H +#define REFTABLE_CONSTANTS_H + +#define REFTABLE_BLOCK_TYPE_LOG 'g' +#define REFTABLE_BLOCK_TYPE_INDEX 'i' +#define REFTABLE_BLOCK_TYPE_REF 'r' +#define REFTABLE_BLOCK_TYPE_OBJ 'o' +#define REFTABLE_BLOCK_TYPE_ANY 0 + +#endif /* REFTABLE_CONSTANTS_H */ diff --git a/reftable/reftable-error.h b/reftable/reftable-error.h index a7e33d964d..d100e0df92 100644 --- a/reftable/reftable-error.h +++ b/reftable/reftable-error.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_ERROR_H #define REFTABLE_ERROR_H diff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h index e3bf688d53..af582028c2 100644 --- a/reftable/reftable-iterator.h +++ b/reftable/reftable-iterator.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_ITERATOR_H #define REFTABLE_ITERATOR_H diff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h index f2d01c3ef8..e5af846b32 100644 --- a/reftable/reftable-merged.h +++ b/reftable/reftable-merged.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_MERGED_H #define REFTABLE_MERGED_H @@ -26,14 +26,14 @@ https://developers.google.com/open-source/licenses/bsd /* A merged table is implements seeking/iterating over a stack of tables. */ struct reftable_merged_table; -struct reftable_reader; +struct reftable_table; /* - * reftable_merged_table_new creates a new merged table. The readers must be + * reftable_merged_table_new creates a new merged table. The tables must be * kept alive as long as the merged table is still in use. */ int reftable_merged_table_new(struct reftable_merged_table **dest, - struct reftable_reader **readers, size_t n, + struct reftable_table **tables, size_t n, enum reftable_hash hash_id); /* Initialize a merged table iterator for reading refs. */ diff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h deleted file mode 100644 index 0085fbb903..0000000000 --- a/reftable/reftable-reader.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - Copyright 2020 Google LLC - - Use of this source code is governed by a BSD-style - license that can be found in the LICENSE file or at - https://developers.google.com/open-source/licenses/bsd -*/ - -#ifndef REFTABLE_READER_H -#define REFTABLE_READER_H - -#include "reftable-iterator.h" -#include "reftable-blocksource.h" - -/* - * Reading single tables - * - * The follow routines are for reading single files. For an - * application-level interface, skip ahead to struct - * reftable_merged_table and struct reftable_stack. - */ - -/* The reader struct is a handle to an open reftable file. */ -struct reftable_reader; - -/* reftable_reader_new opens a reftable for reading. If successful, - * returns 0 code and sets pp. The name is used for creating a - * stack. Typically, it is the basename of the file. The block source - * `src` is owned by the reader, and is closed on calling - * reftable_reader_destroy(). On error, the block source `src` is - * closed as well. - */ -int reftable_reader_new(struct reftable_reader **pp, - struct reftable_block_source *src, const char *name); - -/* - * Manage the reference count of the reftable reader. A newly initialized - * reader starts with a refcount of 1 and will be deleted once the refcount has - * reached 0. - * - * This is required because readers may have longer lifetimes than the stack - * they belong to. The stack may for example be reloaded while the old tables - * are still being accessed by an iterator. - */ -void reftable_reader_incref(struct reftable_reader *reader); -void reftable_reader_decref(struct reftable_reader *reader); - -/* Initialize a reftable iterator for reading refs. */ -int reftable_reader_init_ref_iterator(struct reftable_reader *r, - struct reftable_iterator *it); - -/* Initialize a reftable iterator for reading logs. */ -int reftable_reader_init_log_iterator(struct reftable_reader *r, - struct reftable_iterator *it); - -/* returns the hash ID used in this table. */ -enum reftable_hash reftable_reader_hash_id(struct reftable_reader *r); - -/* return an iterator for the refs pointing to `oid`. */ -int reftable_reader_refs_for(struct reftable_reader *r, - struct reftable_iterator *it, uint8_t *oid); - -/* return the max_update_index for a table */ -uint64_t reftable_reader_max_update_index(struct reftable_reader *r); - -/* return the min_update_index for a table */ -uint64_t reftable_reader_min_update_index(struct reftable_reader *r); - -/* print blocks onto stdout for debugging. */ -int reftable_reader_print_blocks(const char *tablename); - -#endif diff --git a/reftable/reftable-record.h b/reftable/reftable-record.h index 931e594744..385a74cc86 100644 --- a/reftable/reftable-record.h +++ b/reftable/reftable-record.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_RECORD_H #define REFTABLE_RECORD_H diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index ae14270ea7..910ec6ef3a 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_STACK_H #define REFTABLE_STACK_H diff --git a/reftable/reftable-table.h b/reftable/reftable-table.h new file mode 100644 index 0000000000..5f935d02e3 --- /dev/null +++ b/reftable/reftable-table.h @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ + +#ifndef REFTABLE_TABLE_H +#define REFTABLE_TABLE_H + +#include "reftable-iterator.h" +#include "reftable-block.h" +#include "reftable-blocksource.h" + +/* + * Reading single tables + * + * The follow routines are for reading single files. For an + * application-level interface, skip ahead to struct + * reftable_merged_table and struct reftable_stack. + */ + +/* Metadata for a block type. */ +struct reftable_table_offsets { + int is_present; + uint64_t offset; + uint64_t index_offset; +}; + +/* The table struct is a handle to an open reftable file. */ +struct reftable_table { + /* for convenience, associate a name with the instance. */ + char *name; + struct reftable_block_source source; + + /* Size of the file, excluding the footer. */ + uint64_t size; + + /* The hash function used for ref records. */ + enum reftable_hash hash_id; + + uint32_t block_size; + uint64_t min_update_index; + uint64_t max_update_index; + /* Length of the OID keys in the 'o' section */ + int object_id_len; + int version; + + struct reftable_table_offsets ref_offsets; + struct reftable_table_offsets obj_offsets; + struct reftable_table_offsets log_offsets; + + uint64_t refcount; +}; + +/* reftable_table_new opens a reftable for reading. If successful, + * returns 0 code and sets pp. The name is used for creating a + * stack. Typically, it is the basename of the file. The block source + * `src` is owned by the table, and is closed on calling + * reftable_table_destroy(). On error, the block source `src` is + * closed as well. + */ +int reftable_table_new(struct reftable_table **out, + struct reftable_block_source *src, const char *name); + +/* + * Manage the reference count of the reftable table. A newly initialized + * table starts with a refcount of 1 and will be deleted once the refcount has + * reached 0. + * + * This is required because tables may have longer lifetimes than the stack + * they belong to. The stack may for example be reloaded while the old tables + * are still being accessed by an iterator. + */ +void reftable_table_incref(struct reftable_table *table); +void reftable_table_decref(struct reftable_table *table); + +/* Initialize a reftable iterator for reading refs. */ +int reftable_table_init_ref_iterator(struct reftable_table *t, + struct reftable_iterator *it); + +/* Initialize a reftable iterator for reading logs. */ +int reftable_table_init_log_iterator(struct reftable_table *t, + struct reftable_iterator *it); + +/* returns the hash ID used in this table. */ +enum reftable_hash reftable_table_hash_id(struct reftable_table *t); + +/* return an iterator for the refs pointing to `oid`. */ +int reftable_table_refs_for(struct reftable_table *t, + struct reftable_iterator *it, uint8_t *oid); + +/* return the max_update_index for a table */ +uint64_t reftable_table_max_update_index(struct reftable_table *t); + +/* return the min_update_index for a table */ +uint64_t reftable_table_min_update_index(struct reftable_table *t); + +/* + * An iterator that iterates through the blocks contained in a given table. + */ +struct reftable_table_iterator { + void *iter_arg; +}; + +int reftable_table_iterator_init(struct reftable_table_iterator *it, + struct reftable_table *t); + +void reftable_table_iterator_release(struct reftable_table_iterator *it); + +int reftable_table_iterator_next(struct reftable_table_iterator *it, + const struct reftable_block **out); + +#endif diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index 1befe3b07c..0fbeff17f4 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef REFTABLE_WRITER_H #define REFTABLE_WRITER_H diff --git a/reftable/stack.c b/reftable/stack.c index 6dac015b47..4caf96aa1d 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -1,20 +1,20 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "stack.h" #include "system.h" #include "constants.h" #include "merged.h" -#include "reader.h" #include "reftable-error.h" #include "reftable-record.h" #include "reftable-merged.h" +#include "table.h" #include "writer.h" static int stack_try_add(struct reftable_stack *st, @@ -203,14 +203,14 @@ int reftable_stack_init_ref_iterator(struct reftable_stack *st, struct reftable_iterator *it) { return merged_table_init_iter(reftable_stack_merged_table(st), - it, BLOCK_TYPE_REF); + it, REFTABLE_BLOCK_TYPE_REF); } int reftable_stack_init_log_iterator(struct reftable_stack *st, struct reftable_iterator *it) { return merged_table_init_iter(reftable_stack_merged_table(st), - it, BLOCK_TYPE_LOG); + it, REFTABLE_BLOCK_TYPE_LOG); } struct reftable_merged_table * @@ -248,11 +248,11 @@ void reftable_stack_destroy(struct reftable_stack *st) REFTABLE_FREE_AND_NULL(names); } - if (st->readers) { + if (st->tables) { struct reftable_buf filename = REFTABLE_BUF_INIT; - for (size_t i = 0; i < st->readers_len; i++) { - const char *name = reader_name(st->readers[i]); + for (size_t i = 0; i < st->tables_len; i++) { + const char *name = reftable_table_name(st->tables[i]); int try_unlinking = 1; reftable_buf_reset(&filename); @@ -260,7 +260,7 @@ void reftable_stack_destroy(struct reftable_stack *st) if (stack_filename(&filename, st, name) < 0) try_unlinking = 0; } - reftable_reader_decref(st->readers[i]); + reftable_table_decref(st->tables[i]); if (try_unlinking && filename.len) { /* On Windows, can only unlink after closing. */ @@ -269,8 +269,8 @@ void reftable_stack_destroy(struct reftable_stack *st) } reftable_buf_release(&filename); - st->readers_len = 0; - REFTABLE_FREE_AND_NULL(st->readers); + st->tables_len = 0; + REFTABLE_FREE_AND_NULL(st->tables); } if (st->list_fd >= 0) { @@ -284,14 +284,14 @@ void reftable_stack_destroy(struct reftable_stack *st) free_names(names); } -static struct reftable_reader **stack_copy_readers(struct reftable_stack *st, - size_t cur_len) +static struct reftable_table **stack_copy_tables(struct reftable_stack *st, + size_t cur_len) { - struct reftable_reader **cur = reftable_calloc(cur_len, sizeof(*cur)); + struct reftable_table **cur = reftable_calloc(cur_len, sizeof(*cur)); if (!cur) return NULL; for (size_t i = 0; i < cur_len; i++) - cur[i] = st->readers[i]; + cur[i] = st->tables[i]; return cur; } @@ -299,19 +299,19 @@ static int reftable_stack_reload_once(struct reftable_stack *st, const char **names, int reuse_open) { - size_t cur_len = !st->merged ? 0 : st->merged->readers_len; - struct reftable_reader **cur = NULL; - struct reftable_reader **reused = NULL; - struct reftable_reader **new_readers = NULL; + size_t cur_len = !st->merged ? 0 : st->merged->tables_len; + struct reftable_table **cur = NULL; + struct reftable_table **reused = NULL; + struct reftable_table **new_tables = NULL; size_t reused_len = 0, reused_alloc = 0, names_len; - size_t new_readers_len = 0; + size_t new_tables_len = 0; struct reftable_merged_table *new_merged = NULL; struct reftable_buf table_path = REFTABLE_BUF_INIT; int err = 0; size_t i; if (cur_len) { - cur = stack_copy_readers(st, cur_len); + cur = stack_copy_tables(st, cur_len); if (!cur) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; @@ -321,28 +321,28 @@ static int reftable_stack_reload_once(struct reftable_stack *st, names_len = names_length(names); if (names_len) { - new_readers = reftable_calloc(names_len, sizeof(*new_readers)); - if (!new_readers) { + new_tables = reftable_calloc(names_len, sizeof(*new_tables)); + if (!new_tables) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } } while (*names) { - struct reftable_reader *rd = NULL; + struct reftable_table *table = NULL; const char *name = *names++; /* this is linear; we assume compaction keeps the number of tables under control so this is not quadratic. */ for (i = 0; reuse_open && i < cur_len; i++) { if (cur[i] && 0 == strcmp(cur[i]->name, name)) { - rd = cur[i]; + table = cur[i]; cur[i] = NULL; /* * When reloading the stack fails, we end up - * releasing all new readers. This also - * includes the reused readers, even though + * releasing all new tables. This also + * includes the reused tables, even though * they are still in used by the old stack. We * thus need to keep them alive here, which we * do by bumping their refcount. @@ -354,13 +354,13 @@ static int reftable_stack_reload_once(struct reftable_stack *st, err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } - reused[reused_len++] = rd; - reftable_reader_incref(rd); + reused[reused_len++] = table; + reftable_table_incref(table); break; } } - if (!rd) { + if (!table) { struct reftable_block_source src = { NULL }; err = stack_filename(&table_path, st, name); @@ -372,36 +372,36 @@ static int reftable_stack_reload_once(struct reftable_stack *st, if (err < 0) goto done; - err = reftable_reader_new(&rd, &src, name); + err = reftable_table_new(&table, &src, name); if (err < 0) goto done; } - new_readers[new_readers_len] = rd; - new_readers_len++; + new_tables[new_tables_len] = table; + new_tables_len++; } /* success! */ - err = reftable_merged_table_new(&new_merged, new_readers, - new_readers_len, st->opts.hash_id); + err = reftable_merged_table_new(&new_merged, new_tables, + new_tables_len, st->opts.hash_id); if (err < 0) goto done; /* - * Close the old, non-reused readers and proactively try to unlink + * Close the old, non-reused tables and proactively try to unlink * them. This is done for systems like Windows, where the underlying - * file of such an open reader wouldn't have been possible to be + * file of such an open table wouldn't have been possible to be * unlinked by the compacting process. */ for (i = 0; i < cur_len; i++) { if (cur[i]) { - const char *name = reader_name(cur[i]); + const char *name = reftable_table_name(cur[i]); err = stack_filename(&table_path, st, name); if (err < 0) goto done; - reftable_reader_decref(cur[i]); + reftable_table_decref(cur[i]); unlink(table_path.buf); } } @@ -412,25 +412,25 @@ static int reftable_stack_reload_once(struct reftable_stack *st, new_merged->suppress_deletions = 1; st->merged = new_merged; - if (st->readers) - reftable_free(st->readers); - st->readers = new_readers; - st->readers_len = new_readers_len; - new_readers = NULL; - new_readers_len = 0; + if (st->tables) + reftable_free(st->tables); + st->tables = new_tables; + st->tables_len = new_tables_len; + new_tables = NULL; + new_tables_len = 0; /* - * Decrement the refcount of reused readers again. This only needs to + * Decrement the refcount of reused tables again. This only needs to * happen on the successful case, because on the unsuccessful one we - * decrement their refcount via `new_readers`. + * decrement their refcount via `new_tables`. */ for (i = 0; i < reused_len; i++) - reftable_reader_decref(reused[i]); + reftable_table_decref(reused[i]); done: - for (i = 0; i < new_readers_len; i++) - reftable_reader_decref(new_readers[i]); - reftable_free(new_readers); + for (i = 0; i < new_tables_len; i++) + reftable_table_decref(new_tables[i]); + reftable_free(new_tables); reftable_free(reused); reftable_free(cur); reftable_buf_release(&table_path); @@ -615,10 +615,10 @@ static int stack_uptodate(struct reftable_stack *st) /* * It's fine for "tables.list" to not exist. In that * case, we have to refresh when the loaded stack has - * any readers. + * any tables. */ if (errno == ENOENT) - return !!st->readers_len; + return !!st->tables_len; return REFTABLE_IO_ERROR; } @@ -637,19 +637,19 @@ static int stack_uptodate(struct reftable_stack *st) if (err < 0) return err; - for (size_t i = 0; i < st->readers_len; i++) { + for (size_t i = 0; i < st->tables_len; i++) { if (!names[i]) { err = 1; goto done; } - if (strcmp(st->readers[i]->name, names[i])) { + if (strcmp(st->tables[i]->name, names[i])) { err = 1; goto done; } } - if (names[st->merged->readers_len]) { + if (names[st->merged->tables_len]) { err = 1; goto done; } @@ -792,8 +792,8 @@ int reftable_addition_commit(struct reftable_addition *add) if (add->new_tables_len == 0) goto done; - for (i = 0; i < add->stack->merged->readers_len; i++) { - if ((err = reftable_buf_addstr(&table_list, add->stack->readers[i]->name)) < 0 || + for (i = 0; i < add->stack->merged->tables_len; i++) { + if ((err = reftable_buf_addstr(&table_list, add->stack->tables[i]->name)) < 0 || (err = reftable_buf_addstr(&table_list, "\n")) < 0) goto done; } @@ -1000,9 +1000,9 @@ done: uint64_t reftable_stack_next_update_index(struct reftable_stack *st) { - int sz = st->merged->readers_len; + int sz = st->merged->tables_len; if (sz > 0) - return reftable_reader_max_update_index(st->readers[sz - 1]) + + return reftable_table_max_update_index(st->tables[sz - 1]) + 1; return 1; } @@ -1021,8 +1021,8 @@ static int stack_compact_locked(struct reftable_stack *st, struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; int err = 0; - err = format_name(&next_name, reftable_reader_min_update_index(st->readers[first]), - reftable_reader_max_update_index(st->readers[last])); + err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]), + reftable_table_max_update_index(st->tables[last])); if (err < 0) goto done; @@ -1087,18 +1087,18 @@ static int stack_write_compact(struct reftable_stack *st, int err = 0; for (size_t i = first; i <= last; i++) - st->stats.bytes += st->readers[i]->size; - err = reftable_writer_set_limits(wr, st->readers[first]->min_update_index, - st->readers[last]->max_update_index); + st->stats.bytes += st->tables[i]->size; + err = reftable_writer_set_limits(wr, st->tables[first]->min_update_index, + st->tables[last]->max_update_index); if (err < 0) goto done; - err = reftable_merged_table_new(&mt, st->readers + first, subtabs_len, + err = reftable_merged_table_new(&mt, st->tables + first, subtabs_len, st->opts.hash_id); if (err < 0) goto done; - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); if (err < 0) goto done; @@ -1126,7 +1126,7 @@ static int stack_write_compact(struct reftable_stack *st, } reftable_iterator_destroy(&it); - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_LOG); if (err < 0) goto done; @@ -1250,7 +1250,7 @@ static int stack_compact_range(struct reftable_stack *st, table_locks[i] = REFTABLE_FLOCK_INIT; for (i = last + 1; i > first; i--) { - err = stack_filename(&table_name, st, reader_name(st->readers[i - 1])); + err = stack_filename(&table_name, st, reftable_table_name(st->tables[i - 1])); if (err < 0) goto done; @@ -1376,7 +1376,7 @@ static int stack_compact_range(struct reftable_stack *st, * compacted in the updated "tables.list" file. */ for (size_t i = 0; names[i]; i++) { - if (strcmp(names[i], st->readers[first]->name)) + if (strcmp(names[i], st->tables[first]->name)) continue; /* @@ -1386,8 +1386,8 @@ static int stack_compact_range(struct reftable_stack *st, * have compacted them. */ for (size_t j = 1; j < last - first + 1; j++) { - const char *old = first + j < st->merged->readers_len ? - st->readers[first + j]->name : NULL; + const char *old = first + j < st->merged->tables_len ? + st->tables[first + j]->name : NULL; const char *new = names[i + j]; /* @@ -1427,16 +1427,16 @@ static int stack_compact_range(struct reftable_stack *st, * `fd_read_lines()` uses a `NULL` sentinel to indicate that * the array is at its end. As we use `free_names()` to free * the array, we need to include this sentinel value here and - * thus have to allocate `readers_len + 1` many entries. + * thus have to allocate `tables_len + 1` many entries. */ - REFTABLE_CALLOC_ARRAY(names, st->merged->readers_len + 1); + REFTABLE_CALLOC_ARRAY(names, st->merged->tables_len + 1); if (!names) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } - for (size_t i = 0; i < st->merged->readers_len; i++) { - names[i] = reftable_strdup(st->readers[i]->name); + for (size_t i = 0; i < st->merged->tables_len; i++) { + names[i] = reftable_strdup(st->tables[i]->name); if (!names[i]) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; @@ -1451,8 +1451,8 @@ static int stack_compact_range(struct reftable_stack *st, * it into place now. */ if (!is_empty_table) { - err = format_name(&new_table_name, st->readers[first]->min_update_index, - st->readers[last]->max_update_index); + err = format_name(&new_table_name, st->tables[first]->min_update_index, + st->tables[last]->max_update_index); if (err < 0) goto done; @@ -1559,7 +1559,7 @@ done: int reftable_stack_compact_all(struct reftable_stack *st, struct reftable_log_expiry_config *config) { - size_t last = st->merged->readers_len ? st->merged->readers_len - 1 : 0; + size_t last = st->merged->tables_len ? st->merged->tables_len - 1 : 0; return stack_compact_range(st, 0, last, config, 0); } @@ -1650,12 +1650,12 @@ static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st) int overhead = header_size(version) - 1; uint64_t *sizes; - REFTABLE_CALLOC_ARRAY(sizes, st->merged->readers_len); + REFTABLE_CALLOC_ARRAY(sizes, st->merged->tables_len); if (!sizes) return NULL; - for (size_t i = 0; i < st->merged->readers_len; i++) - sizes[i] = st->readers[i]->size - overhead; + for (size_t i = 0; i < st->merged->tables_len; i++) + sizes[i] = st->tables[i]->size - overhead; return sizes; } @@ -1665,14 +1665,14 @@ int reftable_stack_auto_compact(struct reftable_stack *st) struct segment seg; uint64_t *sizes; - if (st->merged->readers_len < 2) + if (st->merged->tables_len < 2) return 0; sizes = stack_table_sizes_for_compaction(st); if (!sizes) return REFTABLE_OUT_OF_MEMORY_ERROR; - seg = suggest_compaction_segment(sizes, st->merged->readers_len, + seg = suggest_compaction_segment(sizes, st->merged->tables_len, st->opts.auto_compaction_factor); reftable_free(sizes); @@ -1763,7 +1763,7 @@ static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max, int err = 0; uint64_t update_idx = 0; struct reftable_block_source src = { NULL }; - struct reftable_reader *rd = NULL; + struct reftable_table *table = NULL; struct reftable_buf table_path = REFTABLE_BUF_INIT; err = stack_filename(&table_path, st, name); @@ -1774,12 +1774,12 @@ static void remove_maybe_stale_table(struct reftable_stack *st, uint64_t max, if (err < 0) goto done; - err = reftable_reader_new(&rd, &src, name); + err = reftable_table_new(&table, &src, name); if (err < 0) goto done; - update_idx = reftable_reader_max_update_index(rd); - reftable_reader_decref(rd); + update_idx = reftable_table_max_update_index(table); + reftable_table_decref(table); if (update_idx <= max) { unlink(table_path.buf); @@ -1803,8 +1803,8 @@ static int reftable_stack_clean_locked(struct reftable_stack *st) if (!is_table_name(d->d_name)) continue; - for (size_t i = 0; !found && i < st->readers_len; i++) - found = !strcmp(reader_name(st->readers[i]), d->d_name); + for (size_t i = 0; !found && i < st->tables_len; i++) + found = !strcmp(reftable_table_name(st->tables[i]), d->d_name); if (found) continue; diff --git a/reftable/stack.h b/reftable/stack.h index 5b45cff4f7..bc28f2998a 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef STACK_H #define STACK_H @@ -22,8 +22,8 @@ struct reftable_stack { struct reftable_write_options opts; - struct reftable_reader **readers; - size_t readers_len; + struct reftable_table **tables; + size_t tables_len; struct reftable_merged_table *merged; struct reftable_compaction_stats stats; }; diff --git a/reftable/system.h b/reftable/system.h index 10055fbff2..beb9d2431f 100644 --- a/reftable/system.h +++ b/reftable/system.h @@ -1,16 +1,17 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef SYSTEM_H #define SYSTEM_H /* This header glues the reftable library to the rest of Git */ +#define MINGW_DONT_HANDLE_IN_USE_ERROR #include "compat/posix.h" #include "compat/zlib-compat.h" diff --git a/reftable/reader.c b/reftable/table.c index 172aff2c10..ee83127615 100644 --- a/reftable/reader.c +++ b/reftable/table.c @@ -1,86 +1,46 @@ /* -Copyright 2020 Google LLC + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ - -#include "reader.h" +#include "table.h" #include "system.h" #include "block.h" +#include "blocksource.h" #include "constants.h" #include "iter.h" #include "record.h" #include "reftable-error.h" -uint64_t block_source_size(struct reftable_block_source *source) -{ - return source->ops->size(source->arg); -} - -ssize_t block_source_read_block(struct reftable_block_source *source, - struct reftable_block *dest, uint64_t off, - uint32_t size) -{ - ssize_t result = source->ops->read_block(source->arg, dest, off, size); - dest->source = *source; - return result; -} - -void block_source_close(struct reftable_block_source *source) -{ - if (!source->ops) { - return; - } - - source->ops->close(source->arg); - source->ops = NULL; -} - -static struct reftable_reader_offsets * -reader_offsets_for(struct reftable_reader *r, uint8_t typ) +static struct reftable_table_offsets * +table_offsets_for(struct reftable_table *t, uint8_t typ) { switch (typ) { - case BLOCK_TYPE_REF: - return &r->ref_offsets; - case BLOCK_TYPE_LOG: - return &r->log_offsets; - case BLOCK_TYPE_OBJ: - return &r->obj_offsets; + case REFTABLE_BLOCK_TYPE_REF: + return &t->ref_offsets; + case REFTABLE_BLOCK_TYPE_LOG: + return &t->log_offsets; + case REFTABLE_BLOCK_TYPE_OBJ: + return &t->obj_offsets; } abort(); } -static int reader_get_block(struct reftable_reader *r, - struct reftable_block *dest, uint64_t off, - uint32_t sz) +enum reftable_hash reftable_table_hash_id(struct reftable_table *t) { - ssize_t bytes_read; - if (off >= r->size) - return 0; - if (off + sz > r->size) - sz = r->size - off; - - bytes_read = block_source_read_block(&r->source, dest, off, sz); - if (bytes_read < 0) - return (int)bytes_read; - - return 0; + return t->hash_id; } -enum reftable_hash reftable_reader_hash_id(struct reftable_reader *r) +const char *reftable_table_name(struct reftable_table *t) { - return r->hash_id; + return t->name; } -const char *reader_name(struct reftable_reader *r) -{ - return r->name; -} - -static int parse_footer(struct reftable_reader *r, uint8_t *footer, +static int parse_footer(struct reftable_table *t, uint8_t *footer, uint8_t *header) { uint8_t *f = footer; @@ -95,29 +55,29 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer, } f += 4; - if (memcmp(footer, header, header_size(r->version))) { + if (memcmp(footer, header, header_size(t->version))) { err = REFTABLE_FORMAT_ERROR; goto done; } f++; - r->block_size = reftable_get_be24(f); + t->block_size = reftable_get_be24(f); f += 3; - r->min_update_index = reftable_get_be64(f); + t->min_update_index = reftable_get_be64(f); f += 8; - r->max_update_index = reftable_get_be64(f); + t->max_update_index = reftable_get_be64(f); f += 8; - if (r->version == 1) { - r->hash_id = REFTABLE_HASH_SHA1; + if (t->version == 1) { + t->hash_id = REFTABLE_HASH_SHA1; } else { switch (reftable_get_be32(f)) { case REFTABLE_FORMAT_ID_SHA1: - r->hash_id = REFTABLE_HASH_SHA1; + t->hash_id = REFTABLE_HASH_SHA1; break; case REFTABLE_FORMAT_ID_SHA256: - r->hash_id = REFTABLE_HASH_SHA256; + t->hash_id = REFTABLE_HASH_SHA256; break; default: err = REFTABLE_FORMAT_ERROR; @@ -127,20 +87,20 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer, f += 4; } - r->ref_offsets.index_offset = reftable_get_be64(f); + t->ref_offsets.index_offset = reftable_get_be64(f); f += 8; - r->obj_offsets.offset = reftable_get_be64(f); + t->obj_offsets.offset = reftable_get_be64(f); f += 8; - r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1); - r->obj_offsets.offset >>= 5; + t->object_id_len = t->obj_offsets.offset & ((1 << 5) - 1); + t->obj_offsets.offset >>= 5; - r->obj_offsets.index_offset = reftable_get_be64(f); + t->obj_offsets.index_offset = reftable_get_be64(f); f += 8; - r->log_offsets.offset = reftable_get_be64(f); + t->log_offsets.offset = reftable_get_be64(f); f += 8; - r->log_offsets.index_offset = reftable_get_be64(f); + t->log_offsets.index_offset = reftable_get_be64(f); f += 8; computed_crc = crc32(0, footer, f - footer); @@ -151,13 +111,13 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer, goto done; } - first_block_typ = header[header_size(r->version)]; - r->ref_offsets.is_present = (first_block_typ == BLOCK_TYPE_REF); - r->ref_offsets.offset = 0; - r->log_offsets.is_present = (first_block_typ == BLOCK_TYPE_LOG || - r->log_offsets.offset > 0); - r->obj_offsets.is_present = r->obj_offsets.offset > 0; - if (r->obj_offsets.is_present && !r->object_id_len) { + first_block_typ = header[header_size(t->version)]; + t->ref_offsets.is_present = (first_block_typ == REFTABLE_BLOCK_TYPE_REF); + t->ref_offsets.offset = 0; + t->log_offsets.is_present = (first_block_typ == REFTABLE_BLOCK_TYPE_LOG || + t->log_offsets.offset > 0); + t->obj_offsets.is_present = t->obj_offsets.offset > 0; + if (t->obj_offsets.is_present && !t->object_id_len) { err = REFTABLE_FORMAT_ERROR; goto done; } @@ -168,20 +128,20 @@ done: } struct table_iter { - struct reftable_reader *r; + struct reftable_table *table; uint8_t typ; uint64_t block_off; - struct block_reader br; + struct reftable_block block; struct block_iter bi; int is_finished; }; -static int table_iter_init(struct table_iter *ti, struct reftable_reader *r) +static int table_iter_init(struct table_iter *ti, struct reftable_table *t) { struct block_iter bi = BLOCK_ITER_INIT; memset(ti, 0, sizeof(*ti)); - reftable_reader_incref(r); - ti->r = r; + reftable_table_incref(t); + ti->table = t; ti->bi = bi; return 0; } @@ -190,8 +150,8 @@ static int table_iter_next_in_block(struct table_iter *ti, struct reftable_record *rec) { int res = block_iter_next(&ti->bi, rec); - if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) { - rec->u.ref.update_index += ti->r->min_update_index; + if (res == 0 && reftable_record_type(rec) == REFTABLE_BLOCK_TYPE_REF) { + rec->u.ref.update_index += ti->table->min_update_index; } return res; @@ -199,68 +159,32 @@ static int table_iter_next_in_block(struct table_iter *ti, static void table_iter_block_done(struct table_iter *ti) { - block_reader_release(&ti->br); + reftable_block_release(&ti->block); block_iter_reset(&ti->bi); } -static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off, - int version) -{ - int32_t result = 0; - - if (off == 0) { - data += header_size(version); - } - - *typ = data[0]; - if (reftable_is_block_type(*typ)) { - result = reftable_get_be24(data + 1); - } - return result; -} - -int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br, - uint64_t next_off, uint8_t want_typ) +int table_init_block(struct reftable_table *t, struct reftable_block *block, + uint64_t next_off, uint8_t want_typ) { - int32_t guess_block_size = r->block_size ? r->block_size : - DEFAULT_BLOCK_SIZE; - struct reftable_block block = { NULL }; - uint8_t block_typ = 0; - int err = 0; - uint32_t header_off = next_off ? 0 : header_size(r->version); - int32_t block_size = 0; + uint32_t header_off = next_off ? 0 : header_size(t->version); + int err; - if (next_off >= r->size) + if (next_off >= t->size) return 1; - err = reader_get_block(r, &block, next_off, guess_block_size); + err = reftable_block_init(block, &t->source, next_off, header_off, + t->block_size, hash_size(t->hash_id)); if (err < 0) goto done; - block_size = extract_block_size(block.data, &block_typ, next_off, - r->version); - if (block_size < 0) { - err = block_size; - goto done; - } - if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) { + if (want_typ != REFTABLE_BLOCK_TYPE_ANY && block->block_type != want_typ) { err = 1; goto done; } - if (block_size > guess_block_size) { - reftable_block_done(&block); - err = reader_get_block(r, &block, next_off, block_size); - if (err < 0) { - goto done; - } - } - - err = block_reader_init(br, &block, header_off, r->block_size, - hash_size(r->hash_id)); done: - reftable_block_done(&block); - + if (err) + reftable_block_release(block); return err; } @@ -268,15 +192,15 @@ static void table_iter_close(struct table_iter *ti) { table_iter_block_done(ti); block_iter_close(&ti->bi); - reftable_reader_decref(ti->r); + reftable_table_decref(ti->table); } static int table_iter_next_block(struct table_iter *ti) { - uint64_t next_block_off = ti->block_off + ti->br.full_block_size; + uint64_t next_block_off = ti->block_off + ti->block.full_block_size; int err; - err = reader_init_block_reader(ti->r, &ti->br, next_block_off, ti->typ); + err = table_init_block(ti->table, &ti->block, next_block_off, ti->typ); if (err > 0) ti->is_finished = 1; if (err) @@ -284,7 +208,7 @@ static int table_iter_next_block(struct table_iter *ti) ti->block_off = next_block_off; ti->is_finished = 0; - block_iter_seek_start(&ti->bi, &ti->br); + block_iter_init(&ti->bi, &ti->block); return 0; } @@ -326,27 +250,27 @@ static int table_iter_seek_to(struct table_iter *ti, uint64_t off, uint8_t typ) { int err; - err = reader_init_block_reader(ti->r, &ti->br, off, typ); + err = table_init_block(ti->table, &ti->block, off, typ); if (err != 0) return err; - ti->typ = block_reader_type(&ti->br); + ti->typ = reftable_block_type(&ti->block); ti->block_off = off; - block_iter_seek_start(&ti->bi, &ti->br); + block_iter_init(&ti->bi, &ti->block); ti->is_finished = 0; return 0; } static int table_iter_seek_start(struct table_iter *ti, uint8_t typ, int index) { - struct reftable_reader_offsets *offs = reader_offsets_for(ti->r, typ); + struct reftable_table_offsets *offs = table_offsets_for(ti->table, typ); uint64_t off = offs->offset; if (index) { off = offs->index_offset; if (off == 0) { return 1; } - typ = BLOCK_TYPE_INDEX; + typ = REFTABLE_BLOCK_TYPE_INDEX; } return table_iter_seek_to(ti, off, typ); @@ -396,10 +320,10 @@ static int table_iter_seek_linear(struct table_iter *ti, * as we have more than three blocks we would have an index, so * we would not do a linear search there anymore. */ - memset(&next.br.block, 0, sizeof(next.br.block)); - next.br.zstream = NULL; - next.br.uncompressed_data = NULL; - next.br.uncompressed_cap = 0; + memset(&next.block.block_data, 0, sizeof(next.block.block_data)); + next.block.zstream = NULL; + next.block.uncompressed_data = NULL; + next.block.uncompressed_cap = 0; err = table_iter_next_block(&next); if (err < 0) @@ -407,7 +331,7 @@ static int table_iter_seek_linear(struct table_iter *ti, if (err > 0) break; - err = block_reader_first_key(&next.br, &got_key); + err = reftable_block_first_key(&next.block, &got_key); if (err < 0) goto done; @@ -425,7 +349,8 @@ static int table_iter_seek_linear(struct table_iter *ti, * the wanted key inside of it. If the block does not contain our key * we know that the corresponding record does not exist. */ - err = block_iter_seek_key(&ti->bi, &ti->br, &want_key); + block_iter_init(&ti->bi, &ti->block); + err = block_iter_seek_key(&ti->bi, &want_key); if (err < 0) goto done; err = 0; @@ -441,10 +366,10 @@ static int table_iter_seek_indexed(struct table_iter *ti, struct reftable_record *rec) { struct reftable_record want_index = { - .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = REFTABLE_BUF_INIT } + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx = { .last_key = REFTABLE_BUF_INIT } }; struct reftable_record index_result = { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx = { .last_key = REFTABLE_BUF_INIT }, }; int err; @@ -493,7 +418,9 @@ static int table_iter_seek_indexed(struct table_iter *ti, if (err != 0) goto done; - err = block_iter_seek_key(&ti->bi, &ti->br, &want_index.u.idx.last_key); + block_iter_init(&ti->bi, &ti->block); + + err = block_iter_seek_key(&ti->bi, &want_index.u.idx.last_key); if (err < 0) goto done; @@ -502,7 +429,7 @@ static int table_iter_seek_indexed(struct table_iter *ti, break; } - if (ti->typ != BLOCK_TYPE_INDEX) { + if (ti->typ != REFTABLE_BLOCK_TYPE_INDEX) { err = REFTABLE_FORMAT_ERROR; goto done; } @@ -518,7 +445,7 @@ static int table_iter_seek(struct table_iter *ti, struct reftable_record *want) { uint8_t typ = reftable_record_type(want); - struct reftable_reader_offsets *offs = reader_offsets_for(ti->r, typ); + struct reftable_table_offsets *offs = table_offsets_for(ti->table, typ); int err; err = table_iter_seek_start(ti, reftable_record_type(want), @@ -566,11 +493,11 @@ static void iterator_from_table_iter(struct reftable_iterator *it, it->ops = &table_iter_vtable; } -int reader_init_iter(struct reftable_reader *r, - struct reftable_iterator *it, - uint8_t typ) +int table_init_iter(struct reftable_table *t, + struct reftable_iterator *it, + uint8_t typ) { - struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); + struct reftable_table_offsets *offs = table_offsets_for(t, typ); if (offs->is_present) { struct table_iter *ti; @@ -578,7 +505,7 @@ int reader_init_iter(struct reftable_reader *r, if (!ti) return REFTABLE_OUT_OF_MEMORY_ERROR; - table_iter_init(ti, r); + table_iter_init(ti, t); iterator_from_table_iter(it, ti); } else { iterator_set_empty(it); @@ -587,31 +514,31 @@ int reader_init_iter(struct reftable_reader *r, return 0; } -int reftable_reader_init_ref_iterator(struct reftable_reader *r, - struct reftable_iterator *it) +int reftable_table_init_ref_iterator(struct reftable_table *t, + struct reftable_iterator *it) { - return reader_init_iter(r, it, BLOCK_TYPE_REF); + return table_init_iter(t, it, REFTABLE_BLOCK_TYPE_REF); } -int reftable_reader_init_log_iterator(struct reftable_reader *r, - struct reftable_iterator *it) +int reftable_table_init_log_iterator(struct reftable_table *t, + struct reftable_iterator *it) { - return reader_init_iter(r, it, BLOCK_TYPE_LOG); + return table_init_iter(t, it, REFTABLE_BLOCK_TYPE_LOG); } -int reftable_reader_new(struct reftable_reader **out, - struct reftable_block_source *source, char const *name) +int reftable_table_new(struct reftable_table **out, + struct reftable_block_source *source, char const *name) { - struct reftable_block footer = { 0 }; - struct reftable_block header = { 0 }; - struct reftable_reader *r; + struct reftable_block_data footer = { 0 }; + struct reftable_block_data header = { 0 }; + struct reftable_table *t; uint64_t file_size = block_source_size(source); uint32_t read_size; ssize_t bytes_read; int err; - REFTABLE_CALLOC_ARRAY(r, 1); - if (!r) { + REFTABLE_CALLOC_ARRAY(t, 1); + if (!t) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } @@ -626,7 +553,7 @@ int reftable_reader_new(struct reftable_reader **out, goto done; } - bytes_read = block_source_read_block(source, &header, 0, read_size); + bytes_read = block_source_read_data(source, &header, 0, read_size); if (bytes_read < 0 || (size_t)bytes_read != read_size) { err = REFTABLE_IO_ERROR; goto done; @@ -636,84 +563,84 @@ int reftable_reader_new(struct reftable_reader **out, err = REFTABLE_FORMAT_ERROR; goto done; } - r->version = header.data[4]; - if (r->version != 1 && r->version != 2) { + t->version = header.data[4]; + if (t->version != 1 && t->version != 2) { err = REFTABLE_FORMAT_ERROR; goto done; } - r->size = file_size - footer_size(r->version); - r->source = *source; - r->name = reftable_strdup(name); - if (!r->name) { + t->size = file_size - footer_size(t->version); + t->source = *source; + t->name = reftable_strdup(name); + if (!t->name) { err = REFTABLE_OUT_OF_MEMORY_ERROR; goto done; } - r->hash_id = 0; - r->refcount = 1; + t->hash_id = 0; + t->refcount = 1; - bytes_read = block_source_read_block(source, &footer, r->size, - footer_size(r->version)); - if (bytes_read < 0 || (size_t)bytes_read != footer_size(r->version)) { + bytes_read = block_source_read_data(source, &footer, t->size, + footer_size(t->version)); + if (bytes_read < 0 || (size_t)bytes_read != footer_size(t->version)) { err = REFTABLE_IO_ERROR; goto done; } - err = parse_footer(r, footer.data, header.data); + err = parse_footer(t, footer.data, header.data); if (err) goto done; - *out = r; + *out = t; done: - reftable_block_done(&footer); - reftable_block_done(&header); + block_source_release_data(&footer); + block_source_release_data(&header); if (err) { - if (r) - reftable_free(r->name); - reftable_free(r); + if (t) + reftable_free(t->name); + reftable_free(t); block_source_close(source); } return err; } -void reftable_reader_incref(struct reftable_reader *r) +void reftable_table_incref(struct reftable_table *t) { - r->refcount++; + t->refcount++; } -void reftable_reader_decref(struct reftable_reader *r) +void reftable_table_decref(struct reftable_table *t) { - if (!r) + if (!t) return; - if (--r->refcount) + if (--t->refcount) return; - block_source_close(&r->source); - REFTABLE_FREE_AND_NULL(r->name); - reftable_free(r); + block_source_close(&t->source); + REFTABLE_FREE_AND_NULL(t->name); + reftable_free(t); } -static int reftable_reader_refs_for_indexed(struct reftable_reader *r, - struct reftable_iterator *it, - uint8_t *oid) +static int reftable_table_refs_for_indexed(struct reftable_table *t, + struct reftable_iterator *it, + uint8_t *oid) { struct reftable_record want = { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj = { .hash_prefix = oid, - .hash_prefix_len = r->object_id_len, + .hash_prefix_len = t->object_id_len, }, }; struct reftable_iterator oit = { NULL }; struct reftable_record got = { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj = { 0 }, }; int err = 0; struct indexed_table_ref_iter *itr = NULL; /* Look through the reverse index. */ - err = reader_init_iter(r, &oit, BLOCK_TYPE_OBJ); + err = table_init_iter(t, &oit, REFTABLE_BLOCK_TYPE_OBJ); if (err < 0) goto done; @@ -727,14 +654,14 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r, goto done; if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix, - r->object_id_len)) { + t->object_id_len)) { /* didn't find it; return empty iterator */ iterator_set_empty(it); err = 0; goto done; } - err = indexed_table_ref_iter_new(&itr, r, oid, hash_size(r->hash_id), + err = indexed_table_ref_iter_new(&itr, t, oid, hash_size(t->hash_id), got.u.obj.offsets, got.u.obj.offset_len); if (err < 0) @@ -748,14 +675,14 @@ done: return err; } -static int reftable_reader_refs_for_unindexed(struct reftable_reader *r, - struct reftable_iterator *it, - uint8_t *oid) +static int reftable_table_refs_for_unindexed(struct reftable_table *t, + struct reftable_iterator *it, + uint8_t *oid) { struct table_iter *ti; struct filtering_ref_iterator *filter = NULL; struct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT; - uint32_t oid_len = hash_size(r->hash_id); + uint32_t oid_len = hash_size(t->hash_id); int err; REFTABLE_ALLOC_ARRAY(ti, 1); @@ -764,8 +691,8 @@ static int reftable_reader_refs_for_unindexed(struct reftable_reader *r, goto out; } - table_iter_init(ti, r); - err = table_iter_seek_start(ti, BLOCK_TYPE_REF, 0); + table_iter_init(ti, t); + err = table_iter_seek_start(ti, REFTABLE_BLOCK_TYPE_REF, 0); if (err < 0) goto out; @@ -795,85 +722,67 @@ out: return err; } -int reftable_reader_refs_for(struct reftable_reader *r, - struct reftable_iterator *it, uint8_t *oid) +int reftable_table_refs_for(struct reftable_table *t, + struct reftable_iterator *it, uint8_t *oid) { - if (r->obj_offsets.is_present) - return reftable_reader_refs_for_indexed(r, it, oid); - return reftable_reader_refs_for_unindexed(r, it, oid); + if (t->obj_offsets.is_present) + return reftable_table_refs_for_indexed(t, it, oid); + return reftable_table_refs_for_unindexed(t, it, oid); } -uint64_t reftable_reader_max_update_index(struct reftable_reader *r) +uint64_t reftable_table_max_update_index(struct reftable_table *t) { - return r->max_update_index; + return t->max_update_index; } -uint64_t reftable_reader_min_update_index(struct reftable_reader *r) +uint64_t reftable_table_min_update_index(struct reftable_table *t) { - return r->min_update_index; + return t->min_update_index; } -int reftable_reader_print_blocks(const char *tablename) +int reftable_table_iterator_init(struct reftable_table_iterator *it, + struct reftable_table *t) { - struct { - const char *name; - int type; - } sections[] = { - { - .name = "ref", - .type = BLOCK_TYPE_REF, - }, - { - .name = "obj", - .type = BLOCK_TYPE_OBJ, - }, - { - .name = "log", - .type = BLOCK_TYPE_LOG, - }, - }; - struct reftable_block_source src = { 0 }; - struct reftable_reader *r = NULL; - struct table_iter ti = { 0 }; - size_t i; + struct table_iter *ti; int err; - err = reftable_block_source_from_file(&src, tablename); - if (err < 0) - goto done; + REFTABLE_ALLOC_ARRAY(ti, 1); + if (!ti) + return REFTABLE_OUT_OF_MEMORY_ERROR; - err = reftable_reader_new(&r, &src, tablename); + err = table_iter_init(ti, t); if (err < 0) - goto done; + goto out; - table_iter_init(&ti, r); + it->iter_arg = ti; + err = 0; - printf("header:\n"); - printf(" block_size: %d\n", r->block_size); +out: + if (err < 0) + reftable_free(ti); + return err; +} - for (i = 0; i < sizeof(sections) / sizeof(*sections); i++) { - err = table_iter_seek_start(&ti, sections[i].type, 0); - if (err < 0) - goto done; - if (err > 0) - continue; +void reftable_table_iterator_release(struct reftable_table_iterator *it) +{ + if (!it->iter_arg) + return; + table_iter_close(it->iter_arg); + reftable_free(it->iter_arg); + it->iter_arg = NULL; +} - printf("%s:\n", sections[i].name); +int reftable_table_iterator_next(struct reftable_table_iterator *it, + const struct reftable_block **out) +{ + struct table_iter *ti = it->iter_arg; + int err; - while (1) { - printf(" - length: %u\n", ti.br.block_len); - printf(" restarts: %u\n", ti.br.restart_count); + err = table_iter_next_block(ti); + if (err) + return err; - err = table_iter_next_block(&ti); - if (err < 0) - goto done; - if (err > 0) - break; - } - } + *out = &ti->block; -done: - reftable_reader_decref(r); - table_iter_close(&ti); - return err; + return 0; } diff --git a/reftable/table.h b/reftable/table.h new file mode 100644 index 0000000000..c54703e621 --- /dev/null +++ b/reftable/table.h @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ + +#ifndef TABLE_H +#define TABLE_H + +#include "block.h" +#include "record.h" +#include "reftable-iterator.h" +#include "reftable-table.h" + +const char *reftable_table_name(struct reftable_table *t); + +int table_init_iter(struct reftable_table *t, + struct reftable_iterator *it, + uint8_t typ); + +/* + * Initialize a block by reading from the given table and offset. + */ +int table_init_block(struct reftable_table *t, struct reftable_block *block, + uint64_t next_off, uint8_t want_typ); + +#endif diff --git a/reftable/tree.c b/reftable/tree.c index f4dbe72090..a52f7c0c7d 100644 --- a/reftable/tree.c +++ b/reftable/tree.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "system.h" #include "tree.h" diff --git a/reftable/tree.h b/reftable/tree.h index 9604453b6d..2c9c465299 100644 --- a/reftable/tree.h +++ b/reftable/tree.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef TREE_H #define TREE_H diff --git a/reftable/writer.c b/reftable/writer.c index 075ea8661b..cb16f71be4 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #include "writer.h" @@ -172,7 +172,7 @@ int reftable_writer_new(struct reftable_writer **out, wp->write_arg = writer_arg; wp->opts = opts; wp->flush = flush_func; - writer_reinit_block_writer(wp, BLOCK_TYPE_REF); + writer_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF); *out = wp; @@ -342,7 +342,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, struct reftable_ref_record *ref) { struct reftable_record rec = { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u = { .ref = *ref }, @@ -406,13 +406,13 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w, struct reftable_log_record *log) { struct reftable_record rec = { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u = { .log = *log, }, }; if (w->block_writer && - block_writer_type(w->block_writer) == BLOCK_TYPE_REF) { + block_writer_type(w->block_writer) == REFTABLE_BLOCK_TYPE_REF) { int err = writer_finish_public_section(w); if (err < 0) return err; @@ -532,7 +532,7 @@ static int writer_finish_section(struct reftable_writer *w) max_level++; index_start = w->next; - err = writer_reinit_block_writer(w, BLOCK_TYPE_INDEX); + err = writer_reinit_block_writer(w, REFTABLE_BLOCK_TYPE_INDEX); if (err < 0) return err; @@ -544,7 +544,7 @@ static int writer_finish_section(struct reftable_writer *w) w->index_cap = 0; for (i = 0; i < idx_len; i++) { struct reftable_record rec = { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u = { .idx = idx[i], }, @@ -609,7 +609,7 @@ static void write_object_record(void *void_arg, void *key) struct write_record_arg *arg = void_arg; struct obj_index_tree_node *entry = key; struct reftable_record - rec = { .type = BLOCK_TYPE_OBJ, + rec = { .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj = { .hash_prefix = (uint8_t *)entry->hash.buf, .hash_prefix_len = arg->w->stats.object_id_len, @@ -639,7 +639,7 @@ static void write_object_record(void *void_arg, void *key) if (arg->err < 0) goto done; - arg->err = writer_reinit_block_writer(arg->w, BLOCK_TYPE_OBJ); + arg->err = writer_reinit_block_writer(arg->w, REFTABLE_BLOCK_TYPE_OBJ); if (arg->err < 0) goto done; @@ -684,7 +684,7 @@ static int writer_dump_object_index(struct reftable_writer *w) infix_walk(w->obj_index_tree, &update_common, &common); w->stats.object_id_len = common.max + 1; - err = writer_reinit_block_writer(w, BLOCK_TYPE_OBJ); + err = writer_reinit_block_writer(w, REFTABLE_BLOCK_TYPE_OBJ); if (err < 0) return err; @@ -708,7 +708,7 @@ static int writer_finish_public_section(struct reftable_writer *w) err = writer_finish_section(w); if (err < 0) return err; - if (typ == BLOCK_TYPE_REF && !w->opts.skip_index_objects && + if (typ == REFTABLE_BLOCK_TYPE_REF && !w->opts.skip_index_objects && w->stats.ref_stats.index_blocks > 0) { err = writer_dump_object_index(w); if (err < 0) @@ -813,7 +813,7 @@ static int writer_flush_nonempty_block(struct reftable_writer *w) * By default, all records except for log records are padded to the * block size. */ - if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) + if (!w->opts.unpadded && typ != REFTABLE_BLOCK_TYPE_LOG) padding = w->opts.block_size - raw_bytes; bstats = writer_reftable_block_stats(w, typ); diff --git a/reftable/writer.h b/reftable/writer.h index 1f4788a430..9f53610b27 100644 --- a/reftable/writer.h +++ b/reftable/writer.h @@ -1,10 +1,10 @@ /* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file or at + * https://developers.google.com/open-source/licenses/bsd + */ #ifndef WRITER_H #define WRITER_H diff --git a/remote-curl.c b/remote-curl.c index 1273507a96..590b228f67 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -1239,7 +1239,7 @@ static int fetch_git(struct discovery *heads, packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); - rpc.service_name = "git-upload-pack", + rpc.service_name = "git-upload-pack"; rpc.gzip_request = 1; err = rpc_service(&rpc, heads, args.v, &preamble, &rpc_result); @@ -1401,7 +1401,7 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs) packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); - rpc.service_name = "git-receive-pack", + rpc.service_name = "git-receive-pack"; err = rpc_service(&rpc, heads, args.v, &preamble, &rpc_result); if (rpc_result.len) @@ -12,7 +12,7 @@ #include "refs.h" #include "refspec.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "commit.h" #include "diff.h" @@ -2297,7 +2297,7 @@ struct ref *get_local_heads(void) struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, - int all) + unsigned flags) { const struct ref *r; struct ref *list = NULL; @@ -2315,8 +2315,10 @@ struct ref *guess_remote_head(const struct ref *head, return copy_ref(find_ref_by_name(refs, head->symref)); /* If a remote branch exists with the default branch name, let's use it. */ - if (!all) { - char *default_branch = repo_default_branch_name(the_repository, 0); + if (!(flags & REMOTE_GUESS_HEAD_ALL)) { + char *default_branch = + repo_default_branch_name(the_repository, + flags & REMOTE_GUESS_HEAD_QUIET); char *ref = xstrfmt("refs/heads/%s", default_branch); r = find_ref_by_name(refs, ref); @@ -2339,7 +2341,7 @@ struct ref *guess_remote_head(const struct ref *head, oideq(&r->old_oid, &head->old_oid)) { *tail = copy_ref(r); tail = &((*tail)->next); - if (!all) + if (!(flags & REMOTE_GUESS_HEAD_ALL)) break; } } @@ -387,15 +387,18 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, int show_divergence_advice); struct ref *get_local_heads(void); + /* * Find refs from a list which are likely to be pointed to by the given HEAD - * ref. If 'all' is false, returns the most likely ref; otherwise, returns a - * list of all candidate refs. If no match is found (or 'head' is NULL), - * returns NULL. All returns are newly allocated and should be freed. + * ref. If REMOTE_GUESS_HEAD_ALL is set, return a list of all candidate refs; + * otherwise, return the most likely ref. If no match is found (or 'head' is + * NULL), returns NULL. All returns are newly allocated and should be freed. */ +#define REMOTE_GUESS_HEAD_ALL (1 << 0) +#define REMOTE_GUESS_HEAD_QUIET (1 << 1) struct ref *guess_remote_head(const struct ref *head, const struct ref *refs, - int all); + unsigned flags); /* Return refs which no longer exist on remote */ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map); diff --git a/replace-object.c b/replace-object.c index 9a3cdd809a..7b8a09b5cb 100644 --- a/replace-object.c +++ b/replace-object.c @@ -2,7 +2,7 @@ #include "gettext.h" #include "hex.h" #include "oidmap.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "refs.h" #include "repository.h" diff --git a/replace-object.h b/replace-object.h index 66c41b938b..ba478eb30c 100644 --- a/replace-object.h +++ b/replace-object.h @@ -3,7 +3,7 @@ #include "oidmap.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" struct replace_object { struct oidmap_entry original; diff --git a/repo-settings.c b/repo-settings.c index 67e9cfd2e6..4129f8fb2b 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -20,6 +20,13 @@ static void repo_cfg_int(struct repository *r, const char *key, int *dest, *dest = def; } +static void repo_cfg_ulong(struct repository *r, const char *key, unsigned long *dest, + unsigned long def) +{ + if (repo_config_get_ulong(r, key, dest)) + *dest = def; +} + void prepare_repo_settings(struct repository *r) { int experimental; @@ -151,6 +158,19 @@ void repo_settings_clear(struct repository *r) r->settings = empty; } +unsigned long repo_settings_get_big_file_threshold(struct repository *repo) +{ + if (!repo->settings.big_file_threshold) + repo_cfg_ulong(repo, "core.bigfilethreshold", + &repo->settings.big_file_threshold, 512 * 1024 * 1024); + return repo->settings.big_file_threshold; +} + +void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long value) +{ + repo->settings.big_file_threshold = value; +} + enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo) { const char *value; diff --git a/repo-settings.h b/repo-settings.h index ddc11967e0..2bf24b2597 100644 --- a/repo-settings.h +++ b/repo-settings.h @@ -64,6 +64,7 @@ struct repo_settings { size_t delta_base_cache_limit; size_t packed_git_window_size; size_t packed_git_limit; + unsigned long big_file_threshold; char *hooks_path; }; @@ -88,6 +89,10 @@ int repo_settings_get_warn_ambiguous_refs(struct repository *repo); /* Read the value for "core.hooksPath". */ const char *repo_settings_get_hooks_path(struct repository *repo); +/* Read and set the value for "core.bigFileThreshold". */ +unsigned long repo_settings_get_big_file_threshold(struct repository *repo); +void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long value); + /* Read, set or reset the value for "core.sharedRepository". */ int repo_settings_get_shared_repository(struct repository *repo); void repo_settings_set_shared_repository(struct repository *repo, int value); diff --git a/repository.c b/repository.c index 6cbaf2e3da..9b3d6665fc 100644 --- a/repository.c +++ b/repository.c @@ -1,7 +1,7 @@ #include "git-compat-util.h" #include "abspath.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "config.h" #include "object.h" #include "lockfile.h" @@ -18,7 +18,7 @@ #include "path.h" #include "pathspec.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strmap.h" #define RESOLVED 0 @@ -860,7 +860,7 @@ static int do_plain_rerere(struct repository *r, string_list_insert(rr, path)->util = id; /* Ensure that the directory exists. */ - mkdir_in_gitdir(rerere_path(&buf, id, NULL)); + safe_create_dir_in_gitdir(the_repository, rerere_path(&buf, id, NULL)); } for (i = 0; i < rr->nr; i++) @@ -895,7 +895,8 @@ static int is_rerere_enabled(void) if (rerere_enabled < 0) return rr_cache_exists; - if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache())) + if (!rr_cache_exists && + safe_create_dir_in_gitdir(the_repository, git_path_rr_cache())) die(_("could not create directory '%s'"), git_path_rr_cache()); return 1; } @@ -80,7 +80,7 @@ static int update_refs(const struct reset_head_opts *opts, } if (!ret && run_hook) run_hooks_l(the_repository, "post-checkout", - oid_to_hex(head ? head : null_oid()), + oid_to_hex(head ? head : null_oid(the_hash_algo)), oid_to_hex(oid), "1", NULL); strbuf_release(&msg); return ret; diff --git a/revision.c b/revision.c index c4390f0938..2c36a9c179 100644 --- a/revision.c +++ b/revision.c @@ -8,7 +8,7 @@ #include "hex.h" #include "object-name.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oidset.h" #include "tag.h" #include "blob.h" @@ -59,14 +59,6 @@ implement_shared_commit_slab(revision_sources, char *); static inline int want_ancestry(const struct rev_info *revs); -void show_object_with_name(FILE *out, struct object *obj, const char *name) -{ - fprintf(out, "%s ", oid_to_hex(&obj->oid)); - for (const char *p = name; *p && *p != '\n'; p++) - fputc(*p, out); - fputc('\n', out); -} - static void mark_blob_uninteresting(struct blob *blob) { if (!blob) @@ -2488,10 +2480,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg die(_("options '%s' and '%s' cannot be used together"), "--left-only", "--right-only/--cherry"); revs->left_only = 1; + revs->limited = 1; } else if (!strcmp(arg, "--right-only")) { if (revs->left_only) die(_("options '%s' and '%s' cannot be used together"), "--right-only", "--left-only"); revs->right_only = 1; + revs->limited = 1; } else if (!strcmp(arg, "--cherry")) { if (revs->left_only) die(_("options '%s' and '%s' cannot be used together"), "--cherry", "--left-only"); @@ -3612,7 +3606,8 @@ static void set_children(struct rev_info *revs) void reset_revision_walk(void) { - clear_object_flags(SEEN | ADDED | SHOWN | TOPO_WALK_EXPLORED | TOPO_WALK_INDEGREE); + clear_object_flags(the_repository, + SEEN | ADDED | SHOWN | TOPO_WALK_EXPLORED | TOPO_WALK_INDEGREE); } static int mark_uninteresting(const struct object_id *oid, diff --git a/revision.h b/revision.h index 71e984c452..21c6a69899 100644 --- a/revision.h +++ b/revision.h @@ -489,8 +489,6 @@ void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit); void mark_tree_uninteresting(struct repository *r, struct tree *tree); void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees); -void show_object_with_name(FILE *, struct object *, const char *); - /** * Helpers to check if a reference should be excluded. */ diff --git a/send-pack.c b/send-pack.c index 856a65d5f5..5005689cb5 100644 --- a/send-pack.c +++ b/send-pack.c @@ -4,7 +4,7 @@ #include "date.h" #include "gettext.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pkt-line.h" #include "sideband.h" #include "run-command.h" diff --git a/sequencer.c b/sequencer.c index c625a39111..b5c4043757 100644 --- a/sequencer.c +++ b/sequencer.c @@ -13,7 +13,7 @@ #include "dir.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "object.h" #include "pager.h" #include "commit.h" @@ -265,8 +265,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref) CALLOC_ARRAY(rec, 1); - oidcpy(&rec->before, null_oid()); - oidcpy(&rec->after, null_oid()); + oidcpy(&rec->before, null_oid(the_hash_algo)); + oidcpy(&rec->after, null_oid(the_hash_algo)); /* This may fail, but that's fine, we will keep the null OID. */ refs_read_ref(get_main_ref_store(the_repository), ref, &rec->before); @@ -667,7 +667,7 @@ static int fast_forward_to(struct repository *r, if (!transaction || ref_transaction_update(transaction, "HEAD", to, unborn && !is_rebase_i(opts) ? - null_oid() : from, NULL, NULL, + null_oid(the_hash_algo) : from, NULL, NULL, 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); @@ -781,28 +781,19 @@ static int do_recursive_merge(struct repository *r, for (i = 0; i < opts->xopts.nr; i++) parse_merge_opt(&o, opts->xopts.v[i]); - if (!opts->strategy || !strcmp(opts->strategy, "ort")) { - memset(&result, 0, sizeof(result)); - merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree, - &result); - show_output = !is_rebase_i(opts) || !result.clean; - /* - * TODO: merge_switch_to_result will update index/working tree; - * we only really want to do that if !result.clean || this is - * the final patch to be picked. But determining this is the - * final patch would take some work, and "head_tree" would need - * to be replace with the tree the index matched before we - * started doing any picks. - */ - merge_switch_to_result(&o, head_tree, &result, 1, show_output); - clean = result.clean; - } else { - ensure_full_index(r->index); - clean = merge_trees(&o, head_tree, next_tree, base_tree); - if (is_rebase_i(opts) && clean <= 0) - fputs(o.obuf.buf, stdout); - strbuf_release(&o.obuf); - } + memset(&result, 0, sizeof(result)); + merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree, &result); + show_output = !is_rebase_i(opts) || !result.clean; + /* + * TODO: merge_switch_to_result will update index/working tree; + * we only really want to do that if !result.clean || this is + * the final patch to be picked. But determining this is the + * final patch would take some work, and "head_tree" would need + * to be replace with the tree the index matched before we + * started doing any picks. + */ + merge_switch_to_result(&o, head_tree, &result, 1, show_output); + clean = result.clean; if (clean < 0) { rollback_lock_file(&index_lock); return clean; @@ -1301,7 +1292,7 @@ int update_head_with_reflog(const struct commit *old_head, 0, err); if (!transaction || ref_transaction_update(transaction, "HEAD", new_head, - old_head ? &old_head->object.oid : null_oid(), + old_head ? &old_head->object.oid : null_oid(the_hash_algo), NULL, NULL, 0, sb.buf, err) || ref_transaction_commit(transaction, err)) { ret = -1; @@ -4328,20 +4319,13 @@ static int do_merge(struct repository *r, o.branch2 = ref_name.buf; o.buffer_output = 2; - if (!opts->strategy || !strcmp(opts->strategy, "ort")) { - /* - * TODO: Should use merge_incore_recursive() and - * merge_switch_to_result(), skipping the call to - * merge_switch_to_result() when we don't actually need to - * update the index and working copy immediately. - */ - ret = merge_ort_recursive(&o, - head_commit, merge_commit, bases, - &i); - } else { - ret = merge_recursive(&o, head_commit, merge_commit, bases, - &i); - } + /* + * TODO: Should use merge_incore_recursive() and + * merge_switch_to_result(), skipping the call to + * merge_switch_to_result() when we don't actually need to + * update the index and working copy immediately. + */ + ret = merge_ort_recursive(&o, head_commit, merge_commit, bases, &i); if (ret <= 0) fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); @@ -4352,7 +4336,7 @@ static int do_merge(struct repository *r, goto leave_merge; } /* - * The return value of merge_recursive() is 1 on clean, and 0 on + * The return value of merge_ort_recursive() is 1 on clean, and 0 on * unclean merge. * * Let's reverse that, so that do_merge() returns 0 upon success and @@ -4411,7 +4395,7 @@ static int write_update_refs_state(struct string_list *refs_to_oids) goto cleanup; } - if (safe_create_leading_directories(path)) { + if (safe_create_leading_directories(the_repository, path)) { result = error(_("unable to create leading directories of %s"), path); goto cleanup; @@ -4677,13 +4661,13 @@ static void create_autostash_internal(struct repository *r, strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); if (path) { - if (safe_create_leading_directories_const(path)) + if (safe_create_leading_directories_const(the_repository, path)) die(_("Could not create directory for '%s'"), path); write_file(path, "%s", oid_to_hex(&oid)); } else { refs_update_ref(get_main_ref_store(r), "", refname, - &oid, null_oid(), 0, UPDATE_REFS_DIE_ON_ERR); + &oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR); } printf(_("Created autostash: %s\n"), buf.buf); diff --git a/server-info.c b/server-info.c index 1ca0e00d51..d6cd20a39d 100644 --- a/server-info.c +++ b/server-info.c @@ -11,7 +11,7 @@ #include "packfile.h" #include "path.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "server-info.h" #include "strbuf.h" #include "tempfile.h" @@ -88,7 +88,7 @@ static int update_info_file(struct repository *r, char *path, .old_sb = STRBUF_INIT }; - safe_create_leading_directories(path); + safe_create_leading_directories(r, path); f = mks_tempfile_m(tmp, 0666); if (!f) goto out; @@ -5,7 +5,7 @@ #include "repository.h" #include "tempfile.h" #include "lockfile.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "tag.h" #include "pkt-line.h" @@ -226,7 +226,7 @@ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av, * SHALLOW (excluded) and NOT_SHALLOW (included) should not be * set at this point. But better be safe than sorry. */ - clear_object_flags(both_flags); + clear_object_flags(the_repository, both_flags); is_repository_shallow(the_repository); /* make sure shallows are read */ @@ -613,9 +613,9 @@ static void paint_down(struct paint_info *info, const struct object_id *oid, } } - nr = get_max_object_index(); + nr = get_max_object_index(the_repository); for (i = 0; i < nr; i++) { - struct object *o = get_indexed_object(i); + struct object *o = get_indexed_object(the_repository, i); if (o && o->type == OBJ_COMMIT) o->flags &= ~SEEN; } @@ -675,9 +675,9 @@ void assign_shallow_commits_to_refs(struct shallow_info *info, * Prepare the commit graph to track what refs can reach what * (new) shallow commits. */ - nr = get_max_object_index(); + nr = get_max_object_index(the_repository); for (i = 0; i < nr; i++) { - struct object *o = get_indexed_object(i); + struct object *o = get_indexed_object(the_repository, i); if (!o || o->type != OBJ_COMMIT) continue; diff --git a/streaming.c b/streaming.c index 38839511af..127d6b5d6a 100644 --- a/streaming.c +++ b/streaming.c @@ -10,7 +10,7 @@ #include "streaming.h" #include "repository.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "replace-object.h" #include "packfile.h" @@ -431,7 +431,8 @@ static int istream_source(struct git_istream *st, st->open = open_istream_loose; return 0; case OI_PACKED: - if (!oi.u.packed.is_delta && big_file_threshold < size) { + if (!oi.u.packed.is_delta && + repo_settings_get_big_file_threshold(the_repository) < size) { st->u.in_pack.pack = oi.u.packed.pack; st->u.in_pack.pos = oi.u.packed.offset; st->open = open_istream_pack_non_delta; diff --git a/submodule-config.c b/submodule-config.c index a25059ed7f..8630e27947 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -13,7 +13,7 @@ #include "submodule.h" #include "strbuf.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "parse-options.h" #include "thread-utils.h" #include "tree-walk.h" @@ -831,7 +831,7 @@ static int gitmodules_cb(const char *var, const char *value, parameter.cache = repo->submodule_cache; parameter.treeish_name = NULL; - parameter.gitmodules_oid = null_oid(); + parameter.gitmodules_oid = null_oid(the_hash_algo); parameter.overwrite = 1; return parse_config(var, value, ctx, ¶meter); diff --git a/submodule.c b/submodule.c index 0530e8cf24..ead3fb5dad 100644 --- a/submodule.c +++ b/submodule.c @@ -27,7 +27,7 @@ #include "parse-options.h" #include "object-file.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit-reach.h" #include "read-cache-ll.h" #include "setup.h" @@ -124,7 +124,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) if (is_gitmodules_unmerged(the_repository->index)) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - submodule = submodule_from_path(the_repository, null_oid(), oldpath); + submodule = submodule_from_path(the_repository, null_oid(the_hash_algo), oldpath); if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), oldpath); return -1; @@ -153,7 +153,7 @@ int remove_path_from_gitmodules(const char *path) if (is_gitmodules_unmerged(the_repository->index)) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - submodule = submodule_from_path(the_repository, null_oid(), path); + submodule = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), path); return -1; @@ -204,7 +204,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path) { const struct submodule *submodule = submodule_from_path(the_repository, - null_oid(), + null_oid(the_hash_algo), path); if (submodule) { const char *ignore; @@ -312,7 +312,7 @@ int is_tree_submodule_active(struct repository *repo, int is_submodule_active(struct repository *repo, const char *path) { - return is_tree_submodule_active(repo, null_oid(), path); + return is_tree_submodule_active(repo, null_oid(the_hash_algo), path); } int is_submodule_populated_gently(const char *path, int *return_error_code) @@ -778,7 +778,7 @@ const struct submodule *submodule_from_ce(const struct cache_entry *ce) if (!should_update_submodules()) return NULL; - return submodule_from_path(the_repository, null_oid(), ce->name); + return submodule_from_path(the_repository, null_oid(the_hash_algo), ce->name); } @@ -1062,7 +1062,7 @@ static int submodule_needs_pushing(struct repository *r, const char *path, struct oid_array *commits) { - if (!submodule_has_commits(r, path, null_oid(), commits)) + if (!submodule_has_commits(r, path, null_oid(the_hash_algo), commits)) /* * NOTE: We do consider it safe to return "no" here. The * correct answer would be "We do not know" instead of @@ -1126,7 +1126,7 @@ int find_unpushed_submodules(struct repository *r, const struct submodule *submodule; const char *path = NULL; - submodule = submodule_from_name(r, null_oid(), name->string); + submodule = submodule_from_name(r, null_oid(the_hash_algo), name->string); if (submodule) path = submodule->path; else @@ -1351,7 +1351,7 @@ static void calculate_changed_submodule_paths(struct repository *r, const struct submodule *submodule; const char *path = NULL; - submodule = submodule_from_name(r, null_oid(), name->string); + submodule = submodule_from_name(r, null_oid(the_hash_algo), name->string); if (submodule) path = submodule->path; else @@ -1360,7 +1360,7 @@ static void calculate_changed_submodule_paths(struct repository *r, if (!path) continue; - if (submodule_has_commits(r, path, null_oid(), &cs_data->new_commits)) { + if (submodule_has_commits(r, path, null_oid(the_hash_algo), &cs_data->new_commits)) { changed_submodule_data_clear(cs_data); *name->string = '\0'; } @@ -1602,7 +1602,7 @@ get_fetch_task_from_index(struct submodule_parallel_fetch *spf, if (!S_ISGITLINK(ce->ce_mode)) continue; - task = fetch_task_create(spf, ce->name, null_oid()); + task = fetch_task_create(spf, ce->name, null_oid(the_hash_algo)); if (!task) continue; @@ -2166,7 +2166,7 @@ int submodule_move_head(const char *path, const char *super_prefix, if (old_head && !is_submodule_populated_gently(path, error_code_ptr)) return 0; - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) BUG("could not get submodule information for '%s'", path); @@ -2376,7 +2376,7 @@ static void relocate_single_git_dir_into_superproject(const char *path, real_old_git_dir = real_pathdup(old_git_dir, 1); - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("could not lookup name for submodule '%s'"), path); @@ -2384,7 +2384,7 @@ static void relocate_single_git_dir_into_superproject(const char *path, if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0) die(_("refusing to move '%s' into an existing git dir"), real_old_git_dir); - if (safe_create_leading_directories_const(new_gitdir.buf) < 0) + if (safe_create_leading_directories_const(the_repository, new_gitdir.buf) < 0) die(_("could not create directory '%s'"), new_gitdir.buf); real_new_git_dir = real_pathdup(new_gitdir.buf, 1); @@ -2462,7 +2462,7 @@ void absorb_git_dir_into_superproject(const char *path, * superproject did not rewrite the git file links yet, * fix it now. */ - sub = submodule_from_path(the_repository, null_oid(), path); + sub = submodule_from_path(the_repository, null_oid(the_hash_algo), path); if (!sub) die(_("could not lookup name for submodule '%s'"), path); submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name); @@ -2594,7 +2594,7 @@ int submodule_to_gitdir(struct repository *repo, strbuf_addstr(buf, git_dir); } if (!is_git_directory(buf->buf)) { - sub = submodule_from_path(repo, null_oid(), submodule); + sub = submodule_from_path(repo, null_oid(the_hash_algo), submodule); if (!sub) { ret = -1; goto cleanup; diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c index 14e075c1a1..9aa2c5a592 100644 --- a/t/helper/test-bloom.c +++ b/t/helper/test-bloom.c @@ -44,7 +44,7 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid) print_bloom_filter(filter); } -static const char *bloom_usage = "\n" +static const char *const bloom_usage = "\n" " test-tool bloom get_murmur3 <string>\n" " test-tool bloom get_murmur3_seven_highbit\n" " test-tool bloom generate_filter <string> [<string>...]\n" diff --git a/t/helper/test-date.c b/t/helper/test-date.c index f25512de9a..87d2ad6fca 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -2,7 +2,7 @@ #include "date.h" #include "trace.h" -static const char *usage_msg = "\n" +static const char *const usage_msg = "\n" " test-tool date relative [time_t]...\n" " test-tool date human [time_t]...\n" " test-tool date show:<format> [time_t]...\n" diff --git a/t/helper/test-find-pack.c b/t/helper/test-find-pack.c index 85a69a4e55..76c2f4eba8 100644 --- a/t/helper/test-find-pack.c +++ b/t/helper/test-find-pack.c @@ -15,7 +15,7 @@ * packfiles containing the object is not <n>. */ -static const char *find_pack_usage[] = { +static const char *const find_pack_usage[] = { "test-tool find-pack [--check-count <n>] <object>", NULL }; diff --git a/t/helper/test-getcwd.c b/t/helper/test-getcwd.c index d680038a78..cd4d424079 100644 --- a/t/helper/test-getcwd.c +++ b/t/helper/test-getcwd.c @@ -2,7 +2,7 @@ #include "git-compat-util.h" #include "parse-options.h" -static const char *getcwd_usage[] = { +static const char *const getcwd_usage[] = { "test-tool getcwd", NULL }; diff --git a/t/helper/test-pack-mtimes.c b/t/helper/test-pack-mtimes.c index f8f9afbb5b..fdf1b13437 100644 --- a/t/helper/test-pack-mtimes.c +++ b/t/helper/test-pack-mtimes.c @@ -3,7 +3,7 @@ #include "test-tool.h" #include "hex.h" #include "strbuf.h" -#include "object-store-ll.h" +#include "object-store.h" #include "packfile.h" #include "pack-mtimes.h" #include "setup.h" @@ -24,7 +24,7 @@ static void dump_mtimes(struct packed_git *p) } } -static const char *pack_mtimes_usage = "\n" +static const char *const pack_mtimes_usage = "\n" " test-tool pack-mtimes <pack-name.mtimes>"; int cmd__pack_mtimes(int argc, const char **argv) diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index bfe45ec68b..f2663dd0c0 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -6,7 +6,7 @@ static int boolean = 0; static int integer = 0; -static unsigned long magnitude = 0; +static unsigned long unsigned_integer = 0; static timestamp_t timestamp; static int abbrev = 7; static int verbose = -1; /* unspecified */ @@ -120,20 +120,31 @@ int cmd__parse_options(int argc, const char **argv) }; struct string_list expect = STRING_LIST_INIT_NODUP; struct string_list list = STRING_LIST_INIT_NODUP; + uint16_t u16 = 0; + int16_t i16 = 0; struct option options[] = { OPT_BOOL(0, "yes", &boolean, "get a boolean"), OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), - { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL, - "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + { + .type = OPTION_SET_INT, + .short_name = 'B', + .long_name = "no-fear", + .value = &boolean, + .help = "be brave", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + .defval = 1, + }, OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), OPT_BIT('4', "or4", &boolean, "bitwise-or boolean with ...0100", 4), OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), OPT_GROUP(""), OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER(0, "i16", &i16, "get a 16 bit integer"), OPT_INTEGER('j', NULL, &integer, "get a integer, too"), - OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), + OPT_UNSIGNED('u', "unsigned", &unsigned_integer, "get an unsigned integer"), + OPT_UNSIGNED(0, "u16", &u16, "get a 16 bit unsigned integer"), OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), OPT_CMDMODE(0, "mode1", &integer, "set integer to 1 (cmdmode option)", 1), OPT_CMDMODE(0, "mode2", &integer, "set integer to 2 (cmdmode option)", 2), @@ -155,12 +166,27 @@ int cmd__parse_options(int argc, const char **argv) OPT_GROUP("Magic arguments"), OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", number_callback), - { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH }, - { OPTION_COUNTUP, 0, "ambiguous", &ambiguous, NULL, - "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, - { OPTION_COUNTUP, 0, "no-ambiguous", &ambiguous, NULL, - "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + { + .type = OPTION_COUNTUP, + .short_name = '+', + .value = &boolean, + .help = "same as -b", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + }, + { + .type = OPTION_COUNTUP, + .long_name = "ambiguous", + .value = &ambiguous, + .help = "positive ambiguity", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + }, + { + .type = OPTION_COUNTUP, + .long_name = "no-ambiguous", + .value = &ambiguous, + .help = "negative ambiguity", + .flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG, + }, OPT_GROUP("Standard options"), OPT__ABBREV(&abbrev), OPT__VERBOSE(&verbose, "be verbose"), @@ -188,7 +214,9 @@ int cmd__parse_options(int argc, const char **argv) } show(&expect, &ret, "boolean: %d", boolean); show(&expect, &ret, "integer: %d", integer); - show(&expect, &ret, "magnitude: %lu", magnitude); + show(&expect, &ret, "i16: %"PRIdMAX, (intmax_t) i16); + show(&expect, &ret, "unsigned: %lu", unsigned_integer); + show(&expect, &ret, "u16: %"PRIuMAX, (uintmax_t) u16); show(&expect, &ret, "timestamp: %"PRItime, timestamp); show(&expect, &ret, "string: %s", string ? string : "(not set)"); show(&expect, &ret, "abbrev: %d", abbrev); diff --git a/t/helper/test-partial-clone.c b/t/helper/test-partial-clone.c index a1af9710c3..34f1aee558 100644 --- a/t/helper/test-partial-clone.c +++ b/t/helper/test-partial-clone.c @@ -1,7 +1,7 @@ #include "test-tool.h" #include "hex.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "setup.h" /* diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c index 3703f734f3..8eccc34216 100644 --- a/t/helper/test-proc-receive.c +++ b/t/helper/test-proc-receive.c @@ -6,7 +6,7 @@ #include "sigchain.h" #include "string-list.h" -static const char *proc_receive_usage[] = { +static const char *const proc_receive_usage[] = { "test-tool proc-receive [<options>]", NULL }; diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c index 811dde1cb3..8b413b644b 100644 --- a/t/helper/test-read-graph.c +++ b/t/helper/test-read-graph.c @@ -3,7 +3,7 @@ #include "test-tool.h" #include "commit-graph.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "bloom.h" #include "setup.h" diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index fc63236961..ac81390899 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -4,7 +4,7 @@ #include "hex.h" #include "midx.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "pack-bitmap.h" #include "packfile.h" #include "setup.h" diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index e00fce592b..4cfc7c90b5 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -5,7 +5,7 @@ #include "refs.h" #include "setup.h" #include "worktree.h" -#include "object-store-ll.h" +#include "object-store.h" #include "path.h" #include "repository.h" #include "strbuf.h" @@ -179,7 +179,7 @@ static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv) static int cmd_resolve_ref(struct ref_store *refs, const char **argv) { - struct object_id oid = *null_oid(); + struct object_id oid = *null_oid(the_hash_algo); const char *refname = notnull(*argv++, "refname"); int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags); int flags; diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index 3c72ed985b..b16c0722c8 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -2,10 +2,11 @@ #include "hash.h" #include "hex.h" #include "reftable/system.h" +#include "reftable/reftable-constants.h" #include "reftable/reftable-error.h" #include "reftable/reftable-merged.h" -#include "reftable/reftable-reader.h" #include "reftable/reftable-stack.h" +#include "reftable/reftable-table.h" #include "test-tool.h" static void print_help(void) @@ -20,6 +21,72 @@ static void print_help(void) "\n"); } +static int dump_blocks(const char *tablename) +{ + struct reftable_table_iterator ti = { 0 }; + struct reftable_block_source src = { 0 }; + struct reftable_table *table = NULL; + uint8_t section_type = 0; + int err; + + err = reftable_block_source_from_file(&src, tablename); + if (err < 0) + goto done; + + err = reftable_table_new(&table, &src, tablename); + if (err < 0) + goto done; + + err = reftable_table_iterator_init(&ti, table); + if (err < 0) + goto done; + + printf("header:\n"); + printf(" block_size: %d\n", table->block_size); + + while (1) { + const struct reftable_block *block; + + err = reftable_table_iterator_next(&ti, &block); + if (err < 0) + goto done; + if (err > 0) + break; + + if (block->block_type != section_type) { + const char *section; + switch (block->block_type) { + case REFTABLE_BLOCK_TYPE_LOG: + section = "log"; + break; + case REFTABLE_BLOCK_TYPE_REF: + section = "ref"; + break; + case REFTABLE_BLOCK_TYPE_OBJ: + section = "obj"; + break; + case REFTABLE_BLOCK_TYPE_INDEX: + section = "idx"; + break; + default: + err = -1; + goto done; + } + + section_type = block->block_type; + printf("%s:\n", section); + } + + printf(" - length: %u\n", block->restart_off); + printf(" restarts: %u\n", block->restart_count); + } + +done: + reftable_table_iterator_release(&ti); + reftable_table_decref(table); + return err; +} + static int dump_table(struct reftable_merged_table *mt) { struct reftable_iterator it = { NULL }; @@ -126,19 +193,19 @@ static int dump_reftable(const char *tablename) { struct reftable_block_source src = { 0 }; struct reftable_merged_table *mt = NULL; - struct reftable_reader *r = NULL; + struct reftable_table *table = NULL; int err; err = reftable_block_source_from_file(&src, tablename); if (err < 0) goto done; - err = reftable_reader_new(&r, &src, tablename); + err = reftable_table_new(&table, &src, tablename); if (err < 0) goto done; - err = reftable_merged_table_new(&mt, &r, 1, - reftable_reader_hash_id(r)); + err = reftable_merged_table_new(&mt, &table, 1, + reftable_table_hash_id(table)); if (err < 0) goto done; @@ -146,7 +213,7 @@ static int dump_reftable(const char *tablename) done: reftable_merged_table_free(mt); - reftable_reader_decref(r); + reftable_table_decref(table); return err; } @@ -184,7 +251,7 @@ int cmd__dump_reftable(int argc, const char **argv) arg = argv[1]; if (opt_dump_blocks) { - err = reftable_reader_print_blocks(arg); + err = dump_blocks(arg); } else if (opt_dump_table) { err = dump_reftable(arg); } else if (opt_dump_stack) { diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c index 722b1cbe77..ad37e10034 100644 --- a/t/helper/test-rot13-filter.c +++ b/t/helper/test-rot13-filter.c @@ -324,7 +324,7 @@ static void packet_initialize(void) packet_flush(1); } -static const char *rot13_usage[] = { +static const char *const rot13_usage[] = { "test-tool rot13-filter [--always-delay] --log=<path> <capabilities>", NULL }; diff --git a/t/helper/test-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c index 6dce957153..2710341cd5 100644 --- a/t/helper/test-submodule-nested-repo-config.c +++ b/t/helper/test-submodule-nested-repo-config.c @@ -21,7 +21,7 @@ int cmd__submodule_nested_repo_config(int argc, const char **argv) setup_git_directory(); - if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid())) { + if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid(the_hash_algo))) { die_usage(argv, "Submodule not found."); } diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c index 22e518d229..0133852e1e 100644 --- a/t/helper/test-submodule.c +++ b/t/helper/test-submodule.c @@ -12,33 +12,33 @@ #define TEST_TOOL_CHECK_NAME_USAGE \ "test-tool submodule check-name" -static const char *submodule_check_name_usage[] = { +static const char *const submodule_check_name_usage[] = { TEST_TOOL_CHECK_NAME_USAGE, NULL }; #define TEST_TOOL_CHECK_URL_USAGE \ "test-tool submodule check-url" -static const char *submodule_check_url_usage[] = { +static const char *const submodule_check_url_usage[] = { TEST_TOOL_CHECK_URL_USAGE, NULL }; #define TEST_TOOL_IS_ACTIVE_USAGE \ "test-tool submodule is-active <name>" -static const char *submodule_is_active_usage[] = { +static const char *const submodule_is_active_usage[] = { TEST_TOOL_IS_ACTIVE_USAGE, NULL }; #define TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE \ "test-tool submodule resolve-relative-url <up_path> <remoteurl> <url>" -static const char *submodule_resolve_relative_url_usage[] = { +static const char *const submodule_resolve_relative_url_usage[] = { TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, NULL, }; -static const char *submodule_usage[] = { +static const char *const submodule_usage[] = { TEST_TOOL_CHECK_NAME_USAGE, TEST_TOOL_CHECK_URL_USAGE, TEST_TOOL_IS_ACTIVE_USAGE, diff --git a/t/helper/test-windows-named-pipe.c b/t/helper/test-windows-named-pipe.c index ae52183e63..bd73784ceb 100644 --- a/t/helper/test-windows-named-pipe.c +++ b/t/helper/test-windows-named-pipe.c @@ -3,7 +3,7 @@ #include "strbuf.h" #ifdef GIT_WINDOWS_NATIVE -static const char *usage_string = "<pipe-filename>"; +static const char *const usage_string = "<pipe-filename>"; #define TEST_BUFSIZE (4096) diff --git a/t/lib-merge.sh b/t/lib-merge.sh deleted file mode 100644 index 8734ebfc17..0000000000 --- a/t/lib-merge.sh +++ /dev/null @@ -1,13 +0,0 @@ -# Helper functions used by merge tests. - -test_expect_merge_algorithm () { - status_for_recursive=$1 status_for_ort=$2 - shift 2 - - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_expect_${status_for_ort} "$@" - else - test_expect_${status_for_recursive} "$@" - fi -} diff --git a/t/meson.build b/t/meson.build index 8b3aed14ea..43c9750b88 100644 --- a/t/meson.build +++ b/t/meson.build @@ -12,6 +12,8 @@ clar_test_suites = [ 'unit-tests/u-strbuf.c', 'unit-tests/u-strcmp-offset.c', 'unit-tests/u-strvec.c', + 'unit-tests/u-trailer.c', + 'unit-tests/u-urlmatch-normalization.c', ] clar_sources = [ @@ -56,12 +58,10 @@ unit_test_programs = [ 'unit-tests/t-reftable-block.c', 'unit-tests/t-reftable-merged.c', 'unit-tests/t-reftable-pq.c', - 'unit-tests/t-reftable-reader.c', 'unit-tests/t-reftable-readwrite.c', 'unit-tests/t-reftable-record.c', 'unit-tests/t-reftable-stack.c', - 'unit-tests/t-trailer.c', - 'unit-tests/t-urlmatch-normalization.c', + 'unit-tests/t-reftable-table.c', ] foreach unit_test_program : unit_test_programs diff --git a/t/perf/p7821-grep-engines-fixed.sh b/t/perf/p7821-grep-engines-fixed.sh index 61e41b82cf..66bec284e3 100755 --- a/t/perf/p7821-grep-engines-fixed.sh +++ b/t/perf/p7821-grep-engines-fixed.sh @@ -7,7 +7,7 @@ git-grep. Make sure to include a leading space, e.g. GIT_PERF_7821_GREP_OPTS=' -w'. See p7820-grep-engines.sh for more options to try. -If GIT_PERF_7821_THREADS is set to a list of threads (e.g. '1 4 8' +If GIT_PERF_GREP_THREADS is set to a list of threads (e.g. '1 4 8' etc.) we will test the patterns under those numbers of threads. " @@ -33,13 +33,13 @@ do fi if ! test_have_prereq PERF_GREP_ENGINES_THREADS then - test_perf $prereq "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern" " + test_perf "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern" --prereq "$prereq" " git -c grep.patternType=$engine grep$GIT_PERF_7821_GREP_OPTS $pattern >'out.$engine' || : " else for threads in $GIT_PERF_GREP_THREADS do - test_perf PTHREADS,$prereq "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern with $threads threads" " + test_perf "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern with $threads threads" --prereq "PTHREADS,$prereq" " git -c grep.patternType=$engine -c grep.threads=$threads grep$GIT_PERF_7821_GREP_OPTS $pattern >'out.$engine.$threads' || : " done diff --git a/t/perf/p9210-scalar.sh b/t/perf/p9210-scalar.sh index 265f7cd1fe..56b075e906 100755 --- a/t/perf/p9210-scalar.sh +++ b/t/perf/p9210-scalar.sh @@ -7,7 +7,8 @@ test_perf_large_repo "$TRASH_DIRECTORY/to-clone" test_expect_success 'enable server-side partial clone' ' git -C to-clone config uploadpack.allowFilter true && - git -C to-clone config uploadpack.allowAnySHA1InWant true + git -C to-clone config uploadpack.allowAnySHA1InWant true && + git -C to-clone checkout -B test-branch ' test_perf 'scalar clone' ' diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 8ab6d9c469..39c3728445 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -25,7 +25,19 @@ TEST_OUTPUT_DIRECTORY=$(pwd) TEST_NO_CREATE_REPO=t TEST_NO_MALLOC_CHECK=t +# GIT-BUILD-OPTIONS, sourced by test-lib.sh, overwrites the `GIT_PERF_*` +# values that are set by the user (if any). Let's stash them away as +# `eval`-able assignments. +git_perf_settings="$(env | + sed -n "/^GIT_PERF_/{ + # escape all single-quotes in the value + s/'/'\\\\''/g + # turn this into an eval-able assignment + s/^\\([^=]*=\\)\\(.*\\)/\\1'\\2'/p + }")" + . ../test-lib.sh +eval "$git_perf_settings" unset GIT_CONFIG_NOSYSTEM GIT_CONFIG_SYSTEM="$TEST_DIRECTORY/perf/config" diff --git a/t/t0001-init.sh b/t/t0001-init.sh index c49d9e0d38..f11a40811f 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -830,6 +830,14 @@ test_expect_success 'advice on unconfigured init.defaultBranch' ' test_grep "<YELLOW>hint: " decoded ' +test_expect_success 'advice on unconfigured init.defaultBranch disabled' ' + test_when_finished "rm -rf no-advice" && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c advice.defaultBranchName=false init no-advice 2>err && + test_grep ! "hint: " err +' + test_expect_success 'overridden default main branch name (env)' ' test_config_global init.defaultBranch nmb && GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=env git init main-branch-env && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 2fe3522305..ca55ea8228 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -22,8 +22,10 @@ usage: test-tool parse-options <options> -i, --[no-]integer <n> get a integer + --[no-]i16 <n> get a 16 bit integer -j <n> get a integer, too - -m, --magnitude <n> get a magnitude + -u, --unsigned <n> get an unsigned integer + --u16 <n> get a 16 bit unsigned integer --[no-]set23 set integer to 23 --mode1 set integer to 1 (cmdmode option) --mode2 set integer to 2 (cmdmode option) @@ -111,32 +113,36 @@ test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown_i18n --no-no-fear test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt' -test_expect_success 'OPT_INT() negative' 'check integer: -2345 -i -2345' +test_expect_success 'OPT_INTEGER() negative' 'check integer: -2345 -i -2345' +test_expect_success 'OPT_INTEGER() kilo' 'check integer: 239616 -i 234k' +test_expect_success 'OPT_INTEGER() negative kilo' 'check integer: -239616 -i -234k' -test_expect_success 'OPT_MAGNITUDE() simple' ' - check magnitude: 2345678 -m 2345678 +test_expect_success 'OPT_UNSIGNED() simple' ' + check unsigned: 2345678 -u 2345678 ' -test_expect_success 'OPT_MAGNITUDE() kilo' ' - check magnitude: 239616 -m 234k +test_expect_success 'OPT_UNSIGNED() kilo' ' + check unsigned: 239616 -u 234k ' -test_expect_success 'OPT_MAGNITUDE() mega' ' - check magnitude: 104857600 -m 100m +test_expect_success 'OPT_UNSIGNED() mega' ' + check unsigned: 104857600 -u 100m ' -test_expect_success 'OPT_MAGNITUDE() giga' ' - check magnitude: 1073741824 -m 1g +test_expect_success 'OPT_UNSIGNED() giga' ' + check unsigned: 1073741824 -u 1g ' -test_expect_success 'OPT_MAGNITUDE() 3giga' ' - check magnitude: 3221225472 -m 3g +test_expect_success 'OPT_UNSIGNED() 3giga' ' + check unsigned: 3221225472 -u 3g ' cat >expect <<\EOF boolean: 2 integer: 1729 -magnitude: 16384 +i16: 0 +unsigned: 16384 +u16: 0 timestamp: 0 string: 123 abbrev: 7 @@ -147,7 +153,7 @@ file: prefix/my.file EOF test_expect_success 'short options' ' - test-tool parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \ + test-tool parse-options -s123 -b -i 1729 -u 16k -b -vv -n -F my.file \ >output 2>output.err && test_cmp expect output && test_must_be_empty output.err @@ -156,7 +162,9 @@ test_expect_success 'short options' ' cat >expect <<\EOF boolean: 2 integer: 1729 -magnitude: 16384 +i16: 9000 +unsigned: 16384 +u16: 32768 timestamp: 0 string: 321 abbrev: 10 @@ -167,8 +175,8 @@ file: prefix/fi.le EOF test_expect_success 'long options' ' - test-tool parse-options --boolean --integer 1729 --magnitude 16k \ - --boolean --string2=321 --verbose --verbose --no-dry-run \ + test-tool parse-options --boolean --integer 1729 --i16 9000 --unsigned 16k \ + --u16 32k --boolean --string2=321 --verbose --verbose --no-dry-run \ --abbrev=10 --file fi.le --obsolete \ >output 2>output.err && test_must_be_empty output.err && @@ -179,7 +187,9 @@ test_expect_success 'abbreviate to something longer than SHA1 length' ' cat >expect <<-EOF && boolean: 0 integer: 0 - magnitude: 0 + i16: 0 + unsigned: 0 + u16: 0 timestamp: 0 string: (not set) abbrev: 100 @@ -253,7 +263,9 @@ test_expect_success 'superfluous value provided: cmdmode' ' cat >expect <<\EOF boolean: 1 integer: 13 -magnitude: 0 +i16: 0 +unsigned: 0 +u16: 0 timestamp: 0 string: 123 abbrev: 7 @@ -276,7 +288,9 @@ test_expect_success 'intermingled arguments' ' cat >expect <<\EOF boolean: 0 integer: 2 -magnitude: 0 +i16: 0 +unsigned: 0 +u16: 0 timestamp: 0 string: (not set) abbrev: 7 @@ -343,7 +357,9 @@ cat >expect <<\EOF Callback: "four", 0 boolean: 5 integer: 4 -magnitude: 0 +i16: 0 +unsigned: 0 +u16: 0 timestamp: 0 string: (not set) abbrev: 7 @@ -368,7 +384,9 @@ test_expect_success 'OPT_CALLBACK() and callback errors work' ' cat >expect <<\EOF boolean: 1 integer: 23 -magnitude: 0 +i16: 0 +unsigned: 0 +u16: 0 timestamp: 0 string: (not set) abbrev: 7 @@ -447,7 +465,9 @@ test_expect_success 'OPT_NUMBER_CALLBACK() works' ' cat >expect <<\EOF boolean: 0 integer: 0 -magnitude: 0 +i16: 0 +unsigned: 0 +u16: 0 timestamp: 0 string: (not set) abbrev: 7 @@ -771,16 +791,35 @@ test_expect_success 'subcommands are incompatible with KEEP_DASHDASH unless in c grep ^BUG err ' -test_expect_success 'negative magnitude' ' - test_must_fail test-tool parse-options --magnitude -1 >out 2>err && +test_expect_success 'negative unsigned' ' + test_must_fail test-tool parse-options --unsigned -1 >out 2>err && grep "non-negative integer" err && test_must_be_empty out ' -test_expect_success 'magnitude with units but no numbers' ' - test_must_fail test-tool parse-options --magnitude m >out 2>err && +test_expect_success 'unsigned with units but no numbers' ' + test_must_fail test-tool parse-options --unsigned m >out 2>err && grep "non-negative integer" err && test_must_be_empty out ' +test_expect_success 'i16 limits range' ' + test-tool parse-options --i16 32767 >out && + test_grep "i16: 32767" out && + test_must_fail test-tool parse-options --i16 32768 2>err && + test_grep "value 32768 for option .i16. not in range \[-32768,32767\]" err && + + test-tool parse-options --i16 -32768 >out && + test_grep "i16: -32768" out && + test_must_fail test-tool parse-options --i16 -32769 2>err && + test_grep "value -32769 for option .i16. not in range \[-32768,32767\]" err +' + +test_expect_success 'u16 limits range' ' + test-tool parse-options --u16 65535 >out && + test_grep "u16: 65535" out && + test_must_fail test-tool parse-options --u16 65536 2>err && + test_grep "value 65536 for option .u16. not in range \[0,65535\]" err +' + test_done diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index 42aa1592f8..6447920c9b 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh @@ -93,6 +93,9 @@ test_expect_success 'many refs results in multiple blocks' ' restarts: 3 - length: 3289 restarts: 3 + idx: + - length: 103 + restarts: 1 EOF test-tool dump-reftable -b .git/reftable/*.ref >actual && test_cmp expect actual @@ -241,6 +244,9 @@ test_expect_success 'object index gets written by default with ref index' ' restarts: 1 - length: 80 restarts: 1 + idx: + - length: 55 + restarts: 2 obj: - length: 11 restarts: 1 @@ -277,6 +283,9 @@ test_expect_success 'object index can be disabled' ' restarts: 1 - length: 80 restarts: 1 + idx: + - length: 55 + restarts: 2 EOF test-tool dump-reftable -b .git/reftable/*.ref >actual && test_cmp expect actual diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 0a22b0a7b8..ce8b27bf54 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -903,6 +903,59 @@ test_expect_success 'cat-file -t and -s on corrupt loose object' ' ) ' +test_expect_success 'truncated object with --allow-unknown-type' - <<\EOT + objtype='a really long type name that exceeds the 32-byte limit' && + blob=$(git hash-object -w --literally -t "$objtype" /dev/null) && + objpath=.git/objects/$(test_oid_to_path "$blob") && + + # We want to truncate the object far enough in that we don't hit the + # end while inflating the first 32 bytes (since we want to have to dig + # for the trailing NUL of the header). But we don't want to go too far, + # since our header isn't very big. And of course we are counting + # deflated zlib bytes in the on-disk file, so it's a bit of a guess. + # Empirically 50 seems to work. + mv "$objpath" obj.bak && + test_when_finished 'mv obj.bak "$objpath"' && + test_copy_bytes 50 <obj.bak >"$objpath" && + + test_must_fail git cat-file --allow-unknown-type -t $blob 2>err && + test_grep "unable to unpack $blob header" err +EOT + +test_expect_success 'object reading handles zlib dictionary' - <<\EOT + echo 'content that will be recompressed' >file && + blob=$(git hash-object -w file) && + objpath=.git/objects/$(test_oid_to_path "$blob") && + + # Recompress a loose object using a precomputed zlib dictionary. + # This was originally done with: + # + # perl -MCompress::Raw::Zlib -e ' + # binmode STDIN; + # binmode STDOUT; + # my $data = do { local $/; <STDIN> }; + # my $in = new Compress::Raw::Zlib::Inflate; + # my $de = new Compress::Raw::Zlib::Deflate( + # -Dictionary => "anything" + # ); + # $in->inflate($data, $raw); + # $de->deflate($raw, $out); + # print $out; + # ' <obj.bak >$objpath + # + # but we do not want to require the perl module for all test runs (nor + # carry a custom t/helper program that uses zlib features we don't + # otherwise care about). + mv "$objpath" obj.bak && + test_when_finished 'mv obj.bak "$objpath"' && + printf '\170\273\017\112\003\143' >$objpath && + + test_must_fail git cat-file blob $blob 2>err && + test_grep ! 'too long' err && + test_grep 'error: unable to unpack' err && + test_grep 'error: inflate: needs dictionary' err +EOT + # Tests for git cat-file --follow-symlinks test_expect_success 'prep for symlink tests' ' echo_without_newline "$hello_content" >morx && @@ -1357,4 +1410,103 @@ test_expect_success PERL_IPC_OPEN2 '--batch-command info is unbuffered by defaul perl -e "$perl_script" -- --batch-command $hello_oid "$expect" "info " ' +test_expect_success 'setup for objects filter' ' + git init repo && + ( + # Seed the repository with four different sets of objects: + # + # - The first set is fully packed and has a bitmap. + # - The second set is packed, but has no bitmap. + # - The third set is loose. + # - The fourth set is loose and contains big objects. + # + # This ensures that we cover all these types as expected. + cd repo && + test_commit first && + git repack -Adb && + test_commit second && + git repack -d && + test_commit third && + + for n in 1000 10000 + do + printf "%"$n"s" X >large.$n || return 1 + done && + git add large.* && + git commit -m fourth + ) +' + +test_expect_success 'objects filter with unknown option' ' + cat >expect <<-EOF && + fatal: invalid filter-spec ${SQ}unknown${SQ} + EOF + test_must_fail git -C repo cat-file --filter=unknown 2>err && + test_cmp expect err +' + +for option in sparse:oid=1234 tree:1 sparse:path=x +do + test_expect_success "objects filter with unsupported option $option" ' + case "$option" in + tree:1) + echo "usage: objects filter not supported: ${SQ}tree${SQ}" >expect + ;; + sparse:path=x) + echo "fatal: sparse:path filters support has been dropped" >expect + ;; + *) + option_name=$(echo "$option" | cut -d= -f1) && + printf "usage: objects filter not supported: ${SQ}%s${SQ}\n" "$option_name" >expect + ;; + esac && + test_must_fail git -C repo cat-file --filter=$option 2>err && + test_cmp expect err + ' +done + +test_expect_success 'objects filter: disabled' ' + git -C repo cat-file --batch-check="%(objectname)" --batch-all-objects --no-filter >actual && + sort actual >actual.sorted && + git -C repo rev-list --objects --no-object-names --all >expect && + sort expect >expect.sorted && + test_cmp expect.sorted actual.sorted +' + +test_objects_filter () { + filter="$1" + + test_expect_success "objects filter: $filter" ' + git -C repo cat-file --batch-check="%(objectname)" --batch-all-objects --filter="$filter" >actual && + sort actual >actual.sorted && + git -C repo rev-list --objects --no-object-names --all --filter="$filter" --filter-provided-objects >expect && + sort expect >expect.sorted && + test_cmp expect.sorted actual.sorted + ' + + test_expect_success "objects filter prints excluded objects: $filter" ' + # Find all objects that would be excluded by the current filter. + git -C repo rev-list --objects --no-object-names --all >all && + git -C repo rev-list --objects --no-object-names --all --filter="$filter" --filter-provided-objects >filtered && + sort all >all.sorted && + sort filtered >filtered.sorted && + comm -23 all.sorted filtered.sorted >expected.excluded && + test_line_count -gt 0 expected.excluded && + + git -C repo cat-file --batch-check="%(objectname)" --filter="$filter" <expected.excluded >actual && + awk "/excluded/{ print \$1 }" actual | sort >actual.excluded && + test_cmp expected.excluded actual.excluded + ' +} + +test_objects_filter "blob:none" +test_objects_filter "blob:limit=1" +test_objects_filter "blob:limit=500" +test_objects_filter "blob:limit=1000" +test_objects_filter "blob:limit=1k" +test_objects_filter "object:type=blob" +test_objects_filter "object:type=commit" +test_objects_filter "object:type=tag" +test_objects_filter "object:type=tree" + test_done diff --git a/t/t1050-large.sh b/t/t1050-large.sh index c71932b024..5be273611a 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -6,7 +6,8 @@ test_description='adding and checking out large blobs' . ./test-lib.sh test_expect_success 'core.bigFileThreshold must be non-negative' ' - test_must_fail git -c core.bigFileThreshold=-1 rev-parse >out 2>err && + : >input && + test_must_fail git -c core.bigFileThreshold=-1 hash-object input >out 2>err && grep "bad numeric config value" err && test_must_be_empty out ' diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index a4c7c41fc0..f9b448792c 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1486,7 +1486,6 @@ test_expect_success 'sparse-index is not expanded' ' ensure_not_expanded checkout -f update-deep && test_config -C sparse-index pull.twohead ort && ( - sane_unset GIT_TEST_MERGE_ALGORITHM && for OPERATION in "merge -m merge" cherry-pick rebase do ensure_not_expanded merge -m merge update-folder1 && @@ -1506,7 +1505,6 @@ test_expect_success 'sparse-index is not expanded: merge conflict in cone' ' done && ( - sane_unset GIT_TEST_MERGE_ALGORITHM && git -C sparse-index config pull.twohead ort && ensure_not_expanded ! merge -m merged expand-right ) diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 29045aad43..d29d23cb89 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2066,6 +2066,239 @@ do grep "$(git rev-parse $a) $(git rev-parse $a)" actual ' + test_expect_success "stdin $type batch-updates" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit commit && + head=$(git rev-parse HEAD) && + + format_command $type "update refs/heads/ref1" "$head" "$Z" >stdin && + format_command $type "update refs/heads/ref2" "$head" "$Z" >>stdin && + git update-ref $type --stdin --batch-updates <stdin && + echo $head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual + ) + ' + + test_expect_success "stdin $type batch-updates with invalid new_oid" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + git update-ref refs/heads/ref2 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$(test_oid 001)" "$head" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual && + test_grep -q "invalid new value provided" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with non-commit new_oid" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + head_tree=$(git rev-parse HEAD^{tree}) && + git update-ref refs/heads/ref1 $head && + git update-ref refs/heads/ref2 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$head_tree" "$head" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual && + test_grep -q "invalid new value provided" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with non-existent ref" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + test_must_fail git rev-parse refs/heads/ref2 && + test_grep -q "reference does not exist" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with dangling symref" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + git symbolic-ref refs/heads/ref2 refs/heads/nonexistent && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin && + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + test_must_fail git rev-parse refs/heads/ref2 && + test_grep -q "reference does not exist" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with regular ref as symref" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + git update-ref refs/heads/ref2 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "symref-update refs/heads/ref2" "$old_head" "ref" "refs/heads/nonexistent" >>stdin && + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + echo $head >expect && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual && + test_grep -q "expected symref but found regular ref" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with invalid old_oid" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + git update-ref refs/heads/ref2 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$old_head" "$Z" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual && + test_grep -q "reference already exists" stdout + ) + ' + + test_expect_success "stdin $type batch-updates with incorrect old oid" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref1 $head && + git update-ref refs/heads/ref2 $head && + + format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref2" "$head" "$old_head" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref1 >actual && + test_cmp expect actual && + echo $head >expect && + git rev-parse refs/heads/ref2 >actual && + test_cmp expect actual && + test_grep -q "incorrect old value provided" stdout + ) + ' + + test_expect_success "stdin $type batch-updates refname conflict" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref/foo $head && + + format_command $type "update refs/heads/ref/foo" "$old_head" "$head" >stdin && + format_command $type "update refs/heads/ref" "$old_head" "" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/ref/foo >actual && + test_cmp expect actual && + test_grep -q "refname conflict" stdout + ) + ' + + test_expect_success "stdin $type batch-updates refname conflict new ref" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + git update-ref refs/heads/ref/foo $head && + + format_command $type "update refs/heads/foo" "$old_head" "" >stdin && + format_command $type "update refs/heads/ref" "$old_head" "" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + echo $old_head >expect && + git rev-parse refs/heads/foo >actual && + test_cmp expect actual && + test_grep -q "refname conflict" stdout + ) + ' done test_expect_success 'update-ref should also create reflog for HEAD' ' diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 9d698b3cc3..9da3650e91 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -196,7 +196,7 @@ test_expect_success 'show-ref --verify with dangling ref' ' remove_object() { file=$(sha1_file "$*") && - test -e "$file" && + test_path_is_file "$file" && rm -f "$file" } && diff --git a/t/t1408-packed-refs.sh b/t/t1408-packed-refs.sh index 41ba1f1d7f..833477f0fa 100755 --- a/t/t1408-packed-refs.sh +++ b/t/t1408-packed-refs.sh @@ -42,4 +42,19 @@ test_expect_success 'no error from stale entry in packed-refs' ' test_cmp expect actual ' +test_expect_success 'list packed refs with unicode characters' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit --no-tag A && + git update-ref refs/heads/ HEAD && + git update-ref refs/heads/z HEAD && + git pack-refs --all && + printf "%s commit\trefs/heads/z\n" $(git rev-parse HEAD) >expect && + git for-each-ref refs/heads/z >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 388fdf9ae5..42b501f163 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -315,9 +315,9 @@ test_expect_success 'git reflog expire unknown reference' ' test_config gc.reflogexpireunreachable never && test_must_fail git reflog expire main@{123} 2>stderr && - test_grep "points nowhere" stderr && + test_grep "error: reflog could not be found: ${SQ}main@{123}${SQ}" stderr && test_must_fail git reflog expire does-not-exist 2>stderr && - test_grep "points nowhere" stderr + test_grep "error: reflog could not be found: ${SQ}does-not-exist${SQ}" stderr ' test_expect_success 'checkout should not delete log for packed ref' ' @@ -551,4 +551,126 @@ test_expect_success 'reflog with invalid object ID can be listed' ' ) ' +test_expect_success 'reflog drop non-existent ref' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog exists refs/heads/non-existent && + test_must_fail git reflog drop refs/heads/non-existent 2>stderr && + test_grep "error: reflog could not be found: ${SQ}refs/heads/non-existent${SQ}" stderr + ) +' + +test_expect_success 'reflog drop' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit_bulk --ref=refs/heads/branch 1 && + git reflog exists refs/heads/main && + git reflog exists refs/heads/branch && + git reflog drop refs/heads/main && + test_must_fail git reflog exists refs/heads/main && + git reflog exists refs/heads/branch + ) +' + +test_expect_success 'reflog drop multiple references' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit_bulk --ref=refs/heads/branch 1 && + git reflog exists refs/heads/main && + git reflog exists refs/heads/branch && + git reflog drop refs/heads/main refs/heads/branch && + test_must_fail git reflog exists refs/heads/main && + test_must_fail git reflog exists refs/heads/branch + ) +' + +test_expect_success 'reflog drop multiple references some non-existent' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit_bulk --ref=refs/heads/branch 1 && + git reflog exists refs/heads/main && + git reflog exists refs/heads/branch && + test_must_fail git reflog exists refs/heads/non-existent && + test_must_fail git reflog drop refs/heads/main refs/heads/non-existent refs/heads/branch 2>stderr && + test_must_fail git reflog exists refs/heads/main && + test_must_fail git reflog exists refs/heads/branch && + test_must_fail git reflog exists refs/heads/non-existent && + test_grep "error: reflog could not be found: ${SQ}refs/heads/non-existent${SQ}" stderr + ) +' + +test_expect_success 'reflog drop --all' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit_bulk --ref=refs/heads/branch 1 && + git reflog exists refs/heads/main && + git reflog exists refs/heads/branch && + git reflog drop --all && + test_must_fail git reflog exists refs/heads/main && + test_must_fail git reflog exists refs/heads/branch + ) +' + +test_expect_success 'reflog drop --all multiple worktrees' ' + test_when_finished "rm -rf repo" && + test_when_finished "rm -rf wt" && + git init repo && + ( + cd repo && + test_commit A && + git worktree add ../wt && + test_commit_bulk -C ../wt --ref=refs/heads/branch 1 && + git reflog exists refs/heads/main && + git reflog exists refs/heads/branch && + git reflog drop --all && + test_must_fail git reflog exists refs/heads/main && + test_must_fail git reflog exists refs/heads/branch + ) +' + +test_expect_success 'reflog drop --all --single-worktree' ' + test_when_finished "rm -rf repo" && + test_when_finished "rm -rf wt" && + git init repo && + ( + cd repo && + test_commit A && + git worktree add ../wt && + test_commit -C ../wt foobar && + git reflog exists refs/heads/main && + git reflog exists refs/heads/wt && + test-tool ref-store worktree:wt reflog-exists HEAD && + git reflog drop --all --single-worktree && + test_must_fail git reflog exists refs/heads/main && + test_must_fail git reflog exists refs/heads/wt && + test_must_fail test-tool ref-store worktree:main reflog-exists HEAD && + test-tool ref-store worktree:wt reflog-exists HEAD + ) +' + +test_expect_success 'reflog drop --all with reference' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_must_fail git reflog drop --all refs/heads/main 2>stderr && + test_grep "usage: references specified along with --all" stderr + ) +' + test_done diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh index dbd8cd6906..6824581317 100755 --- a/t/t1517-outside-repo.sh +++ b/t/t1517-outside-repo.sh @@ -107,4 +107,11 @@ test_expect_success LIBCURL 'remote-http outside repository' ' test_grep "^error: remote-curl" actual ' +test_expect_success 'update-server-info does not crash with -h' ' + test_expect_code 129 git update-server-info -h >usage && + test_grep "[Uu]sage: git update-server-info " usage && + test_expect_code 129 nongit git update-server-info -h >usage && + test_grep "[Uu]sage: git update-server-info " usage +' + test_done diff --git a/t/t2501-cwd-empty.sh b/t/t2501-cwd-empty.sh index f6d8d7d03d..be9140bbaa 100755 --- a/t/t2501-cwd-empty.sh +++ b/t/t2501-cwd-empty.sh @@ -117,8 +117,6 @@ test_expect_success 'merge fails if cwd needs to be removed; recursive friendly' grep "Refusing to remove the current working directory" error ' -GIT_TEST_MERGE_ALGORITHM=ort - test_expect_success 'merge fails if cwd needs to be removed' ' test_required_dir_removal git merge fd_conflict ' diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index f22d1ddead..85a7932697 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -8,11 +8,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh -if test "$GIT_TEST_MERGE_ALGORITHM" != ort -then - KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 - KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 -fi test_submodule_switch "cherry-pick" test_expect_success 'unrelated submodule/file conflict is ignored' ' diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh index 8bfe3ed246..32e15c72ee 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -30,10 +30,6 @@ git_revert () { git revert HEAD } -if test "$GIT_TEST_MERGE_ALGORITHM" != ort -then - KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 -fi test_submodule_switch_func "git_revert" test_done diff --git a/t/t4018/ini-section b/t/t4018/ini-section new file mode 100644 index 0000000000..c895ad9b4f --- /dev/null +++ b/t/t4018/ini-section @@ -0,0 +1,5 @@ +[RIGHT] + # comment + ; comment + name = value + ChangeMe diff --git a/t/t4018/ini-section-noindent b/t/t4018/ini-section-noindent new file mode 100644 index 0000000000..733d23c801 --- /dev/null +++ b/t/t4018/ini-section-noindent @@ -0,0 +1,5 @@ +[RIGHT] +# comment +; comment +name = value +ChangeMe diff --git a/t/t4018/ini-section-same-line b/t/t4018/ini-section-same-line new file mode 100644 index 0000000000..522a1fa4a1 --- /dev/null +++ b/t/t4018/ini-section-same-line @@ -0,0 +1,4 @@ +[RIGHT] name = value + # comment + ; comment + ChangeMe diff --git a/t/t4018/ini-subsection b/t/t4018/ini-subsection new file mode 100644 index 0000000000..3d47349e60 --- /dev/null +++ b/t/t4018/ini-subsection @@ -0,0 +1,12 @@ +[LEFT] + + [LEFT "CENTER"] + # comment + ; comment + name = value + + [LEFT "RIGHT"] + # comment + ; comment + name = value + ChangeMe diff --git a/t/t4018/ini-subsection-noindent b/t/t4018/ini-subsection-noindent new file mode 100644 index 0000000000..698ea00ea3 --- /dev/null +++ b/t/t4018/ini-subsection-noindent @@ -0,0 +1,12 @@ +[LEFT] + +[LEFT "CENTER"] +# comment +; comment +name = value + +[LEFT "RIGHT"] +# comment +; comment +name = value +ChangeMe diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh index c6c94aef14..966882ce91 100755 --- a/t/t4069-remerge-diff.sh +++ b/t/t4069-remerge-diff.sh @@ -4,13 +4,6 @@ test_description='remerge-diff handling' . ./test-lib.sh -# This test is ort-specific -if test "${GIT_TEST_MERGE_ALGORITHM}" != ort -then - skip_all="GIT_TEST_MERGE_ALGORITHM != ort" - test_done -fi - test_expect_success 'setup basic merges' ' test_write_lines 1 2 3 4 5 6 7 8 9 >numbers && git add numbers && diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh index 44f7d07759..f9c5883a7f 100755 --- a/t/t4301-merge-tree-write-tree.sh +++ b/t/t4301-merge-tree-write-tree.sh @@ -4,13 +4,6 @@ test_description='git merge-tree --write-tree' . ./test-lib.sh -# This test is ort-specific -if test "$GIT_TEST_MERGE_ALGORITHM" != "ort" -then - skip_all="GIT_TEST_MERGE_ALGORITHM != ort" - test_done -fi - test_expect_success setup ' test_write_lines 1 2 3 4 5 >numbers && echo hello >greeting && diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 82fccf8e36..bef0250e89 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -499,7 +499,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' ' cd test && git fetch two "refs/heads/*:refs/remotes/two/*" && git remote set-head --auto two >output 2>&1 && - echo "${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}" >expect && + echo "${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}" >expect && test_cmp expect output ) ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 432a2264e6..ebc696546b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -125,7 +125,10 @@ test_expect_success "fetch test followRemoteHEAD never" ' cd two && git update-ref --no-deref -d refs/remotes/origin/HEAD && git config set remote.origin.followRemoteHEAD "never" && - git fetch && + GIT_TRACE_PACKET=$PWD/trace.out git fetch && + # Confirm that we do not even ask for HEAD when we are + # not going to act on it. + test_grep ! "ref-prefix HEAD" trace.out && test_must_fail git rev-parse --verify refs/remotes/origin/HEAD ) ' @@ -256,6 +259,20 @@ test_expect_success "fetch test followRemoteHEAD always" ' ) ' +test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' + test_when_finished "git config unset remote.origin.followRemoteHEAD" && + ( + cd "$D" && + cd two && + git remote set-head origin other && + git config set remote.origin.followRemoteHEAD always && + git fetch origin refs/heads/main:refs/remotes/origin/main && + echo refs/remotes/origin/other >expect && + git symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual + ) +' + test_expect_success 'fetch --prune on its own works as expected' ' cd "$D" && git clone . prune && @@ -543,6 +560,19 @@ test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' ' test_cmp expected atomic/.git/FETCH_HEAD ' +test_expect_success REFFILES 'fetch --atomic fails transaction if reference locked' ' + test_when_finished "rm -rf upstream repo" && + + git init upstream && + git -C upstream commit --allow-empty -m 1 && + git -C upstream switch -c foobar && + git clone --mirror upstream repo && + git -C upstream commit --allow-empty -m 2 && + touch repo/refs/heads/foobar.lock && + + test_must_fail git -C repo fetch --atomic origin +' + test_expect_success '--refmap="" ignores configured refspec' ' cd "$TRASH_DIRECTORY" && git clone "$D" remote-refs && diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index f7650e8475..45f384dd32 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -45,11 +45,6 @@ git_pull_noff () { $2 git pull --no-ff } -if test "$GIT_TEST_MERGE_ALGORITHM" != ort -then - KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 - KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 -fi test_submodule_switch_func "git_pull_noff" test_expect_success 'setup' ' diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 4605703496..2397f8fa61 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -156,11 +156,10 @@ test_expect_success 'cloning a local path with --no-local does not hardlink' ' test_expect_success 'cloning a local path with --no-local from a different user succeeds' ' git clone --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ --no-local a nonlocal-otheruser 2>err && - ! repo_is_hardlinked nonlocal-otheruser && + ! repo_is_hardlinked nonlocal-otheruser/.git && # Verify that this is a git repository. git -C nonlocal-otheruser rev-parse --show-toplevel && - ! test_grep "detected dubious ownership" err - + test_grep ! "detected dubious ownership" err ' test_expect_success 'cloning locally respects "-u" for fetching refs' ' diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh index 82e3621ec5..d709bea753 100755 --- a/t/t5607-clone-bundle.sh +++ b/t/t5607-clone-bundle.sh @@ -211,4 +211,16 @@ test_expect_success 'git bundle v3 rejects unknown capabilities' ' test_grep "unknown capability .unknown=silly." output ' +test_expect_success 'cloning bundle suppresses default branch name advice' ' + test_when_finished "rm -rf bundle-repo clone-repo" && + + git init bundle-repo && + git -C bundle-repo commit --allow-empty -m init && + git -C bundle-repo bundle create repo.bundle --all && + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git clone --single-branch bundle-repo/repo.bundle clone-repo 2>err && + + test_grep ! "hint: " err +' + test_done diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 6289a2e8b0..fec16448cf 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -182,4 +182,70 @@ test_expect_success 'rev-list --unpacked' ' test_cmp expect actual ' +test_expect_success 'rev-list one-sided unrelated symmetric diff' ' + test_tick && + git commit --allow-empty -m xyz && + git branch cmp && + git rebase --force-rebase --root && + + git rev-list --left-only HEAD...cmp >head && + git rev-list --right-only HEAD...cmp >cmp && + + sort head >head.sorted && + sort cmp >cmp.sorted && + comm -12 head.sorted cmp.sorted >actual && + test_line_count = 0 actual +' + +test_expect_success 'rev-list -z' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD~) && + oid2=$(git -C repo rev-parse HEAD) && + + printf "%s\0%s\0" "$oid2" "$oid1" >expect && + git -C repo rev-list -z HEAD >actual && + + test_cmp expect actual +' + +test_expect_success 'rev-list -z --objects' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD:1.t) && + oid2=$(git -C repo rev-parse HEAD:2.t) && + path1=1.t && + path2=2.t && + + printf "%s\0path=%s\0%s\0path=%s\0" "$oid1" "$path1" "$oid2" "$path2" \ + >expect && + git -C repo rev-list -z --objects HEAD:1.t HEAD:2.t >actual && + + test_cmp expect actual +' + +test_expect_success 'rev-list -z --boundary' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD~) && + oid2=$(git -C repo rev-parse HEAD) && + + printf "%s\0%s\0boundary=yes\0" "$oid2" "$oid1" >expect && + git -C repo rev-list -z --boundary HEAD~.. >actual && + + test_cmp expect actual +' + test_done diff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh index b3807e8f35..500c81b8a1 100755 --- a/t/t6020-bundle-misc.sh +++ b/t/t6020-bundle-misc.sh @@ -673,6 +673,59 @@ test_expect_success 'bundle progress with --no-quiet' ' grep "%" err ' +test_expect_success 'create bundle with duplicate refnames' ' + git bundle create out.bdl "main" "main" && + + git bundle list-heads out.bdl | + make_user_friendly_and_stable_output >actual && + cat >expect <<-\EOF && + <COMMIT-P> refs/heads/main + EOF + test_cmp expect actual +' + +test_expect_success 'create bundle with duplicate refnames and --all' ' + git bundle create out.bdl --all "main" "main" && + + git bundle list-heads out.bdl | + make_user_friendly_and_stable_output >actual && + cat >expect <<-\EOF && + <COMMIT-P> refs/heads/main + <COMMIT-N> refs/heads/release + <COMMIT-D> refs/heads/topic/1 + <COMMIT-H> refs/heads/topic/2 + <COMMIT-D> refs/pull/1/head + <COMMIT-G> refs/pull/2/head + <TAG-1> refs/tags/v1 + <TAG-2> refs/tags/v2 + <TAG-3> refs/tags/v3 + <COMMIT-P> HEAD + EOF + test_cmp expect actual +' + +test_expect_success 'create bundle with duplicate exlusion refnames' ' + git bundle create out.bdl "main" "main^!" && + + git bundle list-heads out.bdl | + make_user_friendly_and_stable_output >actual && + cat >expect <<-\EOF && + <COMMIT-P> refs/heads/main + EOF + test_cmp expect actual +' + +test_expect_success 'create bundle with duplicate refname short-form' ' + git bundle create out.bdl "main" "main" "refs/heads/main" "refs/heads/main" && + + git bundle list-heads out.bdl | + make_user_friendly_and_stable_output >actual && + cat >expect <<-\EOF && + <COMMIT-P> refs/heads/main + EOF + test_cmp expect actual +' + test_expect_success 'read bundle over stdin' ' git bundle create some.bundle HEAD && diff --git a/t/t6022-rev-list-missing.sh b/t/t6022-rev-list-missing.sh index 3e2790d4c8..08e92dd002 100755 --- a/t/t6022-rev-list-missing.sh +++ b/t/t6022-rev-list-missing.sh @@ -198,4 +198,35 @@ do ' done +test_expect_success "-z nul-delimited --missing" ' + test_when_finished rm -rf repo && + + git init repo && + ( + cd repo && + git commit --allow-empty -m first && + + path="foo bar" && + echo foobar >"$path" && + git add -A && + git commit -m second && + + oid=$(git rev-parse "HEAD:$path") && + type="$(git cat-file -t $oid)" && + + obj_path=".git/objects/$(test_oid_to_path $oid)" && + + git rev-list -z --objects --no-object-names \ + HEAD ^"$oid" >expect && + printf "%s\0missing=yes\0path=%s\0type=%s\0" "$oid" "$path" \ + "$type" >>expect && + + mv "$obj_path" "$obj_path.hidden" && + git rev-list -z --objects --no-object-names \ + --missing=print-info HEAD >actual && + + test_cmp expect actual + ) +' + test_done diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh index 3de4ef6bd9..b46fe49440 100755 --- a/t/t6400-merge-df.sh +++ b/t/t6400-merge-df.sh @@ -84,12 +84,7 @@ test_expect_success 'modify/delete + directory/file conflict' ' test_stdout_line_count = 5 git ls-files -s && test_stdout_line_count = 4 git ls-files -u && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 0 git ls-files -o - else - test_stdout_line_count = 1 git ls-files -o - fi && + test_stdout_line_count = 0 git ls-files -o && test_path_is_file letters/file && test_path_is_file letters.txt && @@ -105,12 +100,7 @@ test_expect_success 'modify/delete + directory/file conflict; other way' ' test_stdout_line_count = 5 git ls-files -s && test_stdout_line_count = 4 git ls-files -u && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 0 git ls-files -o - else - test_stdout_line_count = 1 git ls-files -o - fi && + test_stdout_line_count = 0 git ls-files -o && test_path_is_file letters/file && test_path_is_file letters.txt && diff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh index 2738b50c2a..ff00b74e9c 100755 --- a/t/t6402-merge-rename.sh +++ b/t/t6402-merge-rename.sh @@ -313,12 +313,7 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' ' test_grep "CONFLICT (modify/delete): dir/file-in-the-way" output && test_grep "Auto-merging dir" output && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_grep "moving it to dir~HEAD instead" output - else - test_grep "Adding as dir~HEAD instead" output - fi && + test_grep "moving it to dir~HEAD instead" output && test_stdout_line_count = 3 git ls-files -u && test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && @@ -340,12 +335,7 @@ test_expect_success 'Same as previous, but merged other way' ' ! grep "error: refusing to lose untracked file at" errors && test_grep "CONFLICT (modify/delete): dir/file-in-the-way" output && test_grep "Auto-merging dir" output && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_grep "moving it to dir~renamed-file-has-no-conflicts instead" output - else - test_grep "Adding as dir~renamed-file-has-no-conflicts instead" output - fi && + test_grep "moving it to dir~renamed-file-has-no-conflicts instead" output && test_stdout_line_count = 3 git ls-files -u && test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && @@ -400,14 +390,7 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in t test_must_fail git merge --strategy=recursive dir-in-way && test_stdout_line_count = 5 git ls-files -u && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 3 git ls-files -u dir~HEAD - else - git ls-files -u dir >out && - test 3 -eq $(grep -v file-in-the-way out | wc -l) && - rm -f out - fi && + test_stdout_line_count = 3 git ls-files -u dir~HEAD && test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && @@ -425,14 +408,7 @@ test_expect_success 'Same as previous, but merged other way' ' test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && test_stdout_line_count = 5 git ls-files -u && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 3 git ls-files -u dir~renamed-file-has-conflicts - else - git ls-files -u dir >out && - test 3 -eq $(grep -v file-in-the-way out | wc -l) && - rm -f out - fi && + test_stdout_line_count = 3 git ls-files -u dir~renamed-file-has-conflicts && test_stdout_line_count = 2 git ls-files -u dir/file-in-the-way && test_must_fail git diff --quiet && @@ -488,12 +464,7 @@ test_expect_success 'both rename source and destination involved in D/F conflict git checkout -q rename-dest^0 && test_must_fail git merge --strategy=recursive source-conflict && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 2 git ls-files -u - else - test_stdout_line_count = 1 git ls-files -u - fi && + test_stdout_line_count = 2 git ls-files -u && test_must_fail git diff --quiet && @@ -527,63 +498,33 @@ test_expect_success 'setup pair rename to parent of other (D/F conflicts)' ' git commit -m "Rename one/file -> two" ' -if test "$GIT_TEST_MERGE_ALGORITHM" = ort -then - test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' ' - git checkout -q rename-one^0 && - mkdir one && - test_must_fail git merge --strategy=recursive rename-two && - - test_stdout_line_count = 4 git ls-files -u && - test_stdout_line_count = 2 git ls-files -u one && - test_stdout_line_count = 2 git ls-files -u two && - - test_must_fail git diff --quiet && - - test 3 -eq $(find . | grep -v .git | wc -l) && - - test_path_is_file one && - test_path_is_file two && - test "other" = $(cat one) && - test "stuff" = $(cat two) - ' -else - test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' ' - git checkout -q rename-one^0 && - mkdir one && - test_must_fail git merge --strategy=recursive rename-two && +test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' ' + git checkout -q rename-one^0 && + mkdir one && + test_must_fail git merge --strategy=recursive rename-two && - test_stdout_line_count = 2 git ls-files -u && - test_stdout_line_count = 1 git ls-files -u one && - test_stdout_line_count = 1 git ls-files -u two && + test_stdout_line_count = 4 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u one && + test_stdout_line_count = 2 git ls-files -u two && - test_must_fail git diff --quiet && + test_must_fail git diff --quiet && - test 4 -eq $(find . | grep -v .git | wc -l) && + test 3 -eq $(find . | grep -v .git | wc -l) && - test_path_is_dir one && - test_path_is_file one~rename-two && - test_path_is_file two && - test "other" = $(cat one~rename-two) && - test "stuff" = $(cat two) - ' -fi + test_path_is_file one && + test_path_is_file two && + test "other" = $(cat one) && + test "stuff" = $(cat two) +' test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' ' git reset --hard && git clean -fdqx && test_must_fail git merge --strategy=recursive rename-two && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 4 git ls-files -u && - test_stdout_line_count = 2 git ls-files -u one && - test_stdout_line_count = 2 git ls-files -u two - else - test_stdout_line_count = 2 git ls-files -u && - test_stdout_line_count = 1 git ls-files -u one && - test_stdout_line_count = 1 git ls-files -u two - fi && + test_stdout_line_count = 4 git ls-files -u && + test_stdout_line_count = 2 git ls-files -u one && + test_stdout_line_count = 2 git ls-files -u two && test_must_fail git diff --quiet && @@ -623,22 +564,12 @@ test_expect_success 'check handling of differently renamed file with D/F conflic git checkout -q first-rename^0 && test_must_fail git merge --strategy=recursive second-rename && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_stdout_line_count = 5 git ls-files -s && - test_stdout_line_count = 3 git ls-files -u && - test_stdout_line_count = 1 git ls-files -u one~HEAD && - test_stdout_line_count = 1 git ls-files -u two~second-rename && - test_stdout_line_count = 1 git ls-files -u original && - test_stdout_line_count = 0 git ls-files -o - else - test_stdout_line_count = 5 git ls-files -s && - test_stdout_line_count = 3 git ls-files -u && - test_stdout_line_count = 1 git ls-files -u one && - test_stdout_line_count = 1 git ls-files -u two && - test_stdout_line_count = 1 git ls-files -u original && - test_stdout_line_count = 2 git ls-files -o - fi && + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 3 git ls-files -u && + test_stdout_line_count = 1 git ls-files -u one~HEAD && + test_stdout_line_count = 1 git ls-files -u two~second-rename && + test_stdout_line_count = 1 git ls-files -u original && + test_stdout_line_count = 0 git ls-files -o && test_path_is_file one/file && test_path_is_file two/file && diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh index ae687f2ce5..346f3608b9 100755 --- a/t/t6404-recursive-merge.sh +++ b/t/t6404-recursive-merge.sh @@ -108,12 +108,7 @@ test_expect_success 'refuse to merge binary files' ' printf "\0\0" >binary-file && git add binary-file && git commit -m binary2 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge F >merge_output - else - test_must_fail git merge F 2>merge_output - fi && + test_must_fail git merge F >merge_output && grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output ' @@ -129,22 +124,12 @@ test_expect_success 'mark rename/delete as unmerged' ' test_tick && git commit -m rename && test_must_fail git merge delete && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test 2 = $(git ls-files --unmerged | wc -l) - else - test 1 = $(git ls-files --unmerged | wc -l) - fi && + test 2 = $(git ls-files --unmerged | wc -l) && git rev-parse --verify :2:a2 && test_must_fail git rev-parse --verify :3:a2 && git checkout -f delete && test_must_fail git merge rename && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test 2 = $(git ls-files --unmerged | wc -l) - else - test 1 = $(git ls-files --unmerged | wc -l) - fi && + test 2 = $(git ls-files --unmerged | wc -l) && test_must_fail git rev-parse --verify :2:a2 && git rev-parse --verify :3:a2 ' diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 66e01464b5..8f6fbef002 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -259,12 +259,7 @@ test_expect_success 'binary files with union attribute' ' printf "two\0" >bin.txt && git commit -am two && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge bin-main >output - else - test_must_fail git merge bin-main 2>output - fi && + test_must_fail git merge bin-main >output && grep -i "warning.*cannot merge.*HEAD vs. bin-main" output ' diff --git a/t/t6416-recursive-corner-cases.sh b/t/t6416-recursive-corner-cases.sh index 17b54d625d..ed20de8ea2 100755 --- a/t/t6416-recursive-corner-cases.sh +++ b/t/t6416-recursive-corner-cases.sh @@ -6,7 +6,6 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh # # L1 L2 @@ -529,15 +528,8 @@ test_expect_success 'setup differently handled merges of directory/file conflict git checkout B^0 && test_must_fail git merge C^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git rm -rf a/ && - git rm a~HEAD - else - git clean -fd && - git rm -rf a/ && - git rm a - fi && + git rm -rf a/ && + git rm a~HEAD && git cat-file -p B:a >a2 && git add a2 && git commit -m D2 && @@ -556,12 +548,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict git checkout C^0 && test_must_fail git merge B^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git rm a~B^0 - else - git clean -fd - fi && + git rm a~B^0 && git rm -rf a/ && test_write_lines 1 2 3 4 5 6 7 8 >a && git add a && @@ -570,15 +557,8 @@ test_expect_success 'setup differently handled merges of directory/file conflict git checkout C^0 && test_must_fail git merge B^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git rm -rf a/ && - git rm a~B^0 - else - git clean -fd && - git rm -rf a/ && - git rm a - fi && + git rm -rf a/ && + git rm a~B^0 && test_write_lines 1 2 3 4 5 6 7 8 >a2 && git add a2 && git commit -m E4 && @@ -596,34 +576,16 @@ test_expect_success 'merge of D1 & E1 fails but has appropriate contents' ' test_must_fail git merge -s recursive E1^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a D1:a && - git rev-parse >actual \ - :0:ignore-me :1:a :2:a && - test_cmp expect actual - else - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a && - git rev-parse >actual \ - :0:ignore-me :2:a && - test_cmp expect actual - fi + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect A:ignore-me B:a D1:a && + git rev-parse >actual :0:ignore-me :1:a :2:a && + test_cmp expect actual ) ' @@ -637,34 +599,18 @@ test_expect_success 'merge of E1 & D1 fails but has appropriate contents' ' test_must_fail git merge -s recursive D1^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a D1:a && - git rev-parse >actual \ - :0:ignore-me :1:a :3:a && - test_cmp expect actual - else - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a && - git rev-parse >actual \ - :0:ignore-me :3:a && - test_cmp expect actual - fi + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a D1:a && + git rev-parse >actual \ + :0:ignore-me :1:a :3:a && + test_cmp expect actual ) ' @@ -678,32 +624,17 @@ test_expect_success 'merge of D1 & E2 fails but has appropriate contents' ' test_must_fail git merge -s recursive E2^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 5 out && - git ls-files -u >out && - test_line_count = 4 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - B:a D1:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :1:a~HEAD :2:a~HEAD :3:a/file :1:a/file :0:ignore-me - else - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 2 out && - - git rev-parse >expect \ - B:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :2:a :3:a/file :1:a/file :0:ignore-me - fi && + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + B:a D1:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :1:a~HEAD :2:a~HEAD :3:a/file :1:a/file :0:ignore-me && test_cmp expect actual && test_path_is_file a~HEAD @@ -720,32 +651,17 @@ test_expect_success 'merge of E2 & D1 fails but has appropriate contents' ' test_must_fail git merge -s recursive D1^0 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 5 out && - git ls-files -u >out && - test_line_count = 4 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - B:a D1:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :1:a~D1^0 :3:a~D1^0 :2:a/file :1:a/file :0:ignore-me - else - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 2 out && - - git rev-parse >expect \ - B:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :3:a :2:a/file :1:a/file :0:ignore-me - fi && + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + B:a D1:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :1:a~D1^0 :3:a~D1^0 :2:a/file :1:a/file :0:ignore-me && test_cmp expect actual && test_path_is_file a~D1^0 @@ -777,7 +693,7 @@ test_expect_success 'merge of D1 & E3 succeeds' ' ) ' -test_expect_merge_algorithm failure success 'merge of D1 & E4 puts merge of a and a2 in both a and a2' ' +test_expect_success 'merge of D1 & E4 puts merge of a and a2 in both a and a2' ' test_when_finished "git -C directory-file reset --hard" && test_when_finished "git -C directory-file clean -fdqx" && ( @@ -1140,7 +1056,7 @@ test_expect_success 'setup symlink modify/modify' ' ) ' -test_expect_merge_algorithm failure success 'check symlink modify/modify' ' +test_expect_success 'check symlink modify/modify' ' ( cd symlink-modify-modify && @@ -1206,7 +1122,7 @@ test_expect_success 'setup symlink add/add' ' ) ' -test_expect_merge_algorithm failure success 'check symlink add/add' ' +test_expect_success 'check symlink add/add' ' ( cd symlink-add-add && @@ -1294,7 +1210,7 @@ test_expect_success 'setup submodule modify/modify' ' ) ' -test_expect_merge_algorithm failure success 'check submodule modify/modify' ' +test_expect_success 'check submodule modify/modify' ' ( cd submodule-modify-modify && @@ -1382,7 +1298,7 @@ test_expect_success 'setup submodule add/add' ' ) ' -test_expect_merge_algorithm failure success 'check submodule add/add' ' +test_expect_success 'check submodule add/add' ' ( cd submodule-add-add && @@ -1457,7 +1373,7 @@ test_expect_success 'setup conflicting entry types (submodule vs symlink)' ' ) ' -test_expect_merge_algorithm failure success 'check conflicting entry types (submodule vs symlink)' ' +test_expect_success 'check conflicting entry types (submodule vs symlink)' ' ( cd submodule-symlink-add-add && diff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh index b99f29ef9b..6eb51285a0 100755 --- a/t/t6421-merge-partial-clone.sh +++ b/t/t6421-merge-partial-clone.sh @@ -27,7 +27,6 @@ test_description="limiting blob downloads when merging with partial clones" # files that might be renamed into each other's paths.) . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh test_setup_repo () { test -d server && return @@ -207,7 +206,7 @@ test_setup_repo () { # # Summary: 2 fetches (1 for 2 objects, 1 for 1 object) # -test_expect_merge_algorithm failure success 'Objects downloaded for single relevant rename' ' +test_expect_success 'Objects downloaded for single relevant rename' ' test_setup_repo && git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-single && ( @@ -297,7 +296,7 @@ test_expect_merge_algorithm failure success 'Objects downloaded for single relev # this are not all that common.) # Summary: 1 fetches for 6 objects # -test_expect_merge_algorithm failure success 'Objects downloaded when a directory rename triggered' ' +test_expect_success 'Objects downloaded when a directory rename triggered' ' test_setup_repo && git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-dir && ( @@ -399,7 +398,7 @@ test_expect_merge_algorithm failure success 'Objects downloaded when a directory # # Summary: 4 fetches (1 for 6 objects, 1 for 8, 1 for 3, 1 for 2) # -test_expect_merge_algorithm failure success 'Objects downloaded with lots of renames and modifications' ' +test_expect_success 'Objects downloaded with lots of renames and modifications' ' test_setup_repo && git clone --sparse --filter=blob:none "file://$(pwd)/server" objects-many && ( diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index 62b49c67e2..9cbe7ca782 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh @@ -7,7 +7,6 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh test_setup_rename_delete_untracked () { git init rename-delete-untracked && @@ -316,12 +315,7 @@ test_expect_success 'rename/directory conflict + clean content merge' ' git ls-files -u >out && test_line_count = 1 out && git ls-files -o >out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_line_count = 1 out - else - test_line_count = 2 out - fi && + test_line_count = 1 out && echo 0 >expect && git cat-file -p base:file >>expect && @@ -350,12 +344,7 @@ test_expect_success 'rename/directory conflict + content merge conflict' ' git ls-files -u >out && test_line_count = 3 out && git ls-files -o >out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_line_count = 1 out - else - test_line_count = 2 out - fi && + test_line_count = 1 out && git cat-file -p left-conflict:newfile >left && git cat-file -p base:file >base && @@ -369,14 +358,8 @@ test_expect_success 'rename/directory conflict + content merge conflict' ' git rev-parse >expect \ base:file left-conflict:newfile right:file && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git rev-parse >actual \ - :1:newfile~HEAD :2:newfile~HEAD :3:newfile~HEAD - else - git rev-parse >actual \ - :1:newfile :2:newfile :3:newfile - fi && + git rev-parse >actual \ + :1:newfile~HEAD :2:newfile~HEAD :3:newfile~HEAD && test_cmp expect actual && test_path_is_file newfile/realfile && @@ -896,7 +879,7 @@ test_setup_rad () { ) } -test_expect_merge_algorithm failure success 'rad-check: rename/add/delete conflict' ' +test_expect_success 'rad-check: rename/add/delete conflict' ' test_setup_rad && ( cd rad && @@ -969,7 +952,7 @@ test_setup_rrdd () { ) } -test_expect_merge_algorithm failure success 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' +test_expect_success 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' test_setup_rrdd && ( cd rrdd && @@ -1058,7 +1041,7 @@ test_setup_mod6 () { ) } -test_expect_merge_algorithm failure success 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' +test_expect_success 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' test_setup_mod6 && ( cd mod6 && diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index e0785410cd..f48ed6d035 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -26,8 +26,6 @@ test_description="recursive merge with directory renames" # files that might be renamed into each other's paths.) . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh - ########################################################################### # SECTION 1: Basic cases we should be able to handle @@ -302,20 +300,11 @@ test_expect_success '1d: Directory renames cause a rename/rename(2to1) conflict' git cat-file -p :2:x/wham >expect && git cat-file -p :3:x/wham >other && >empty && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge-file \ - -L "HEAD:y/wham" \ - -L "" \ - -L "B^0:z/wham" \ - expect empty other - else - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other - fi && + test_must_fail git merge-file \ + -L "HEAD:y/wham" \ + -L "" \ + -L "B^0:z/wham" \ + expect empty other && test_cmp expect x/wham ) ' @@ -1186,18 +1175,10 @@ test_expect_success '5d: Directory/file/file conflict due to directory rename' ' git ls-files -u >out && test_line_count = 1 out && git ls-files -o >out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_line_count = 1 out && - - git rev-parse >actual \ - :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d~HEAD :0:y/d/e - else - test_line_count = 2 out && - - git rev-parse >actual \ - :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e - fi && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d~HEAD :0:y/d/e && git rev-parse >expect \ O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e && test_cmp expect actual && @@ -1280,32 +1261,17 @@ test_expect_success '6a: Tricky rename/delete' ' test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out && test_grep "CONFLICT (rename/delete).*z/c.*y/c" out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >actual \ - :0:y/b :1:y/c :3:y/c && - git rev-parse >expect \ - O:z/b O:z/c O:z/c - else - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >actual \ - :0:y/b :3:y/c && - git rev-parse >expect \ - O:z/b O:z/c - fi && + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :1:y/c :3:y/c && + git rev-parse >expect \ + O:z/b O:z/c O:z/c && test_cmp expect actual ) ' @@ -1372,7 +1338,7 @@ test_setup_6b1 () { ) } -test_expect_merge_algorithm failure success '6b1: Same renames done on both sides, plus another rename' ' +test_expect_success '6b1: Same renames done on both sides, plus another rename' ' test_setup_6b1 && ( cd 6b1 && @@ -1445,7 +1411,7 @@ test_setup_6b2 () { ) } -test_expect_merge_algorithm failure success '6b2: Same rename done on both sides' ' +test_expect_success '6b2: Same rename done on both sides' ' test_setup_6b2 && ( cd 6b2 && @@ -1832,20 +1798,11 @@ test_expect_success '7b: rename/rename(2to1), but only due to transitive rename' git cat-file -p :2:y/d >expect && git cat-file -p :3:y/d >other && >empty && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge-file \ - -L "HEAD:y/d" \ - -L "" \ - -L "B^0:z/d" \ - expect empty other - else - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other - fi && + test_must_fail git merge-file \ + -L "HEAD:y/d" \ + -L "" \ + -L "B^0:z/d" \ + expect empty other && test_cmp expect y/d ) ' @@ -1967,32 +1924,17 @@ test_expect_success '7d: transitive rename involved in rename/delete; how is it test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out && test_grep "CONFLICT (rename/delete).*x/d.*y/d" out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >actual \ - :0:y/b :0:y/c :1:y/d :3:y/d && - git rev-parse >expect \ - O:z/b O:z/c O:x/d O:x/d - else - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >actual \ - :0:y/b :0:y/c :3:y/d && - git rev-parse >expect \ - O:z/b O:z/c O:x/d - fi && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :1:y/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d O:x/d && test_cmp expect actual ) ' @@ -2073,32 +2015,17 @@ test_expect_success '7e: transitive rename in rename/delete AND dirs in the way' test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out && test_grep "CONFLICT (rename/delete).*x/d.*y/d" out && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git ls-files -s >out && - test_line_count = 6 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >actual \ - :0:x/d/f :0:y/d/g :0:y/b :0:y/c :1:y/d~B^0 :3:y/d~B^0 && - git rev-parse >expect \ - A:x/d/f A:y/d/g O:z/b O:z/c O:x/d O:x/d - else - git ls-files -s >out && - test_line_count = 5 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 2 out && - - git rev-parse >actual \ - :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d && - git rev-parse >expect \ - A:x/d/f A:y/d/g O:z/b O:z/c O:x/d - fi && + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:x/d/f :0:y/d/g :0:y/b :0:y/c :1:y/d~B^0 :3:y/d~B^0 && + git rev-parse >expect \ + A:x/d/f A:y/d/g O:z/b O:z/c O:x/d O:x/d && test_cmp expect actual && git hash-object y/d~B^0 >actual && @@ -3284,34 +3211,15 @@ test_expect_success '10b: Overwrite untracked with dir rename + delete' ' echo contents >y/e && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: The following untracked working tree files would be overwritten by merge" err && - - git ls-files -s >out && - test_line_count = 1 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 5 out - else - test_grep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out && - test_grep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 5 out && - - git rev-parse >actual \ - :0:y/b :3:y/d :3:y/e && - git rev-parse >expect \ - O:z/b O:z/c B:z/e && - test_cmp expect actual - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 5 out && echo very >expect && test_cmp expect y/c && @@ -3374,38 +3282,15 @@ test_expect_success '10c1: Overwrite untracked with dir rename/rename(1to2)' ' echo important >y/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: The following untracked working tree files would be overwritten by merge" err && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 3 out - else - test_grep "CONFLICT (rename/rename)" out && - test_grep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out && - - git ls-files -s >out && - test_line_count = 6 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c && - git rev-parse >expect \ - O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c && - test_cmp expect actual && - - git hash-object y/c~B^0 >actual && - git rev-parse O:x/c >expect && - test_cmp expect actual - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 3 out && echo important >expect && test_cmp expect y/c @@ -3425,38 +3310,15 @@ test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), oth echo important >y/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: The following untracked working tree files would be overwritten by merge" err && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 3 out - else - test_grep "CONFLICT (rename/rename)" out && - test_grep "Refusing to lose untracked file at y/c; adding as y/c~HEAD instead" out && - - git ls-files -s >out && - test_line_count = 6 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:y/a :0:y/b :0:x/d :1:x/c :3:w/c :2:y/c && - git rev-parse >expect \ - O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c && - test_cmp expect actual && - - git hash-object y/c~HEAD >actual && - git rev-parse O:x/c >expect && - test_cmp expect actual - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 3 out && echo important >expect && test_cmp expect y/c @@ -3514,47 +3376,15 @@ test_expect_success '10d: Delete untracked with dir rename/rename(2to1)' ' echo important >y/wham && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: The following untracked working tree files would be overwritten by merge" err && - - git ls-files -s >out && - test_line_count = 6 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 3 out - else - test_grep "CONFLICT (rename/rename)" out && - test_grep "Refusing to lose untracked file at y/wham" out && - - git ls-files -s >out && - test_line_count = 6 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham && - git rev-parse >expect \ - O:z/a O:z/b O:x/d O:x/e O:z/c O:x/f && - test_cmp expect actual && - - test_must_fail git rev-parse :1:y/wham && - - # Test that two-way merge in y/wham~merged is as expected - git cat-file -p :2:y/wham >expect && - git cat-file -p :3:y/wham >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect y/wham~merged - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 3 out && echo important >expect && test_cmp expect y/wham @@ -3596,7 +3426,7 @@ test_setup_10e () { ) } -test_expect_merge_algorithm failure success '10e: Does git complain about untracked file that is not really in the way?' ' +test_expect_success '10e: Does git complain about untracked file that is not really in the way?' ' test_setup_10e && ( cd 10e && @@ -3687,30 +3517,8 @@ test_expect_success '11a: Avoid losing dirty contents with simple rename' ' echo stuff >>z/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - test_grep "Refusing to lose dirty file at z/c" out && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:z/a :2:z/c && - git rev-parse >expect \ - O:z/a B:z/b && - test_cmp expect actual && - - git hash-object z/c~HEAD >actual && - git rev-parse B:z/b >expect && - test_cmp expect actual - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && test_seq 1 10 >expected && echo stuff >>expected && @@ -3766,34 +3574,9 @@ test_expect_success '11b: Avoid losing dirty file involved in directory rename' git checkout A^0 && echo stuff >>z/c && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - test_grep "Refusing to lose dirty file at z/c" out && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -m >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:x/b :0:y/a :0:y/c && - git rev-parse >expect \ - O:x/b O:z/a B:x/c && - test_cmp expect actual && - - git hash-object y/c >actual && - git rev-parse B:x/c >expect && - test_cmp expect actual - fi && + test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && grep -q stuff z/c && test_seq 1 10 >expected && @@ -3850,13 +3633,8 @@ test_expect_success '11c: Avoid losing not-uptodate with rename + D/F conflict' echo stuff >>y/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - test_grep "following files would be overwritten by merge" err - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && grep -q stuff y/c && test_seq 1 10 >expected && @@ -3924,30 +3702,8 @@ test_expect_success '11d: Avoid losing not-uptodate with rename + D/F conflict' echo stuff >>z/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - test_grep "Refusing to lose dirty file at z/c" out && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 4 out && - - git rev-parse >actual \ - :0:x/b :0:y/a :0:y/c/d :3:y/c && - git rev-parse >expect \ - O:x/b O:z/a B:y/c/d B:x/c && - test_cmp expect actual && - - git hash-object y/c~HEAD >actual && - git rev-parse B:x/c >expect && - test_cmp expect actual - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && grep -q stuff z/c && test_seq 1 10 >expected && @@ -4010,39 +3766,8 @@ test_expect_success '11e: Avoid deleting not-uptodate with dir rename/rename(1to echo mods >>y/c && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - test_grep "CONFLICT (rename/rename)" out && - test_grep "Refusing to lose dirty file at y/c" out && - - git ls-files -s >out && - test_line_count = 7 out && - git ls-files -u >out && - test_line_count = 4 out && - git ls-files -o >out && - test_line_count = 3 out && - - git rev-parse >actual \ - :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c && - git rev-parse >expect \ - O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c && - test_cmp expect actual && - - # See if y/c~merged has expected contents; requires manually - # doing the expected file merge - git cat-file -p A:y/c >c1 && - git cat-file -p B:z/c >c2 && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - c1 empty c2 && - test_cmp c1 y/c~merged - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && echo different >expected && echo mods >>expected && @@ -4099,40 +3824,8 @@ test_expect_success '11f: Avoid deleting not-uptodate with dir rename/rename(2to echo important >>y/wham && test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_path_is_missing .git/MERGE_HEAD && - test_grep "error: Your local changes to the following files would be overwritten by merge" err - else - test_grep "CONFLICT (rename/rename)" out && - test_grep "Refusing to lose dirty file at y/wham" out && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 3 out && - - test_must_fail git rev-parse :1:y/wham && - - git rev-parse >actual \ - :0:y/a :0:y/b :2:y/wham :3:y/wham && - git rev-parse >expect \ - O:z/a O:z/b O:x/c O:x/d && - test_cmp expect actual && - - # Test that two-way merge in y/wham~merged is as expected - git cat-file -p :2:y/wham >expect && - git cat-file -p :3:y/wham >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect y/wham~merged - fi && + test_path_is_missing .git/MERGE_HEAD && + test_grep "error: Your local changes to the following files would be overwritten by merge" err && test_seq 1 10 >expected && echo important >>expected && @@ -4267,7 +3960,7 @@ test_setup_12b1 () { ) } -test_expect_merge_algorithm failure success '12b1: Moving two directory hierarchies into each other' ' +test_expect_success '12b1: Moving two directory hierarchies into each other' ' test_setup_12b1 && ( cd 12b1 && @@ -4435,7 +4128,7 @@ test_setup_12c1 () { ) } -test_expect_merge_algorithm failure success '12c1: Moving one directory hierarchy into another w/ content merge' ' +test_expect_success '12c1: Moving one directory hierarchy into another w/ content merge' ' test_setup_12c1 && ( cd 12c1 && @@ -4797,7 +4490,7 @@ test_setup_12f () { ) } -test_expect_merge_algorithm failure success '12f: Trivial directory resolve, caching, all kinds of fun' ' +test_expect_success '12f: Trivial directory resolve, caching, all kinds of fun' ' test_setup_12f && ( cd 12f && @@ -5253,7 +4946,7 @@ test_setup_12l () { ) } -test_expect_merge_algorithm failure success '12l (B into A): Rename into each other + add/add conflict' ' +test_expect_success '12l (B into A): Rename into each other + add/add conflict' ' test_setup_12l BintoA && ( cd 12l_BintoA && @@ -5280,7 +4973,7 @@ test_expect_merge_algorithm failure success '12l (B into A): Rename into each ot ) ' -test_expect_merge_algorithm failure success '12l (A into B): Rename into each other + add/add conflict' ' +test_expect_success '12l (A into B): Rename into each other + add/add conflict' ' test_setup_12l AintoB && ( cd 12l_AintoB && @@ -5348,7 +5041,7 @@ test_setup_12m () { ) } -test_expect_merge_algorithm failure success '12m: Change parent of renamed-dir to symlink on other side' ' +test_expect_success '12m: Change parent of renamed-dir to symlink on other side' ' test_setup_12m && ( cd 12m && diff --git a/t/t6424-merge-unrelated-index-changes.sh b/t/t6424-merge-unrelated-index-changes.sh index 7677c5f08d..ddc7524f6c 100755 --- a/t/t6424-merge-unrelated-index-changes.sh +++ b/t/t6424-merge-unrelated-index-changes.sh @@ -176,9 +176,11 @@ test_expect_success 'merge-recursive, when index==head but head!=HEAD' ' # Make index match B git diff C B -- | git apply --cached && test_when_finished "git clean -fd" && # Do not leave untracked around + git write-tree >index-before && # Merge B & F, with B as "head" git merge-recursive A -- B F > out && - test_grep "Already up to date" out + git write-tree >index-after && + test_cmp index-before index-after ' test_expect_success 'recursive, when file has staged changes not matching HEAD nor what a merge would give' ' @@ -294,10 +296,8 @@ test_expect_success 'with multiple strategies, recursive or ort failure do not e git add a && git rev-parse :a >expect && - sane_unset GIT_TEST_MERGE_ALGORITHM && - test_must_fail git merge -s recursive -s ort -s octopus C^0 >output 2>&1 && + test_must_fail git merge -s ort -s octopus C^0 >output 2>&1 && - grep "Trying merge strategy recursive..." output && grep "Trying merge strategy ort..." output && grep "Trying merge strategy octopus..." output && grep "No merge strategy handled the merge." output && diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh index b059475ed0..404cd3f2ca 100755 --- a/t/t6426-merge-skip-unneeded-updates.sh +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -23,8 +23,6 @@ test_description="merge cases" # files that might be renamed into each other's paths.) . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh - ########################################################################### # SECTION 1: Cases involving no renames (one side has subset of changes of @@ -663,7 +661,7 @@ test_setup_4a () { # correct requires doing the merge in-memory first, then realizing that no # updates to the file are necessary, and thus that we can just leave the path # alone. -test_expect_merge_algorithm failure success '4a: Change on A, change on B subset of A, dirty mods present' ' +test_expect_success '4a: Change on A, change on B subset of A, dirty mods present' ' test_setup_4a && ( cd 4a && diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh index 9919c3fa7c..c86e02c6e2 100755 --- a/t/t6428-merge-conflicts-sparse.sh +++ b/t/t6428-merge-conflicts-sparse.sh @@ -23,8 +23,6 @@ test_description="merge cases" # files that might be renamed into each other's paths.) . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh - # Testcase basic, conflicting changes in 'numerals' diff --git a/t/t6430-merge-recursive.sh b/t/t6430-merge-recursive.sh index ca15e6dd6d..e59560a5dd 100755 --- a/t/t6430-merge-recursive.sh +++ b/t/t6430-merge-recursive.sh @@ -6,7 +6,6 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh test_expect_success 'setup 1' ' @@ -373,9 +372,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" && - echo "100644 $o1 2 a" && echo "100644 $o4 0 a/c" && + echo "100644 $o0 1 a~$c1" && + echo "100644 $o1 2 a~$c1" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" @@ -397,9 +396,9 @@ test_expect_success 'merge-recursive d/f conflict result the other way' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" && - echo "100644 $o1 3 a" && echo "100644 $o4 0 a/c" && + echo "100644 $o0 1 a~$c1" && + echo "100644 $o1 3 a~$c1" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" @@ -424,9 +423,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' echo "100644 $o1 0 a" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && - echo "100644 $o6 3 d" && echo "100644 $o0 1 d/e" && - echo "100644 $o1 2 d/e" + echo "100644 $o1 2 d/e" && + echo "100644 $o6 3 d~$c6" ) >expected && test_cmp expected actual @@ -448,9 +447,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' echo "100644 $o1 0 a" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && - echo "100644 $o6 2 d" && echo "100644 $o0 1 d/e" && - echo "100644 $o1 3 d/e" + echo "100644 $o1 3 d/e" && + echo "100644 $o6 2 d~$c6" ) >expected && test_cmp expected actual @@ -645,7 +644,7 @@ test_expect_success 'merge-recursive copy vs. rename' ' test_cmp expected actual ' -test_expect_merge_algorithm failure success 'merge-recursive rename vs. rename/symlink' ' +test_expect_success 'merge-recursive rename vs. rename/symlink' ' git checkout -f rename && git merge rename-ln && @@ -696,33 +695,6 @@ test_expect_success 'merging with triple rename across D/F conflict' ' git merge other ' -test_expect_success 'merge-recursive remembers the names of all base trees' ' - git reset --hard HEAD && - - # make the index match $c1 so that merge-recursive below does not - # fail early - git diff --binary HEAD $c1 -- | git apply --cached && - - # more trees than static slots used by oid_to_hex() - for commit in $c0 $c2 $c4 $c5 $c6 $c7 - do - git rev-parse "$commit^{tree}" || return 1 - done >trees && - - # ignore the return code; it only fails because the input is weird... - test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out && - - # ...but make sure it fails in the expected way - test_grep CONFLICT.*rename/rename out && - - # merge-recursive prints in reverse order, but we do not care - sort <trees >expect && - sed -n "s/^virtual //p" out | sort >actual && - test_cmp expect actual && - - git clean -fd -' - test_expect_success 'merge-recursive internal merge resolves to the sameness' ' git reset --hard HEAD && diff --git a/t/t6434-merge-recursive-rename-options.sh b/t/t6434-merge-recursive-rename-options.sh index 6e913c30a1..5a6f74839c 100755 --- a/t/t6434-merge-recursive-rename-options.sh +++ b/t/t6434-merge-recursive-rename-options.sh @@ -34,7 +34,9 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME get_expected_stages () { git checkout rename -- $1-new && git ls-files --stage $1-new >expected-stages-undetected-$1 && - sed "s/ 0 / 2 /" <expected-stages-undetected-$1 \ + git ls-tree HEAD^ $1-old >tmp && + git ls-tree HEAD $1-new >>tmp && + cat tmp | awk '{print $1 " " $3 " " NR "\t" '$1'"-new"}' \ >expected-stages-detected-$1 && git read-tree -u --reset HEAD } @@ -51,11 +53,11 @@ rename_undetected () { check_common () { git ls-files --stage >stages-actual && - test_line_count = 4 stages-actual + test_line_count = $1 stages-actual } check_threshold_0 () { - check_common && + check_common 8 && rename_detected 0 && rename_detected 1 && rename_detected 2 && @@ -63,7 +65,7 @@ check_threshold_0 () { } check_threshold_1 () { - check_common && + check_common 7 && rename_undetected 0 && rename_detected 1 && rename_detected 2 && @@ -71,7 +73,7 @@ check_threshold_1 () { } check_threshold_2 () { - check_common && + check_common 6 && rename_undetected 0 && rename_undetected 1 && rename_detected 2 && @@ -79,7 +81,7 @@ check_threshold_2 () { } check_exact_renames () { - check_common && + check_common 5 && rename_undetected 0 && rename_undetected 1 && rename_undetected 2 && @@ -87,7 +89,7 @@ check_exact_renames () { } check_no_renames () { - check_common && + check_common 4 && rename_undetected 0 && rename_undetected 1 && rename_undetected 2 && diff --git a/t/t6436-merge-overwrite.sh b/t/t6436-merge-overwrite.sh index 4f4376421e..70b5d2d694 100755 --- a/t/t6436-merge-overwrite.sh +++ b/t/t6436-merge-overwrite.sh @@ -101,19 +101,10 @@ test_expect_success 'will not overwrite unstaged changes in renamed file' ' git mv c1.c other.c && git commit -m rename && cp important other.c && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge c1a >out 2>err && - test_grep "would be overwritten by merge" err && - test_cmp important other.c && - test_path_is_missing .git/MERGE_HEAD - else - test_must_fail git merge c1a >out && - test_grep "Refusing to lose dirty file at other.c" out && - test_path_is_file other.c~HEAD && - test $(git hash-object other.c~HEAD) = $(git rev-parse c1a:c1.c) && - test_cmp important other.c - fi + test_must_fail git merge c1a >out 2>err && + test_grep "would be overwritten by merge" err && + test_cmp important other.c && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'will not overwrite untracked subtree' ' diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 4815559157..a564758f52 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -9,7 +9,6 @@ GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-merge.sh # # history @@ -110,14 +109,10 @@ test_expect_success 'merging should conflict for non fast-forward' ' test_when_finished "git -C merge-search reset --hard" && (cd merge-search && git checkout -b test-nonforward-a b && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge c 2>actual && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual - else - test_must_fail git merge c 2> actual - fi) + test_must_fail git merge c 2>actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + ) ' test_expect_success 'finish setup for merge-search' ' @@ -151,14 +146,9 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge c >actual 2>sub-actual && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" sub-actual - else - test_must_fail git merge c 2> actual - fi && + test_must_fail git merge c >actual 2>sub-actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" sub-actual && grep $(cat expect) actual > /dev/null && git reset --hard) ' @@ -169,23 +159,12 @@ test_expect_success 'merging should fail for ambiguous common parent' ' (cd sub && git checkout -b ambiguous sub-b && git merge sub-c && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - git rev-parse --short sub-d >../expect1 && - git rev-parse --short ambiguous >../expect2 - else - git rev-parse sub-d > ../expect1 && - git rev-parse ambiguous > ../expect2 - fi + git rev-parse --short sub-d >../expect1 && + git rev-parse --short ambiguous >../expect2 ) && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - test_must_fail git merge c >actual 2>sub-actual && - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" sub-actual - else - test_must_fail git merge c 2> actual - fi && + test_must_fail git merge c >actual 2>sub-actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" sub-actual && grep $(cat expect1) actual > /dev/null && grep $(cat expect2) actual > /dev/null && git reset --hard) @@ -227,11 +206,9 @@ test_expect_success 'merging should fail for changes that are backwards' ' git checkout -b test-backward e && test_must_fail git merge f 2>actual && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && - grep "$sub_expect" actual - fi) + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + ) ' @@ -358,7 +335,7 @@ test_expect_success 'setup file/submodule conflict' ' ) ' -test_expect_merge_algorithm failure success 'file/submodule conflict' ' +test_expect_success 'file/submodule conflict' ' test_when_finished "git -C file-submodule reset --hard" && ( cd file-submodule && @@ -467,7 +444,7 @@ test_expect_failure 'directory/submodule conflict; keep submodule clean' ' ) ' -test_expect_merge_algorithm failure success !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' ' +test_expect_success !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' ' test_when_finished "git -C directory-submodule/path reset --hard" && test_when_finished "git -C directory-submodule reset --hard" && ( @@ -535,11 +512,9 @@ test_expect_success 'merging should fail with no merge base' ' git add sub && git commit -m "b" && test_must_fail git merge a 2>actual && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && - grep "$sub_expect" actual - fi) + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && + grep "$sub_expect" actual + ) ' test_done diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh index 8df67a0ef9..53d83c828a 100755 --- a/t/t6438-submodule-directory-file-conflicts.sh +++ b/t/t6438-submodule-directory-file-conflicts.sh @@ -12,11 +12,6 @@ test_submodule_switch "merge --ff" test_submodule_switch "merge --ff-only" -if test "$GIT_TEST_MERGE_ALGORITHM" != ort -then - KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 - KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 -fi test_submodule_switch "merge --no-ff" test_done diff --git a/t/t6439-merge-co-error-msgs.sh b/t/t6439-merge-co-error-msgs.sh index 55bd744a3f..643c9368e0 100755 --- a/t/t6439-merge-co-error-msgs.sh +++ b/t/t6439-merge-co-error-msgs.sh @@ -47,7 +47,7 @@ test_expect_success 'untracked files overwritten by merge (fast and non-fast for export GIT_MERGE_VERBOSITY && test_must_fail git merge branch 2>out2 ) && - echo "Merge with strategy ${GIT_TEST_MERGE_ALGORITHM:-ort} failed." >>expect && + echo "Merge with strategy ort failed." >>expect && test_cmp out2 expect && git reset --hard HEAD^ ' diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 25b33a1e87..06cee3432f 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -124,11 +124,8 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && test_cmp expect actual && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && - grep "$sub_expect" actual_output - fi + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output ' test_done diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index ff085b086c..42f675b739 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -77,12 +77,9 @@ Merge made by the 'recursive' strategy. EOF test_expect_success 'merge reduces irrelevant remote heads' ' - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - mv expected expected.tmp && - sed s/recursive/ort/ expected.tmp >expected && - rm expected.tmp - fi && + mv expected expected.tmp && + sed s/recursive/ort/ expected.tmp >expected && + rm expected.tmp && GIT_MERGE_VERBOSITY=0 git merge c4 c5 >actual && test_cmp expected actual ' diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index c077aba7ce..957f8e20ba 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -535,14 +535,9 @@ test_expect_success 'file vs modified submodule' ' yes "" | git mergetool file1 file2 spaced\ name subdir/file3 && yes "" | git mergetool both && yes "d" | git mergetool file11 file12 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - yes "c" | git mergetool submod~HEAD && - git rm submod && - git mv submod~HEAD submod - else - yes "l" | git mergetool submod - fi && + yes "c" | git mergetool submod~HEAD && + git rm submod && + git mv submod~HEAD submod && git submodule update -N && echo "not a submodule" >expect && test_cmp expect submod && @@ -559,15 +554,10 @@ test_expect_success 'file vs modified submodule' ' yes "" | git mergetool file1 file2 spaced\ name subdir/file3 && yes "" | git mergetool both && yes "d" | git mergetool file11 file12 && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - mv submod submod.orig && - git rm --cached submod && - yes "c" | git mergetool submod~test19 && - git mv submod~test19 submod - else - yes "r" | git mergetool submod - fi && + mv submod submod.orig && + git rm --cached submod && + yes "c" | git mergetool submod~test19 && + git mv submod~test19 submod && test -d submod.orig && git submodule update -N && echo "not a submodule" >expect && @@ -585,10 +575,7 @@ test_expect_success 'file vs modified submodule' ' yes "" | git mergetool both && yes "d" | git mergetool file11 file12 && yes "l" | git mergetool submod && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - yes "d" | git mergetool submod~test19 - fi && + yes "d" | git mergetool submod~test19 && echo "main submodule" >expect && test_cmp expect submod/bar && git submodule update -N && @@ -686,14 +673,9 @@ test_expect_success 'directory vs modified submodule' ' test_must_fail git merge main && test -n "$(git ls-files -u)" && test ! -e submod.orig && - if test "$GIT_TEST_MERGE_ALGORITHM" = ort - then - yes "r" | git mergetool submod~main && - git mv submod submod.orig && - git mv submod~main submod - else - yes "r" | git mergetool submod - fi && + yes "r" | git mergetool submod~main && + git mv submod submod.orig && + git mv submod~main submod && test -d submod.orig && echo "not a submodule" >expect && test_cmp expect submod.orig/file16 && diff --git a/t/t7615-diff-algo-with-mergy-operations.sh b/t/t7615-diff-algo-with-mergy-operations.sh index ac5863e788..5822d02d51 100755 --- a/t/t7615-diff-algo-with-mergy-operations.sh +++ b/t/t7615-diff-algo-with-mergy-operations.sh @@ -22,8 +22,6 @@ test_expect_success 'setup' ' git tag c2 ' -GIT_TEST_MERGE_ALGORITHM=recursive - test_expect_success 'merge c2 to c1 with recursive merge strategy fails with the current default myers diff algorithm' ' git reset --hard c1 && test_must_fail git merge -s recursive -Xdiff-algorithm=myers c2 diff --git a/t/t7815-grep-binary.sh b/t/t7815-grep-binary.sh index 3bd91da970..b7d83f9a5d 100755 --- a/t/t7815-grep-binary.sh +++ b/t/t7815-grep-binary.sh @@ -63,7 +63,7 @@ test_expect_success 'git grep ile a' ' git grep ile a ' -test_expect_failure 'git grep .fi a' ' +test_expect_failure !CYGWIN 'git grep .fi a' ' git grep .fi a ' diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 1909aed95e..9b82e11c10 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -306,6 +306,34 @@ test_expect_success 'maintenance.loose-objects.auto' ' test_subcommand git prune-packed --quiet <trace-loC ' +test_expect_success 'maintenance.loose-objects.batchSize' ' + git init loose-batch && + + # This creates three objects per commit. + test_commit_bulk -C loose-batch 34 && + pack=$(ls loose-batch/.git/objects/pack/pack-*.pack) && + index="${pack%pack}idx" && + rm "$index" && + git -C loose-batch unpack-objects <"$pack" && + git -C loose-batch config maintenance.loose-objects.batchSize 50 && + + GIT_PROGRESS_DELAY=0 \ + git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err && + grep "Enumerating objects: 50, done." err && + + GIT_PROGRESS_DELAY=0 \ + git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err && + grep "Enumerating objects: 50, done." err && + + GIT_PROGRESS_DELAY=0 \ + git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err && + grep "Enumerating objects: 2, done." err && + + GIT_PROGRESS_DELAY=0 \ + git -C loose-batch maintenance run --no-quiet --task=loose-objects 2>err && + test_must_be_empty err +' + test_expect_success 'incremental-repack task' ' packDir=.git/objects/pack && for i in $(test_seq 1 5) @@ -447,6 +475,24 @@ test_expect_success 'pack-refs task' ' test_subcommand git pack-refs --all --prune <pack-refs.txt ' +test_expect_success 'reflog-expire task' ' + GIT_TRACE2_EVENT="$(pwd)/reflog-expire.txt" \ + git maintenance run --task=reflog-expire && + test_subcommand git reflog expire --all <reflog-expire.txt +' + +test_expect_success 'reflog-expire task --auto only packs when exceeding limits' ' + git reflog expire --all --expire=now && + test_commit reflog-one && + test_commit reflog-two && + GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \ + git -c maintenance.reflog-expire.auto=3 maintenance run --auto --task=reflog-expire && + test_subcommand ! git reflog expire --all <reflog-expire-auto.txt && + GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \ + git -c maintenance.reflog-expire.auto=2 maintenance run --auto --task=reflog-expire && + test_subcommand git reflog expire --all <reflog-expire-auto.txt +' + test_expect_success '--auto and --schedule incompatible' ' test_must_fail git maintenance run --auto --schedule=daily 2>err && test_grep "at most one" err diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh index 370b768149..cace00ae8d 100755 --- a/t/t8013-blame-ignore-revs.sh +++ b/t/t8013-blame-ignore-revs.sh @@ -158,6 +158,25 @@ test_expect_success mark_unblamable_lines ' test_cmp expect actual ' +for opt in --porcelain --line-porcelain +do + test_expect_success "mark_unblamable_lines with $opt" " + sha=$(git rev-parse Y) && + + git -c blame.markUnblamableLines=false blame $opt --ignore-rev Y file >raw && + cat > sedscript <<- 'EOF' && + /^ y3/i\\ + unblamable + /^ y4/i\\ + unblamable + EOF + sed -f sedscript raw >expect && + + git -c blame.markUnblamableLines=true blame $opt --ignore-rev Y file >actual && + test_cmp expect actual + " +done + # Commit Z will touch the first two lines. Y touched all four. # A--B--X--Y--Z # The blame output when ignoring Z should be: @@ -191,6 +210,25 @@ test_expect_success mark_ignored_lines ' ! test_cmp expect actual ' +for opt in --porcelain --line-porcelain +do + test_expect_success "mark_ignored_lines with $opt" " + sha=$(git rev-parse Y) && + + git -c blame.markIgnoredLines=false blame $opt --ignore-rev Z file >raw && + cat > sedscript <<- 'EOF' && + /^ line-one-Z/i\\ + ignored + /^ line-two-Z/i\\ + ignored + EOF + sed -f sedscript raw >expect && + + git -c blame.markIgnoredLines=true blame $opt --ignore-rev Z file >actual && + test_cmp expect actual + " +done + # For ignored revs that added 'unblamable' lines and more recent commits changed # the blamable lines, mark the unblamable lines with a # '*' diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index 5ac5383fb7..7614dfbd95 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -95,9 +95,8 @@ test_expect_success 'two labels on the same changelist' ' cd "$git" && git p4 sync --import-labels && - git tag | grep TAG_F1 && - git tag | grep -q TAG_F1_1 && - git tag | grep -q TAG_F1_2 && + git show-ref --verify refs/tags/TAG_F1_1 && + git show-ref --verify refs/tags/TAG_F1_2 && cd main && @@ -207,8 +206,7 @@ test_expect_success 'use git config to enable import/export of tags' ' git tag CFG_A_GIT_TAG && git p4 rebase --verbose && git p4 submit --verbose && - git tag && - git tag | grep TAG_F1_1 + git show-ref --verify refs/tags/TAG_F1_1 ) && ( cd "$cli" && diff --git a/t/test-lib.sh b/t/test-lib.sh index f0cadc159f..af722d383d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -540,8 +540,6 @@ GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}" export GIT_DEFAULT_HASH GIT_DEFAULT_REF_FORMAT="${GIT_TEST_DEFAULT_REF_FORMAT:-files}" export GIT_DEFAULT_REF_FORMAT -GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}" -export GIT_TEST_MERGE_ALGORITHM # Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output GIT_TRACE_BARE=1 diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h index 8b206179fc..2203743fb4 100644 --- a/t/unit-tests/clar/clar/fs.h +++ b/t/unit-tests/clar/clar/fs.h @@ -376,9 +376,12 @@ fs_copydir_helper(const char *source, const char *dest, int dest_mode) mkdir(dest, dest_mode); cl_assert_(source_dir = opendir(source), "Could not open source dir"); - while ((d = (errno = 0, readdir(source_dir))) != NULL) { + for (;;) { char *child; + errno = 0; + if ((d = readdir(source_dir)) == NULL) + break; if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; @@ -479,9 +482,12 @@ fs_rmdir_helper(const char *path) struct dirent *d; cl_assert_(dir = opendir(path), "Could not open dir"); - while ((d = (errno = 0, readdir(dir))) != NULL) { + for (;;) { char *child; + errno = 0; + if ((d = readdir(dir)) == NULL) + break; if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c index 22040aeefa..7dbd93601c 100644 --- a/t/unit-tests/t-reftable-block.c +++ b/t/unit-tests/t-reftable-block.c @@ -19,24 +19,25 @@ static void t_ref_block_read_write(void) struct reftable_record recs[30]; const size_t N = ARRAY_SIZE(recs); const size_t block_size = 1024; - struct reftable_block block = { 0 }; + struct reftable_block_source source = { 0 }; struct block_writer bw = { .last_key = REFTABLE_BUF_INIT, }; struct reftable_record rec = { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, }; size_t i = 0; int ret; - struct block_reader br = { 0 }; + struct reftable_block block = { 0 }; struct block_iter it = BLOCK_ITER_INIT; - struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT; + struct reftable_buf want = REFTABLE_BUF_INIT; + struct reftable_buf block_data = REFTABLE_BUF_INIT; - REFTABLE_CALLOC_ARRAY(block.data, block_size); - check(block.data != NULL); - block.len = block_size; - block_source_from_buf(&block.source ,&buf); - ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, + REFTABLE_CALLOC_ARRAY(block_data.buf, block_size); + check(block_data.buf != NULL); + block_data.len = block_size; + + ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_REF, (uint8_t *) block_data.buf, block_size, header_off, hash_size(REFTABLE_HASH_SHA1)); check(!ret); @@ -62,9 +63,10 @@ static void t_ref_block_read_write(void) block_writer_release(&bw); - block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); + block_source_from_buf(&source ,&block_data); + reftable_block_init(&block, &source, 0, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); - block_iter_seek_start(&it, &br); + block_iter_init(&it, &block); for (i = 0; ; i++) { ret = block_iter_next(&it, &rec); @@ -77,10 +79,9 @@ static void t_ref_block_read_write(void) } for (i = 0; i < N; i++) { - block_iter_reset(&it); reftable_record_key(&recs[i], &want); - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -89,7 +90,7 @@ static void t_ref_block_read_write(void) check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1)); want.len--; - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -97,12 +98,11 @@ static void t_ref_block_read_write(void) check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1)); } - block_reader_release(&br); + reftable_block_release(&block); block_iter_close(&it); reftable_record_release(&rec); - reftable_block_done(&br.block); reftable_buf_release(&want); - reftable_buf_release(&buf); + reftable_buf_release(&block_data); for (i = 0; i < N; i++) reftable_record_release(&recs[i]); } @@ -113,24 +113,25 @@ static void t_log_block_read_write(void) struct reftable_record recs[30]; const size_t N = ARRAY_SIZE(recs); const size_t block_size = 2048; - struct reftable_block block = { 0 }; + struct reftable_block_source source = { 0 }; struct block_writer bw = { .last_key = REFTABLE_BUF_INIT, }; struct reftable_record rec = { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, }; size_t i = 0; int ret; - struct block_reader br = { 0 }; + struct reftable_block block = { 0 }; struct block_iter it = BLOCK_ITER_INIT; - struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT; + struct reftable_buf want = REFTABLE_BUF_INIT; + struct reftable_buf block_data = REFTABLE_BUF_INIT; + + REFTABLE_CALLOC_ARRAY(block_data.buf, block_size); + check(block_data.buf != NULL); + block_data.len = block_size; - REFTABLE_CALLOC_ARRAY(block.data, block_size); - check(block.data != NULL); - block.len = block_size; - block_source_from_buf(&block.source ,&buf); - ret = block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size, + ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_LOG, (uint8_t *) block_data.buf, block_size, header_off, hash_size(REFTABLE_HASH_SHA1)); check(!ret); @@ -151,9 +152,10 @@ static void t_log_block_read_write(void) block_writer_release(&bw); - block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); + block_source_from_buf(&source, &block_data); + reftable_block_init(&block, &source, 0, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); - block_iter_seek_start(&it, &br); + block_iter_init(&it, &block); for (i = 0; ; i++) { ret = block_iter_next(&it, &rec); @@ -166,11 +168,10 @@ static void t_log_block_read_write(void) } for (i = 0; i < N; i++) { - block_iter_reset(&it); reftable_buf_reset(&want); check(!reftable_buf_addstr(&want, recs[i].u.log.refname)); - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -179,7 +180,7 @@ static void t_log_block_read_write(void) check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1)); want.len--; - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -187,12 +188,11 @@ static void t_log_block_read_write(void) check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1)); } - block_reader_release(&br); + reftable_block_release(&block); block_iter_close(&it); reftable_record_release(&rec); - reftable_block_done(&br.block); reftable_buf_release(&want); - reftable_buf_release(&buf); + reftable_buf_release(&block_data); for (i = 0; i < N; i++) reftable_record_release(&recs[i]); } @@ -203,24 +203,25 @@ static void t_obj_block_read_write(void) struct reftable_record recs[30]; const size_t N = ARRAY_SIZE(recs); const size_t block_size = 1024; - struct reftable_block block = { 0 }; + struct reftable_block_source source = { 0 }; struct block_writer bw = { .last_key = REFTABLE_BUF_INIT, }; struct reftable_record rec = { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, }; size_t i = 0; int ret; - struct block_reader br = { 0 }; + struct reftable_block block = { 0 }; struct block_iter it = BLOCK_ITER_INIT; - struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT; + struct reftable_buf want = REFTABLE_BUF_INIT; + struct reftable_buf block_data = REFTABLE_BUF_INIT; + + REFTABLE_CALLOC_ARRAY(block_data.buf, block_size); + check(block_data.buf != NULL); + block_data.len = block_size; - REFTABLE_CALLOC_ARRAY(block.data, block_size); - check(block.data != NULL); - block.len = block_size; - block_source_from_buf(&block.source, &buf); - ret = block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size, + ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_OBJ, (uint8_t *) block_data.buf, block_size, header_off, hash_size(REFTABLE_HASH_SHA1)); check(!ret); @@ -243,9 +244,10 @@ static void t_obj_block_read_write(void) block_writer_release(&bw); - block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); + block_source_from_buf(&source, &block_data); + reftable_block_init(&block, &source, 0, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); - block_iter_seek_start(&it, &br); + block_iter_init(&it, &block); for (i = 0; ; i++) { ret = block_iter_next(&it, &rec); @@ -258,10 +260,9 @@ static void t_obj_block_read_write(void) } for (i = 0; i < N; i++) { - block_iter_reset(&it); reftable_record_key(&recs[i], &want); - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -270,12 +271,11 @@ static void t_obj_block_read_write(void) check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1)); } - block_reader_release(&br); + reftable_block_release(&block); block_iter_close(&it); reftable_record_release(&rec); - reftable_block_done(&br.block); reftable_buf_release(&want); - reftable_buf_release(&buf); + reftable_buf_release(&block_data); for (i = 0; i < N; i++) reftable_record_release(&recs[i]); } @@ -286,25 +286,26 @@ static void t_index_block_read_write(void) struct reftable_record recs[30]; const size_t N = ARRAY_SIZE(recs); const size_t block_size = 1024; - struct reftable_block block = { 0 }; + struct reftable_block_source source = { 0 }; struct block_writer bw = { .last_key = REFTABLE_BUF_INIT, }; struct reftable_record rec = { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx.last_key = REFTABLE_BUF_INIT, }; size_t i = 0; int ret; - struct block_reader br = { 0 }; + struct reftable_block block = { 0 }; struct block_iter it = BLOCK_ITER_INIT; - struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT; + struct reftable_buf want = REFTABLE_BUF_INIT; + struct reftable_buf block_data = REFTABLE_BUF_INIT; - REFTABLE_CALLOC_ARRAY(block.data, block_size); - check(block.data != NULL); - block.len = block_size; - block_source_from_buf(&block.source, &buf); - ret = block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size, + REFTABLE_CALLOC_ARRAY(block_data.buf, block_size); + check(block_data.buf != NULL); + block_data.len = block_size; + + ret = block_writer_init(&bw, REFTABLE_BLOCK_TYPE_INDEX, (uint8_t *) block_data.buf, block_size, header_off, hash_size(REFTABLE_HASH_SHA1)); check(!ret); @@ -314,7 +315,7 @@ static void t_index_block_read_write(void) snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i); reftable_buf_init(&recs[i].u.idx.last_key); - recs[i].type = BLOCK_TYPE_INDEX; + recs[i].type = REFTABLE_BLOCK_TYPE_INDEX; check(!reftable_buf_addstr(&recs[i].u.idx.last_key, buf)); recs[i].u.idx.offset = i; @@ -327,9 +328,10 @@ static void t_index_block_read_write(void) block_writer_release(&bw); - block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); + block_source_from_buf(&source, &block_data); + reftable_block_init(&block, &source, 0, header_off, block_size, REFTABLE_HASH_SIZE_SHA1); - block_iter_seek_start(&it, &br); + block_iter_init(&it, &block); for (i = 0; ; i++) { ret = block_iter_next(&it, &rec); @@ -342,10 +344,9 @@ static void t_index_block_read_write(void) } for (i = 0; i < N; i++) { - block_iter_reset(&it); reftable_record_key(&recs[i], &want); - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -354,7 +355,7 @@ static void t_index_block_read_write(void) check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1)); want.len--; - ret = block_iter_seek_key(&it, &br, &want); + ret = block_iter_seek_key(&it, &want); check_int(ret, ==, 0); ret = block_iter_next(&it, &rec); @@ -362,22 +363,99 @@ static void t_index_block_read_write(void) check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1)); } - block_reader_release(&br); + reftable_block_release(&block); block_iter_close(&it); reftable_record_release(&rec); - reftable_block_done(&br.block); reftable_buf_release(&want); - reftable_buf_release(&buf); + reftable_buf_release(&block_data); for (i = 0; i < N; i++) reftable_record_release(&recs[i]); } +static void t_block_iterator(void) +{ + struct reftable_block_source source = { 0 }; + struct block_writer writer = { + .last_key = REFTABLE_BUF_INIT, + }; + struct reftable_record expected_refs[20]; + struct reftable_ref_record ref = { 0 }; + struct reftable_iterator it = { 0 }; + struct reftable_block block = { 0 }; + struct reftable_buf data; + int err; + + data.len = 1024; + REFTABLE_CALLOC_ARRAY(data.buf, data.len); + check(data.buf != NULL); + + err = block_writer_init(&writer, REFTABLE_BLOCK_TYPE_REF, (uint8_t *) data.buf, data.len, + 0, hash_size(REFTABLE_HASH_SHA1)); + check(!err); + + for (size_t i = 0; i < ARRAY_SIZE(expected_refs); i++) { + expected_refs[i] = (struct reftable_record) { + .type = REFTABLE_BLOCK_TYPE_REF, + .u.ref = { + .value_type = REFTABLE_REF_VAL1, + .refname = xstrfmt("refs/heads/branch-%02"PRIuMAX, (uintmax_t)i), + }, + }; + memset(expected_refs[i].u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1); + + err = block_writer_add(&writer, &expected_refs[i]); + check_int(err, ==, 0); + } + + err = block_writer_finish(&writer); + check_int(err, >, 0); + + block_source_from_buf(&source, &data); + reftable_block_init(&block, &source, 0, 0, data.len, REFTABLE_HASH_SIZE_SHA1); + + err = reftable_block_init_iterator(&block, &it); + check_int(err, ==, 0); + + for (size_t i = 0; ; i++) { + err = reftable_iterator_next_ref(&it, &ref); + if (err > 0) { + check_int(i, ==, ARRAY_SIZE(expected_refs)); + break; + } + check_int(err, ==, 0); + + check(reftable_ref_record_equal(&ref, &expected_refs[i].u.ref, + REFTABLE_HASH_SIZE_SHA1)); + } + + err = reftable_iterator_seek_ref(&it, "refs/heads/does-not-exist"); + check_int(err, ==, 0); + err = reftable_iterator_next_ref(&it, &ref); + check_int(err, ==, 1); + + err = reftable_iterator_seek_ref(&it, "refs/heads/branch-13"); + check_int(err, ==, 0); + err = reftable_iterator_next_ref(&it, &ref); + check_int(err, ==, 0); + check(reftable_ref_record_equal(&ref, &expected_refs[13].u.ref, + REFTABLE_HASH_SIZE_SHA1)); + + for (size_t i = 0; i < ARRAY_SIZE(expected_refs); i++) + reftable_free(expected_refs[i].u.ref.refname); + reftable_ref_record_release(&ref); + reftable_iterator_destroy(&it); + reftable_block_release(&block); + block_writer_release(&writer); + reftable_buf_release(&data); +} + int cmd_main(int argc UNUSED, const char *argv[] UNUSED) { TEST(t_index_block_read_write(), "read-write operations on index blocks work"); TEST(t_log_block_read_write(), "read-write operations on log blocks work"); TEST(t_obj_block_read_write(), "read-write operations on obj blocks work"); TEST(t_ref_block_read_write(), "read-write operations on ref blocks work"); + TEST(t_block_iterator(), "block iterator works"); return test_done(); } diff --git a/t/unit-tests/t-reftable-merged.c b/t/unit-tests/t-reftable-merged.c index 60836f80d6..18c3251a56 100644 --- a/t/unit-tests/t-reftable-merged.c +++ b/t/unit-tests/t-reftable-merged.c @@ -11,7 +11,7 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable/blocksource.h" #include "reftable/constants.h" #include "reftable/merged.h" -#include "reftable/reader.h" +#include "reftable/table.h" #include "reftable/reftable-error.h" #include "reftable/reftable-merged.h" #include "reftable/reftable-writer.h" @@ -19,7 +19,7 @@ https://developers.google.com/open-source/licenses/bsd static struct reftable_merged_table * merged_table_from_records(struct reftable_ref_record **refs, struct reftable_block_source **source, - struct reftable_reader ***readers, const size_t *sizes, + struct reftable_table ***tables, const size_t *sizes, struct reftable_buf *buf, const size_t n) { struct reftable_merged_table *mt = NULL; @@ -28,8 +28,8 @@ merged_table_from_records(struct reftable_ref_record **refs, }; int err; - REFTABLE_CALLOC_ARRAY(*readers, n); - check(*readers != NULL); + REFTABLE_CALLOC_ARRAY(*tables, n); + check(*tables != NULL); REFTABLE_CALLOC_ARRAY(*source, n); check(*source != NULL); @@ -37,21 +37,21 @@ merged_table_from_records(struct reftable_ref_record **refs, t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts); block_source_from_buf(&(*source)[i], &buf[i]); - err = reftable_reader_new(&(*readers)[i], &(*source)[i], - "name"); + err = reftable_table_new(&(*tables)[i], &(*source)[i], + "name"); check(!err); } - err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1); + err = reftable_merged_table_new(&mt, *tables, n, REFTABLE_HASH_SHA1); check(!err); return mt; } -static void readers_destroy(struct reftable_reader **readers, const size_t n) +static void tables_destroy(struct reftable_table **tables, const size_t n) { for (size_t i = 0; i < n; i++) - reftable_reader_decref(readers[i]); - reftable_free(readers); + reftable_table_decref(tables[i]); + reftable_free(tables); } static void t_merged_single_record(void) @@ -77,14 +77,14 @@ static void t_merged_single_record(void) size_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) }; struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT }; struct reftable_block_source *bs = NULL; - struct reftable_reader **readers = NULL; + struct reftable_table **tables = NULL; struct reftable_merged_table *mt = - merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3); + merged_table_from_records(refs, &bs, &tables, sizes, bufs, 3); struct reftable_ref_record ref = { 0 }; struct reftable_iterator it = { 0 }; int err; - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); check(!err); err = reftable_iterator_seek_ref(&it, "a"); check(!err); @@ -94,7 +94,7 @@ static void t_merged_single_record(void) check(reftable_ref_record_equal(&r2[0], &ref, REFTABLE_HASH_SIZE_SHA1)); reftable_ref_record_release(&ref); reftable_iterator_destroy(&it); - readers_destroy(readers, 3); + tables_destroy(tables, 3); reftable_merged_table_free(mt); for (size_t i = 0; i < ARRAY_SIZE(bufs); i++) reftable_buf_release(&bufs[i]); @@ -154,9 +154,9 @@ static void t_merged_refs(void) size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) }; struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT }; struct reftable_block_source *bs = NULL; - struct reftable_reader **readers = NULL; + struct reftable_table **tables = NULL; struct reftable_merged_table *mt = - merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3); + merged_table_from_records(refs, &bs, &tables, sizes, bufs, 3); struct reftable_iterator it = { 0 }; int err; struct reftable_ref_record *out = NULL; @@ -164,7 +164,7 @@ static void t_merged_refs(void) size_t cap = 0; size_t i; - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); check(!err); err = reftable_iterator_seek_ref(&it, "a"); check(!err); @@ -193,7 +193,7 @@ static void t_merged_refs(void) for (i = 0; i < 3; i++) reftable_buf_release(&bufs[i]); - readers_destroy(readers, 3); + tables_destroy(tables, 3); reftable_merged_table_free(mt); reftable_free(bs); } @@ -238,13 +238,13 @@ static void t_merged_seek_multiple_times(void) REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, }; struct reftable_block_source *sources = NULL; - struct reftable_reader **readers = NULL; + struct reftable_table **tables = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; struct reftable_merged_table *mt; - mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2); - merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + mt = merged_table_from_records(refs, &sources, &tables, sizes, bufs, 2); + merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); for (size_t i = 0; i < 5; i++) { int err = reftable_iterator_seek_ref(&it, "c"); @@ -266,7 +266,7 @@ static void t_merged_seek_multiple_times(void) for (size_t i = 0; i < ARRAY_SIZE(bufs); i++) reftable_buf_release(&bufs[i]); - readers_destroy(readers, ARRAY_SIZE(refs)); + tables_destroy(tables, ARRAY_SIZE(refs)); reftable_ref_record_release(&rec); reftable_iterator_destroy(&it); reftable_merged_table_free(mt); @@ -313,14 +313,14 @@ static void t_merged_seek_multiple_times_without_draining(void) REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, }; struct reftable_block_source *sources = NULL; - struct reftable_reader **readers = NULL; + struct reftable_table **tables = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; struct reftable_merged_table *mt; int err; - mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2); - merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + mt = merged_table_from_records(refs, &sources, &tables, sizes, bufs, 2); + merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_REF); err = reftable_iterator_seek_ref(&it, "b"); check(!err); @@ -338,7 +338,7 @@ static void t_merged_seek_multiple_times_without_draining(void) for (size_t i = 0; i < ARRAY_SIZE(bufs); i++) reftable_buf_release(&bufs[i]); - readers_destroy(readers, ARRAY_SIZE(refs)); + tables_destroy(tables, ARRAY_SIZE(refs)); reftable_ref_record_release(&rec); reftable_iterator_destroy(&it); reftable_merged_table_free(mt); @@ -348,7 +348,7 @@ static void t_merged_seek_multiple_times_without_draining(void) static struct reftable_merged_table * merged_table_from_log_records(struct reftable_log_record **logs, struct reftable_block_source **source, - struct reftable_reader ***readers, const size_t *sizes, + struct reftable_table ***tables, const size_t *sizes, struct reftable_buf *buf, const size_t n) { struct reftable_merged_table *mt = NULL; @@ -358,8 +358,8 @@ merged_table_from_log_records(struct reftable_log_record **logs, }; int err; - REFTABLE_CALLOC_ARRAY(*readers, n); - check(*readers != NULL); + REFTABLE_CALLOC_ARRAY(*tables, n); + check(*tables != NULL); REFTABLE_CALLOC_ARRAY(*source, n); check(*source != NULL); @@ -367,12 +367,12 @@ merged_table_from_log_records(struct reftable_log_record **logs, t_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts); block_source_from_buf(&(*source)[i], &buf[i]); - err = reftable_reader_new(&(*readers)[i], &(*source)[i], - "name"); + err = reftable_table_new(&(*tables)[i], &(*source)[i], + "name"); check(!err); } - err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1); + err = reftable_merged_table_new(&mt, *tables, n, REFTABLE_HASH_SHA1); check(!err); return mt; } @@ -435,9 +435,9 @@ static void t_merged_logs(void) size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) }; struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT }; struct reftable_block_source *bs = NULL; - struct reftable_reader **readers = NULL; + struct reftable_table **tables = NULL; struct reftable_merged_table *mt = merged_table_from_log_records( - logs, &bs, &readers, sizes, bufs, 3); + logs, &bs, &tables, sizes, bufs, 3); struct reftable_iterator it = { 0 }; int err; struct reftable_log_record *out = NULL; @@ -445,7 +445,7 @@ static void t_merged_logs(void) size_t cap = 0; size_t i; - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_LOG); check(!err); err = reftable_iterator_seek_log(&it, "a"); check(!err); @@ -469,7 +469,7 @@ static void t_merged_logs(void) check(reftable_log_record_equal(want[i], &out[i], REFTABLE_HASH_SIZE_SHA1)); - err = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = merged_table_init_iter(mt, &it, REFTABLE_BLOCK_TYPE_LOG); check(!err); err = reftable_iterator_seek_log_at(&it, "a", 2); check(!err); @@ -485,7 +485,7 @@ static void t_merged_logs(void) for (i = 0; i < 3; i++) reftable_buf_release(&bufs[i]); - readers_destroy(readers, 3); + tables_destroy(tables, 3); reftable_merged_table_free(mt); reftable_free(bs); } @@ -502,7 +502,7 @@ static void t_default_write_opts(void) int err; struct reftable_block_source source = { 0 }; uint32_t hash_id; - struct reftable_reader *rd = NULL; + struct reftable_table *table = NULL; struct reftable_merged_table *merged = NULL; reftable_writer_set_limits(w, 1, 1); @@ -516,18 +516,18 @@ static void t_default_write_opts(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&rd, &source, "filename"); + err = reftable_table_new(&table, &source, "filename"); check(!err); - hash_id = reftable_reader_hash_id(rd); + hash_id = reftable_table_hash_id(table); check_int(hash_id, ==, REFTABLE_HASH_SHA1); - err = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA256); + err = reftable_merged_table_new(&merged, &table, 1, REFTABLE_HASH_SHA256); check_int(err, ==, REFTABLE_FORMAT_ERROR); - err = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA1); + err = reftable_merged_table_new(&merged, &table, 1, REFTABLE_HASH_SHA1); check(!err); - reftable_reader_decref(rd); + reftable_table_decref(table); reftable_merged_table_free(merged); reftable_buf_release(&buf); } diff --git a/t/unit-tests/t-reftable-pq.c b/t/unit-tests/t-reftable-pq.c index c128fe8616..fb5a4eb187 100644 --- a/t/unit-tests/t-reftable-pq.c +++ b/t/unit-tests/t-reftable-pq.c @@ -34,7 +34,7 @@ static void t_pq_record(void) char *last = NULL; for (i = 0; i < N; i++) { - check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); + check(!reftable_record_init(&recs[i], REFTABLE_BLOCK_TYPE_REF)); recs[i].u.ref.refname = xstrfmt("%02"PRIuMAX, (uintmax_t)i); } @@ -57,7 +57,7 @@ static void t_pq_record(void) merged_iter_pqueue_check(&pq); check(pq_entry_equal(&top, &e)); - check(reftable_record_type(e.rec) == BLOCK_TYPE_REF); + check(reftable_record_type(e.rec) == REFTABLE_BLOCK_TYPE_REF); if (last) check_int(strcmp(last, e.rec->u.ref.refname), <, 0); last = e.rec->u.ref.refname; @@ -76,7 +76,7 @@ static void t_pq_index(void) size_t N = ARRAY_SIZE(recs), i; for (i = 0; i < N; i++) { - check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); + check(!reftable_record_init(&recs[i], REFTABLE_BLOCK_TYPE_REF)); recs[i].u.ref.refname = (char *) "refs/heads/master"; } @@ -100,7 +100,7 @@ static void t_pq_index(void) merged_iter_pqueue_check(&pq); check(pq_entry_equal(&top, &e)); - check(reftable_record_type(e.rec) == BLOCK_TYPE_REF); + check(reftable_record_type(e.rec) == REFTABLE_BLOCK_TYPE_REF); check_int(e.index, ==, i); if (last) check_str(last, e.rec->u.ref.refname); @@ -117,7 +117,7 @@ static void t_merged_iter_pqueue_top(void) size_t N = ARRAY_SIZE(recs), i; for (i = 0; i < N; i++) { - check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); + check(!reftable_record_init(&recs[i], REFTABLE_BLOCK_TYPE_REF)); recs[i].u.ref.refname = (char *) "refs/heads/master"; } diff --git a/t/unit-tests/t-reftable-reader.c b/t/unit-tests/t-reftable-reader.c deleted file mode 100644 index 546df6005e..0000000000 --- a/t/unit-tests/t-reftable-reader.c +++ /dev/null @@ -1,96 +0,0 @@ -#include "test-lib.h" -#include "lib-reftable.h" -#include "reftable/blocksource.h" -#include "reftable/reader.h" - -static int t_reader_seek_once(void) -{ - struct reftable_ref_record records[] = { - { - .refname = (char *) "refs/heads/main", - .value_type = REFTABLE_REF_VAL1, - .value.val1 = { 42 }, - }, - }; - struct reftable_block_source source = { 0 }; - struct reftable_ref_record ref = { 0 }; - struct reftable_iterator it = { 0 }; - struct reftable_reader *reader; - struct reftable_buf buf = REFTABLE_BUF_INIT; - int ret; - - t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); - block_source_from_buf(&source, &buf); - - ret = reftable_reader_new(&reader, &source, "name"); - check(!ret); - - reftable_reader_init_ref_iterator(reader, &it); - ret = reftable_iterator_seek_ref(&it, ""); - check(!ret); - ret = reftable_iterator_next_ref(&it, &ref); - check(!ret); - - ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1); - check_int(ret, ==, 1); - - ret = reftable_iterator_next_ref(&it, &ref); - check_int(ret, ==, 1); - - reftable_ref_record_release(&ref); - reftable_iterator_destroy(&it); - reftable_reader_decref(reader); - reftable_buf_release(&buf); - return 0; -} - -static int t_reader_reseek(void) -{ - struct reftable_ref_record records[] = { - { - .refname = (char *) "refs/heads/main", - .value_type = REFTABLE_REF_VAL1, - .value.val1 = { 42 }, - }, - }; - struct reftable_block_source source = { 0 }; - struct reftable_ref_record ref = { 0 }; - struct reftable_iterator it = { 0 }; - struct reftable_reader *reader; - struct reftable_buf buf = REFTABLE_BUF_INIT; - int ret; - - t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); - block_source_from_buf(&source, &buf); - - ret = reftable_reader_new(&reader, &source, "name"); - check(!ret); - - reftable_reader_init_ref_iterator(reader, &it); - - for (size_t i = 0; i < 5; i++) { - ret = reftable_iterator_seek_ref(&it, ""); - check(!ret); - ret = reftable_iterator_next_ref(&it, &ref); - check(!ret); - - ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1); - check_int(ret, ==, 1); - - ret = reftable_iterator_next_ref(&it, &ref); - check_int(ret, ==, 1); - } - - reftable_ref_record_release(&ref); - reftable_iterator_destroy(&it); - reftable_reader_decref(reader); - reftable_buf_release(&buf); - return 0; -} - -int cmd_main(int argc UNUSED, const char *argv[] UNUSED) -{ - TEST(t_reader_seek_once(), "reader can seek once"); - TEST(t_reader_reseek(), "reader can reseek multiple times"); - return test_done(); -} diff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c index c9626831da..4c49129439 100644 --- a/t/unit-tests/t-reftable-readwrite.c +++ b/t/unit-tests/t-reftable-readwrite.c @@ -12,9 +12,9 @@ https://developers.google.com/open-source/licenses/bsd #include "lib-reftable.h" #include "reftable/basics.h" #include "reftable/blocksource.h" -#include "reftable/reader.h" #include "reftable/reftable-error.h" #include "reftable/reftable-writer.h" +#include "reftable/table.h" #include "strbuf.h" static const int update_index = 5; @@ -23,22 +23,22 @@ static void t_buffer(void) { struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_block_source source = { 0 }; - struct reftable_block out = { 0 }; + struct reftable_block_data out = { 0 }; int n; uint8_t in[] = "hello"; check(!reftable_buf_add(&buf, in, sizeof(in))); block_source_from_buf(&source, &buf); check_int(block_source_size(&source), ==, 6); - n = block_source_read_block(&source, &out, 0, sizeof(in)); + n = block_source_read_data(&source, &out, 0, sizeof(in)); check_int(n, ==, sizeof(in)); check(!memcmp(in, out.data, n)); - reftable_block_done(&out); + block_source_release_data(&out); - n = block_source_read_block(&source, &out, 1, 2); + n = block_source_read_data(&source, &out, 1, 2); check_int(n, ==, 2); check(!memcmp(out.data, "el", 2)); - reftable_block_done(&out); + block_source_release_data(&out); block_source_close(&source); reftable_buf_release(&buf); } @@ -204,7 +204,7 @@ static void t_log_write_read(void) struct reftable_ref_record ref = { 0 }; struct reftable_log_record log = { 0 }; struct reftable_iterator it = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); @@ -254,10 +254,10 @@ static void t_log_write_read(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.log"); + err = reftable_table_new(&table, &source, "file.log"); check(!err); - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, names[N - 1]); @@ -273,7 +273,7 @@ static void t_log_write_read(void) reftable_iterator_destroy(&it); reftable_ref_record_release(&ref); - err = reftable_reader_init_log_iterator(reader, &it); + err = reftable_table_init_log_iterator(table, &it); check(!err); err = reftable_iterator_seek_log(&it, ""); check(!err); @@ -294,7 +294,7 @@ static void t_log_write_read(void) /* cleanup. */ reftable_buf_release(&buf); free_names(names); - reftable_reader_decref(reader); + reftable_table_decref(table); } static void t_log_zlib_corruption(void) @@ -303,7 +303,7 @@ static void t_log_zlib_corruption(void) .block_size = 256, }; struct reftable_iterator it = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); @@ -345,10 +345,10 @@ static void t_log_zlib_corruption(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.log"); + err = reftable_table_new(&table, &source, "file.log"); check(!err); - err = reftable_reader_init_log_iterator(reader, &it); + err = reftable_table_init_log_iterator(table, &it); check(!err); err = reftable_iterator_seek_log(&it, "refname"); check_int(err, ==, REFTABLE_ZLIB_ERROR); @@ -356,7 +356,7 @@ static void t_log_zlib_corruption(void) reftable_iterator_destroy(&it); /* cleanup. */ - reftable_reader_decref(reader); + reftable_table_decref(table); reftable_buf_release(&buf); } @@ -367,7 +367,7 @@ static void t_table_read_write_sequential(void) int N = 50; struct reftable_iterator it = { 0 }; struct reftable_block_source source = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; int err = 0; int j = 0; @@ -375,10 +375,10 @@ static void t_table_read_write_sequential(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.ref"); + err = reftable_table_new(&table, &source, "file.ref"); check(!err); - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, ""); check(!err); @@ -396,7 +396,7 @@ static void t_table_read_write_sequential(void) check_int(j, ==, N); reftable_iterator_destroy(&it); - reftable_reader_decref(reader); + reftable_table_decref(table); reftable_buf_release(&buf); free_names(names); } @@ -417,7 +417,7 @@ static void t_table_read_api(void) char **names; struct reftable_buf buf = REFTABLE_BUF_INIT; int N = 50; - struct reftable_reader *reader; + struct reftable_table *table; struct reftable_block_source source = { 0 }; int err; struct reftable_log_record log = { 0 }; @@ -427,10 +427,10 @@ static void t_table_read_api(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.ref"); + err = reftable_table_new(&table, &source, "file.ref"); check(!err); - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, names[0]); check(!err); @@ -441,7 +441,7 @@ static void t_table_read_api(void) reftable_buf_release(&buf); free_names(names); reftable_iterator_destroy(&it); - reftable_reader_decref(reader); + reftable_table_decref(table); reftable_buf_release(&buf); } @@ -450,7 +450,7 @@ static void t_table_read_write_seek(int index, enum reftable_hash hash_id) char **names; struct reftable_buf buf = REFTABLE_BUF_INIT; int N = 50; - struct reftable_reader *reader; + struct reftable_table *table; struct reftable_block_source source = { 0 }; int err; int i = 0; @@ -463,18 +463,18 @@ static void t_table_read_write_seek(int index, enum reftable_hash hash_id) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.ref"); + err = reftable_table_new(&table, &source, "file.ref"); check(!err); - check_int(hash_id, ==, reftable_reader_hash_id(reader)); + check_int(hash_id, ==, reftable_table_hash_id(table)); if (!index) { - reader->ref_offsets.index_offset = 0; + table->ref_offsets.index_offset = 0; } else { - check_int(reader->ref_offsets.index_offset, >, 0); + check_int(table->ref_offsets.index_offset, >, 0); } for (i = 1; i < N; i++) { - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, names[i]); check(!err); @@ -491,7 +491,7 @@ static void t_table_read_write_seek(int index, enum reftable_hash hash_id) check(!reftable_buf_addstr(&pastLast, names[N - 1])); check(!reftable_buf_addstr(&pastLast, "/")); - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, pastLast.buf); if (err == 0) { @@ -507,7 +507,7 @@ static void t_table_read_write_seek(int index, enum reftable_hash hash_id) reftable_buf_release(&buf); free_names(names); - reftable_reader_decref(reader); + reftable_table_decref(table); } static void t_table_read_write_seek_linear(void) @@ -535,7 +535,7 @@ static void t_table_refs_for(int indexed) .block_size = 256, }; struct reftable_ref_record ref = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); @@ -585,18 +585,18 @@ static void t_table_refs_for(int indexed) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.ref"); + err = reftable_table_new(&table, &source, "file.ref"); check(!err); if (!indexed) - reader->obj_offsets.is_present = 0; + table->obj_offsets.is_present = 0; - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, ""); check(!err); reftable_iterator_destroy(&it); - err = reftable_reader_refs_for(reader, &it, want_hash); + err = reftable_table_refs_for(table, &it, want_hash); check(!err); for (j = 0; ; j++) { @@ -613,7 +613,7 @@ static void t_table_refs_for(int indexed) reftable_buf_release(&buf); free_names(want_names); reftable_iterator_destroy(&it); - reftable_reader_decref(reader); + reftable_table_decref(table); } static void t_table_refs_for_no_index(void) @@ -632,7 +632,7 @@ static void t_write_empty_table(void) struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts); struct reftable_block_source source = { 0 }; - struct reftable_reader *rd = NULL; + struct reftable_table *table = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; int err; @@ -647,10 +647,10 @@ static void t_write_empty_table(void) block_source_from_buf(&source, &buf); - err = reftable_reader_new(&rd, &source, "filename"); + err = reftable_table_new(&table, &source, "filename"); check(!err); - err = reftable_reader_init_ref_iterator(rd, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, ""); check(!err); @@ -659,7 +659,7 @@ static void t_write_empty_table(void) check_int(err, >, 0); reftable_iterator_destroy(&it); - reftable_reader_decref(rd); + reftable_table_decref(table); reftable_buf_release(&buf); } @@ -803,7 +803,7 @@ static void t_write_multiple_indices(void) struct reftable_iterator it = { 0 }; const struct reftable_stats *stats; struct reftable_writer *writer; - struct reftable_reader *reader; + struct reftable_table *table; char buf[128]; int err, i; @@ -852,21 +852,21 @@ static void t_write_multiple_indices(void) check_int(stats->log_stats.index_offset, >, 0); block_source_from_buf(&source, &writer_buf); - err = reftable_reader_new(&reader, &source, "filename"); + err = reftable_table_new(&table, &source, "filename"); check(!err); /* * Seeking the log uses the log index now. In case there is any * confusion regarding indices we would notice here. */ - err = reftable_reader_init_log_iterator(reader, &it); + err = reftable_table_init_log_iterator(table, &it); check(!err); err = reftable_iterator_seek_log(&it, ""); check(!err); reftable_iterator_destroy(&it); reftable_writer_free(writer); - reftable_reader_decref(reader); + reftable_table_decref(table); reftable_buf_release(&writer_buf); } @@ -880,7 +880,7 @@ static void t_write_multi_level_index(void) struct reftable_iterator it = { 0 }; const struct reftable_stats *stats; struct reftable_writer *writer; - struct reftable_reader *reader; + struct reftable_table *table; int err; writer = t_reftable_strbuf_writer(&writer_buf, &opts); @@ -909,20 +909,20 @@ static void t_write_multi_level_index(void) check_int(stats->ref_stats.max_index_level, ==, 2); block_source_from_buf(&source, &writer_buf); - err = reftable_reader_new(&reader, &source, "filename"); + err = reftable_table_new(&table, &source, "filename"); check(!err); /* * Seeking the last ref should work as expected. */ - err = reftable_reader_init_ref_iterator(reader, &it); + err = reftable_table_init_ref_iterator(table, &it); check(!err); err = reftable_iterator_seek_ref(&it, "refs/heads/199"); check(!err); reftable_iterator_destroy(&it); reftable_writer_free(writer); - reftable_reader_decref(reader); + reftable_table_decref(table); reftable_buf_release(&writer_buf); reftable_buf_release(&buf); } @@ -931,11 +931,11 @@ static void t_corrupt_table_empty(void) { struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_block_source source = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; int err; block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.log"); + err = reftable_table_new(&table, &source, "file.log"); check_int(err, ==, REFTABLE_FORMAT_ERROR); } @@ -944,12 +944,12 @@ static void t_corrupt_table(void) uint8_t zeros[1024] = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_block_source source = { 0 }; - struct reftable_reader *reader; + struct reftable_table *table; int err; check(!reftable_buf_add(&buf, zeros, sizeof(zeros))); block_source_from_buf(&source, &buf); - err = reftable_reader_new(&reader, &source, "file.log"); + err = reftable_table_new(&table, &source, "file.log"); check_int(err, ==, REFTABLE_FORMAT_ERROR); reftable_buf_release(&buf); diff --git a/t/unit-tests/t-reftable-record.c b/t/unit-tests/t-reftable-record.c index 5954966373..553a007664 100644 --- a/t/unit-tests/t-reftable-record.c +++ b/t/unit-tests/t-reftable-record.c @@ -84,17 +84,17 @@ static void t_reftable_ref_record_comparison(void) { struct reftable_record in[3] = { { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u.ref.refname = (char *) "refs/heads/master", .u.ref.value_type = REFTABLE_REF_VAL1, }, { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u.ref.refname = (char *) "refs/heads/master", .u.ref.value_type = REFTABLE_REF_DELETION, }, { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u.ref.refname = (char *) "HEAD", .u.ref.value_type = REFTABLE_REF_SYMREF, .u.ref.value.symref = (char *) "refs/heads/master", @@ -141,10 +141,10 @@ static void t_reftable_ref_record_roundtrip(void) for (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) { struct reftable_record in = { - .type = BLOCK_TYPE_REF, + .type = REFTABLE_BLOCK_TYPE_REF, .u.ref.value_type = i, }; - struct reftable_record out = { .type = BLOCK_TYPE_REF }; + struct reftable_record out = { .type = REFTABLE_BLOCK_TYPE_REF }; struct reftable_buf key = REFTABLE_BUF_INIT; uint8_t buffer[1024] = { 0 }; struct string_view dest = { @@ -198,17 +198,17 @@ static void t_reftable_log_record_comparison(void) { struct reftable_record in[3] = { { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u.log.refname = (char *) "refs/heads/master", .u.log.update_index = 42, }, { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u.log.refname = (char *) "refs/heads/master", .u.log.update_index = 22, }, { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u.log.refname = (char *) "refs/heads/main", .u.log.update_index = 22, }, @@ -297,7 +297,7 @@ static void t_reftable_log_record_roundtrip(void) check(!reftable_log_record_is_deletion(&in[2])); for (size_t i = 0; i < ARRAY_SIZE(in); i++) { - struct reftable_record rec = { .type = BLOCK_TYPE_LOG }; + struct reftable_record rec = { .type = REFTABLE_BLOCK_TYPE_LOG }; struct reftable_buf key = REFTABLE_BUF_INIT; uint8_t buffer[1024] = { 0 }; struct string_view dest = { @@ -306,7 +306,7 @@ static void t_reftable_log_record_roundtrip(void) }; /* populate out, to check for leaks. */ struct reftable_record out = { - .type = BLOCK_TYPE_LOG, + .type = REFTABLE_BLOCK_TYPE_LOG, .u.log = { .refname = xstrdup("old name"), .value_type = REFTABLE_LOG_UPDATE, @@ -384,21 +384,21 @@ static void t_reftable_obj_record_comparison(void) uint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112}; struct reftable_record in[3] = { { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj.hash_prefix = id_bytes, .u.obj.hash_prefix_len = 7, .u.obj.offsets = offsets, .u.obj.offset_len = 8, }, { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj.hash_prefix = id_bytes, .u.obj.hash_prefix_len = 7, .u.obj.offsets = offsets, .u.obj.offset_len = 5, }, { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u.obj.hash_prefix = id_bytes, .u.obj.hash_prefix_len = 5, }, @@ -450,13 +450,13 @@ static void t_reftable_obj_record_roundtrip(void) .len = sizeof(buffer), }; struct reftable_record in = { - .type = BLOCK_TYPE_OBJ, + .type = REFTABLE_BLOCK_TYPE_OBJ, .u = { .obj = recs[i], }, }; struct reftable_buf key = REFTABLE_BUF_INIT; - struct reftable_record out = { .type = BLOCK_TYPE_OBJ }; + struct reftable_record out = { .type = REFTABLE_BLOCK_TYPE_OBJ }; int n, m; uint8_t extra; @@ -482,17 +482,17 @@ static void t_reftable_index_record_comparison(void) { struct reftable_record in[3] = { { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx.offset = 22, .u.idx.last_key = REFTABLE_BUF_INIT, }, { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx.offset = 32, .u.idx.last_key = REFTABLE_BUF_INIT, }, { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx.offset = 32, .u.idx.last_key = REFTABLE_BUF_INIT, }, @@ -523,7 +523,7 @@ static void t_reftable_index_record_comparison(void) static void t_reftable_index_record_roundtrip(void) { struct reftable_record in = { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx = { .offset = 42, .last_key = REFTABLE_BUF_INIT, @@ -537,7 +537,7 @@ static void t_reftable_index_record_roundtrip(void) struct reftable_buf scratch = REFTABLE_BUF_INIT; struct reftable_buf key = REFTABLE_BUF_INIT; struct reftable_record out = { - .type = BLOCK_TYPE_INDEX, + .type = REFTABLE_BLOCK_TYPE_INDEX, .u.idx = { .last_key = REFTABLE_BUF_INIT }, }; int n, m; diff --git a/t/unit-tests/t-reftable-stack.c b/t/unit-tests/t-reftable-stack.c index c3f0059c34..2f49c97519 100644 --- a/t/unit-tests/t-reftable-stack.c +++ b/t/unit-tests/t-reftable-stack.c @@ -12,9 +12,9 @@ https://developers.google.com/open-source/licenses/bsd #include "lib-reftable.h" #include "dir.h" #include "reftable/merged.h" -#include "reftable/reader.h" #include "reftable/reftable-error.h" #include "reftable/stack.h" +#include "reftable/table.h" #include "strbuf.h" #include "tempfile.h" #include <dirent.h> @@ -176,7 +176,7 @@ static void t_reftable_stack_add_one(void) err = reftable_stack_read_ref(st, ref.refname, &dest); check(!err); check(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1)); - check_int(st->readers_len, >, 0); + check_int(st->tables_len, >, 0); #ifndef GIT_WINDOWS_NATIVE check(!reftable_buf_addstr(&scratch, dir)); @@ -189,7 +189,7 @@ static void t_reftable_stack_add_one(void) check(!reftable_buf_addstr(&scratch, dir)); check(!reftable_buf_addstr(&scratch, "/")); /* do not try at home; not an external API for reftable. */ - check(!reftable_buf_addstr(&scratch, st->readers[0]->name)); + check(!reftable_buf_addstr(&scratch, st->tables[0]->name)); err = stat(scratch.buf, &stat_result); check(!err); check_int((stat_result.st_mode & 0777), ==, opts.default_permissions); @@ -402,9 +402,9 @@ static void t_reftable_stack_transaction_api_performs_auto_compaction(void) * all tables in the stack. */ if (i != n) - check_int(st->merged->readers_len, ==, i + 1); + check_int(st->merged->tables_len, ==, i + 1); else - check_int(st->merged->readers_len, ==, 1); + check_int(st->merged->tables_len, ==, 1); } reftable_stack_destroy(st); @@ -430,7 +430,7 @@ static void t_reftable_stack_auto_compaction_fails_gracefully(void) err = reftable_stack_add(st, write_test_ref, &ref); check(!err); - check_int(st->merged->readers_len, ==, 1); + check_int(st->merged->tables_len, ==, 1); check_int(st->stats.attempts, ==, 0); check_int(st->stats.failures, ==, 0); @@ -441,14 +441,14 @@ static void t_reftable_stack_auto_compaction_fails_gracefully(void) */ check(!reftable_buf_addstr(&table_path, dir)); check(!reftable_buf_addstr(&table_path, "/")); - check(!reftable_buf_addstr(&table_path, st->readers[0]->name)); + check(!reftable_buf_addstr(&table_path, st->tables[0]->name)); check(!reftable_buf_addstr(&table_path, ".lock")); write_file_buf(table_path.buf, "", 0); ref.update_index = 2; err = reftable_stack_add(st, write_test_ref, &ref); check(!err); - check_int(st->merged->readers_len, ==, 2); + check_int(st->merged->tables_len, ==, 2); check_int(st->stats.attempts, ==, 1); check_int(st->stats.failures, ==, 1); @@ -592,7 +592,7 @@ static void t_reftable_stack_add(void) check(!reftable_buf_addstr(&path, dir)); check(!reftable_buf_addstr(&path, "/")); /* do not try at home; not an external API for reftable. */ - check(!reftable_buf_addstr(&path, st->readers[0]->name)); + check(!reftable_buf_addstr(&path, st->tables[0]->name)); err = stat(path.buf, &stat_result); check(!err); check_int((stat_result.st_mode & 0777), ==, opts.default_permissions); @@ -1026,7 +1026,7 @@ static void t_reftable_stack_auto_compaction(void) err = reftable_stack_auto_compact(st); check(!err); - check(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2)); + check(i < 2 || st->merged->tables_len < 2 * fastlogN(i, 2)); } check_int(reftable_stack_compaction_stats(st)->entries_written, <, @@ -1061,7 +1061,7 @@ static void t_reftable_stack_auto_compaction_factor(void) err = reftable_stack_add(st, &write_test_ref, &ref); check(!err); - check(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5)); + check(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); } reftable_stack_destroy(st); @@ -1082,7 +1082,7 @@ static void t_reftable_stack_auto_compaction_with_locked_tables(void) check(!err); write_n_ref_tables(st, 5); - check_int(st->merged->readers_len, ==, 5); + check_int(st->merged->tables_len, ==, 5); /* * Given that all tables we have written should be roughly the same @@ -1091,7 +1091,7 @@ static void t_reftable_stack_auto_compaction_with_locked_tables(void) */ check(!reftable_buf_addstr(&buf, dir)); check(!reftable_buf_addstr(&buf, "/")); - check(!reftable_buf_addstr(&buf, st->readers[2]->name)); + check(!reftable_buf_addstr(&buf, st->tables[2]->name)); check(!reftable_buf_addstr(&buf, ".lock")); write_file_buf(buf.buf, "", 0); @@ -1104,7 +1104,7 @@ static void t_reftable_stack_auto_compaction_with_locked_tables(void) err = reftable_stack_auto_compact(st); check(!err); check_int(st->stats.failures, ==, 0); - check_int(st->merged->readers_len, ==, 4); + check_int(st->merged->tables_len, ==, 4); reftable_stack_destroy(st); reftable_buf_release(&buf); @@ -1149,9 +1149,9 @@ static void t_reftable_stack_add_performs_auto_compaction(void) * all tables in the stack. */ if (i != n) - check_int(st->merged->readers_len, ==, i + 1); + check_int(st->merged->tables_len, ==, i + 1); else - check_int(st->merged->readers_len, ==, 1); + check_int(st->merged->tables_len, ==, 1); } reftable_stack_destroy(st); @@ -1172,12 +1172,12 @@ static void t_reftable_stack_compaction_with_locked_tables(void) check(!err); write_n_ref_tables(st, 3); - check_int(st->merged->readers_len, ==, 3); + check_int(st->merged->tables_len, ==, 3); /* Lock one of the tables that we're about to compact. */ check(!reftable_buf_addstr(&buf, dir)); check(!reftable_buf_addstr(&buf, "/")); - check(!reftable_buf_addstr(&buf, st->readers[1]->name)); + check(!reftable_buf_addstr(&buf, st->tables[1]->name)); check(!reftable_buf_addstr(&buf, ".lock")); write_file_buf(buf.buf, "", 0); @@ -1188,7 +1188,7 @@ static void t_reftable_stack_compaction_with_locked_tables(void) err = reftable_stack_compact_all(st, NULL); check_int(err, ==, REFTABLE_LOCK_ERROR); check_int(st->stats.failures, ==, 1); - check_int(st->merged->readers_len, ==, 3); + check_int(st->merged->tables_len, ==, 3); reftable_stack_destroy(st); reftable_buf_release(&buf); @@ -1222,10 +1222,10 @@ static void t_reftable_stack_compaction_concurrent(void) static void unclean_stack_close(struct reftable_stack *st) { /* break abstraction boundary to simulate unclean shutdown. */ - for (size_t i = 0; i < st->readers_len; i++) - reftable_reader_decref(st->readers[i]); - st->readers_len = 0; - REFTABLE_FREE_AND_NULL(st->readers); + for (size_t i = 0; i < st->tables_len; i++) + reftable_table_decref(st->tables[i]); + st->tables_len = 0; + REFTABLE_FREE_AND_NULL(st->tables); } static void t_reftable_stack_compaction_concurrent_clean(void) @@ -1275,7 +1275,7 @@ static void t_reftable_stack_read_across_reload(void) err = reftable_new_stack(&st1, dir, &opts); check(!err); write_n_ref_tables(st1, 2); - check_int(st1->merged->readers_len, ==, 2); + check_int(st1->merged->tables_len, ==, 2); reftable_stack_init_ref_iterator(st1, &it); err = reftable_iterator_seek_ref(&it, ""); check(!err); @@ -1283,10 +1283,10 @@ static void t_reftable_stack_read_across_reload(void) /* Set up a second stack for the same directory and compact it. */ err = reftable_new_stack(&st2, dir, &opts); check(!err); - check_int(st2->merged->readers_len, ==, 2); + check_int(st2->merged->tables_len, ==, 2); err = reftable_stack_compact_all(st2, NULL); check(!err); - check_int(st2->merged->readers_len, ==, 1); + check_int(st2->merged->tables_len, ==, 1); /* * Verify that we can continue to use the old iterator even after we @@ -1294,7 +1294,7 @@ static void t_reftable_stack_read_across_reload(void) */ err = reftable_stack_reload(st1); check(!err); - check_int(st1->merged->readers_len, ==, 1); + check_int(st1->merged->tables_len, ==, 1); err = reftable_iterator_next_ref(&it, &rec); check(!err); check_str(rec.refname, "refs/heads/branch-0000"); @@ -1325,19 +1325,19 @@ static void t_reftable_stack_reload_with_missing_table(void) err = reftable_new_stack(&st, dir, &opts); check(!err); write_n_ref_tables(st, 2); - check_int(st->merged->readers_len, ==, 2); + check_int(st->merged->tables_len, ==, 2); reftable_stack_init_ref_iterator(st, &it); err = reftable_iterator_seek_ref(&it, ""); check(!err); /* * Update the tables.list file with some garbage data, while reusing - * our old readers. This should trigger a partial reload of the stack, - * where we try to reuse our old readers. + * our old tables. This should trigger a partial reload of the stack, + * where we try to reuse our old tables. */ - check(!reftable_buf_addstr(&content, st->readers[0]->name)); + check(!reftable_buf_addstr(&content, st->tables[0]->name)); check(!reftable_buf_addstr(&content, "\n")); - check(!reftable_buf_addstr(&content, st->readers[1]->name)); + check(!reftable_buf_addstr(&content, st->tables[1]->name)); check(!reftable_buf_addstr(&content, "\n")); check(!reftable_buf_addstr(&content, "garbage\n")); check(!reftable_buf_addstr(&table_path, st->list_file)); @@ -1348,7 +1348,7 @@ static void t_reftable_stack_reload_with_missing_table(void) err = reftable_stack_reload(st); check_int(err, ==, -4); - check_int(st->merged->readers_len, ==, 2); + check_int(st->merged->tables_len, ==, 2); /* * Even though the reload has failed, we should be able to continue diff --git a/t/unit-tests/t-reftable-table.c b/t/unit-tests/t-reftable-table.c new file mode 100644 index 0000000000..7e1eb533d0 --- /dev/null +++ b/t/unit-tests/t-reftable-table.c @@ -0,0 +1,206 @@ +#include "test-lib.h" +#include "lib-reftable.h" +#include "reftable/blocksource.h" +#include "reftable/constants.h" +#include "reftable/iter.h" +#include "reftable/table.h" +#include "strbuf.h" + +static int t_table_seek_once(void) +{ + struct reftable_ref_record records[] = { + { + .refname = (char *) "refs/heads/main", + .value_type = REFTABLE_REF_VAL1, + .value.val1 = { 42 }, + }, + }; + struct reftable_block_source source = { 0 }; + struct reftable_ref_record ref = { 0 }; + struct reftable_iterator it = { 0 }; + struct reftable_table *table; + struct reftable_buf buf = REFTABLE_BUF_INIT; + int ret; + + t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); + block_source_from_buf(&source, &buf); + + ret = reftable_table_new(&table, &source, "name"); + check(!ret); + + reftable_table_init_ref_iterator(table, &it); + ret = reftable_iterator_seek_ref(&it, ""); + check(!ret); + ret = reftable_iterator_next_ref(&it, &ref); + check(!ret); + + ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1); + check_int(ret, ==, 1); + + ret = reftable_iterator_next_ref(&it, &ref); + check_int(ret, ==, 1); + + reftable_ref_record_release(&ref); + reftable_iterator_destroy(&it); + reftable_table_decref(table); + reftable_buf_release(&buf); + return 0; +} + +static int t_table_reseek(void) +{ + struct reftable_ref_record records[] = { + { + .refname = (char *) "refs/heads/main", + .value_type = REFTABLE_REF_VAL1, + .value.val1 = { 42 }, + }, + }; + struct reftable_block_source source = { 0 }; + struct reftable_ref_record ref = { 0 }; + struct reftable_iterator it = { 0 }; + struct reftable_table *table; + struct reftable_buf buf = REFTABLE_BUF_INIT; + int ret; + + t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); + block_source_from_buf(&source, &buf); + + ret = reftable_table_new(&table, &source, "name"); + check(!ret); + + reftable_table_init_ref_iterator(table, &it); + + for (size_t i = 0; i < 5; i++) { + ret = reftable_iterator_seek_ref(&it, ""); + check(!ret); + ret = reftable_iterator_next_ref(&it, &ref); + check(!ret); + + ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1); + check_int(ret, ==, 1); + + ret = reftable_iterator_next_ref(&it, &ref); + check_int(ret, ==, 1); + } + + reftable_ref_record_release(&ref); + reftable_iterator_destroy(&it); + reftable_table_decref(table); + reftable_buf_release(&buf); + return 0; +} + +static int t_table_block_iterator(void) +{ + struct reftable_block_source source = { 0 }; + struct reftable_table_iterator it = { 0 }; + struct reftable_ref_record *records; + const struct reftable_block *block; + struct reftable_table *table; + struct reftable_buf buf = REFTABLE_BUF_INIT; + struct { + uint8_t block_type; + uint16_t header_off; + uint16_t restart_count; + uint16_t record_count; + } expected_blocks[] = { + { + .block_type = REFTABLE_BLOCK_TYPE_REF, + .header_off = 24, + .restart_count = 10, + .record_count = 158, + }, + { + .block_type = REFTABLE_BLOCK_TYPE_REF, + .restart_count = 10, + .record_count = 159, + }, + { + .block_type = REFTABLE_BLOCK_TYPE_REF, + .restart_count = 10, + .record_count = 159, + }, + { + .block_type = REFTABLE_BLOCK_TYPE_REF, + .restart_count = 2, + .record_count = 24, + }, + { + .block_type = REFTABLE_BLOCK_TYPE_INDEX, + .restart_count = 1, + .record_count = 4, + }, + { + .block_type = REFTABLE_BLOCK_TYPE_OBJ, + .restart_count = 1, + .record_count = 1, + }, + }; + const size_t nrecords = 500; + int ret; + + REFTABLE_CALLOC_ARRAY(records, nrecords); + for (size_t i = 0; i < nrecords; i++) { + records[i].value_type = REFTABLE_REF_VAL1; + records[i].refname = xstrfmt("refs/heads/branch-%03"PRIuMAX, + (uintmax_t) i); + } + + t_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL); + block_source_from_buf(&source, &buf); + + ret = reftable_table_new(&table, &source, "name"); + check(!ret); + + ret = reftable_table_iterator_init(&it, table); + check(!ret); + + for (size_t i = 0; i < ARRAY_SIZE(expected_blocks); i++) { + struct reftable_iterator record_it = { 0 }; + struct reftable_record record = { + .type = expected_blocks[i].block_type, + }; + + ret = reftable_table_iterator_next(&it, &block); + check(!ret); + + check_int(block->block_type, ==, expected_blocks[i].block_type); + check_int(block->header_off, ==, expected_blocks[i].header_off); + check_int(block->restart_count, ==, expected_blocks[i].restart_count); + + ret = reftable_block_init_iterator(block, &record_it); + check(!ret); + + for (size_t j = 0; ; j++) { + ret = iterator_next(&record_it, &record); + if (ret > 0) { + check_int(j, ==, expected_blocks[i].record_count); + break; + } + check(!ret); + } + + reftable_iterator_destroy(&record_it); + reftable_record_release(&record); + } + + ret = reftable_table_iterator_next(&it, &block); + check_int(ret, ==, 1); + + for (size_t i = 0; i < nrecords; i++) + reftable_free(records[i].refname); + reftable_table_iterator_release(&it); + reftable_table_decref(table); + reftable_buf_release(&buf); + reftable_free(records); + return 0; +} + +int cmd_main(int argc UNUSED, const char *argv[] UNUSED) +{ + TEST(t_table_seek_once(), "table can seek once"); + TEST(t_table_reseek(), "table can reseek multiple times"); + TEST(t_table_block_iterator(), "table can iterate through blocks"); + return test_done(); +} diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c deleted file mode 100644 index 184593e73d..0000000000 --- a/t/unit-tests/t-trailer.c +++ /dev/null @@ -1,317 +0,0 @@ -#define DISABLE_SIGN_COMPARE_WARNINGS - -#include "test-lib.h" -#include "trailer.h" - -struct contents { - const char *raw; - const char *key; - const char *val; -}; - -static void t_trailer_iterator(const char *msg, size_t num_expected, - struct contents *contents) -{ - struct trailer_iterator iter; - size_t i = 0; - - trailer_iterator_init(&iter, msg); - while (trailer_iterator_advance(&iter)) { - if (num_expected) { - check_str(iter.raw, contents[i].raw); - check_str(iter.key.buf, contents[i].key); - check_str(iter.val.buf, contents[i].val); - } - i++; - } - trailer_iterator_release(&iter); - - check_uint(i, ==, num_expected); -} - -static void run_t_trailer_iterator(void) -{ - - static struct test_cases { - const char *name; - const char *msg; - size_t num_expected; - struct contents contents[10]; - } tc[] = { - { - "empty input", - "", - 0, - {{0}}, - }, - { - "no newline at beginning", - "Fixes: x\n" - "Acked-by: x\n" - "Reviewed-by: x\n", - 0, - {{0}}, - }, - { - "newline at beginning", - "\n" - "Fixes: x\n" - "Acked-by: x\n" - "Reviewed-by: x\n", - 3, - { - { - .raw = "Fixes: x\n", - .key = "Fixes", - .val = "x", - }, - { - .raw = "Acked-by: x\n", - .key = "Acked-by", - .val = "x", - }, - { - .raw = "Reviewed-by: x\n", - .key = "Reviewed-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "without body text", - "subject: foo bar\n" - "\n" - "Fixes: x\n" - "Acked-by: x\n" - "Reviewed-by: x\n", - 3, - { - { - .raw = "Fixes: x\n", - .key = "Fixes", - .val = "x", - }, - { - .raw = "Acked-by: x\n", - .key = "Acked-by", - .val = "x", - }, - { - .raw = "Reviewed-by: x\n", - .key = "Reviewed-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "with body text, without divider", - "my subject\n" - "\n" - "my body which is long\n" - "and contains some special\n" - "chars like : = ? !\n" - "hello\n" - "\n" - "Fixes: x\n" - "Acked-by: x\n" - "Reviewed-by: x\n" - "Signed-off-by: x\n", - 4, - { - { - .raw = "Fixes: x\n", - .key = "Fixes", - .val = "x", - }, - { - .raw = "Acked-by: x\n", - .key = "Acked-by", - .val = "x", - }, - { - .raw = "Reviewed-by: x\n", - .key = "Reviewed-by", - .val = "x", - }, - { - .raw = "Signed-off-by: x\n", - .key = "Signed-off-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "with body text, without divider (second trailer block)", - "my subject\n" - "\n" - "my body which is long\n" - "and contains some special\n" - "chars like : = ? !\n" - "hello\n" - "\n" - "Fixes: x\n" - "Acked-by: x\n" - "Reviewed-by: x\n" - "Signed-off-by: x\n" - "\n" - /* - * Because this is the last trailer block, it takes - * precedence over the first one encountered above. - */ - "Helped-by: x\n" - "Signed-off-by: x\n", - 2, - { - { - .raw = "Helped-by: x\n", - .key = "Helped-by", - .val = "x", - }, - { - .raw = "Signed-off-by: x\n", - .key = "Signed-off-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "with body text, with divider", - "my subject\n" - "\n" - "my body which is long\n" - "and contains some special\n" - "chars like : = ? !\n" - "hello\n" - "\n" - "---\n" - "\n" - /* - * This trailer still counts because the iterator - * always ignores the divider. - */ - "Signed-off-by: x\n", - 1, - { - { - .raw = "Signed-off-by: x\n", - .key = "Signed-off-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "with non-trailer lines in trailer block", - "subject: foo bar\n" - "\n" - /* - * Even though this trailer block has a non-trailer line - * in it, it's still a valid trailer block because it's - * at least 25% trailers and is Git-generated (see - * git_generated_prefixes[] in trailer.c). - */ - "not a trailer line\n" - "not a trailer line\n" - "not a trailer line\n" - "Signed-off-by: x\n", - /* - * Even though there is only really 1 real "trailer" - * (Signed-off-by), we still have 4 trailer objects - * because we still want to iterate through the entire - * block. - */ - 4, - { - { - .raw = "not a trailer line\n", - .key = "not a trailer line", - .val = "", - }, - { - .raw = "not a trailer line\n", - .key = "not a trailer line", - .val = "", - }, - { - .raw = "not a trailer line\n", - .key = "not a trailer line", - .val = "", - }, - { - .raw = "Signed-off-by: x\n", - .key = "Signed-off-by", - .val = "x", - }, - { - 0 - }, - }, - }, - { - "with non-trailer lines (one too many) in trailer block", - "subject: foo bar\n" - "\n" - /* - * This block has only 20% trailers, so it's below the - * 25% threshold. - */ - "not a trailer line\n" - "not a trailer line\n" - "not a trailer line\n" - "not a trailer line\n" - "Signed-off-by: x\n", - 0, - {{0}}, - }, - { - "with non-trailer lines (only 1) in trailer block, but no Git-generated trailers", - "subject: foo bar\n" - "\n" - /* - * This block has only 1 non-trailer out of 10 (IOW, 90% - * trailers) but is not considered a trailer block - * because the 25% threshold only applies to cases where - * there was a Git-generated trailer. - */ - "Reviewed-by: x\n" - "Reviewed-by: x\n" - "Reviewed-by: x\n" - "Helped-by: x\n" - "Helped-by: x\n" - "Helped-by: x\n" - "Acked-by: x\n" - "Acked-by: x\n" - "Acked-by: x\n" - "not a trailer line\n", - 0, - {{0}}, - }, - }; - - for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) { - TEST(t_trailer_iterator(tc[i].msg, - tc[i].num_expected, - tc[i].contents), - "%s", tc[i].name); - } -} - -int cmd_main(int argc UNUSED, const char **argv UNUSED) -{ - run_t_trailer_iterator(); - return test_done(); -} diff --git a/t/unit-tests/u-trailer.c b/t/unit-tests/u-trailer.c new file mode 100644 index 0000000000..3d60ea1603 --- /dev/null +++ b/t/unit-tests/u-trailer.c @@ -0,0 +1,320 @@ +#define DISABLE_SIGN_COMPARE_WARNINGS + +#include "unit-test.h" +#include "trailer.h" + +struct contents { + const char *raw; + const char *key; + const char *val; +}; + +static void t_trailer_iterator(const char *msg, size_t num_expected, + struct contents *contents) +{ + struct trailer_iterator iter; + size_t i = 0; + + trailer_iterator_init(&iter, msg); + while (trailer_iterator_advance(&iter)) { + if (num_expected) { + cl_assert_equal_s(iter.raw, contents[i].raw); + cl_assert_equal_s(iter.key.buf, contents[i].key); + cl_assert_equal_s(iter.val.buf, contents[i].val); + } + i++; + } + trailer_iterator_release(&iter); + + cl_assert_equal_i(i, num_expected); +} + +void test_trailer__empty_input(void) +{ + struct contents expected_contents[] = { 0 }; + t_trailer_iterator("", 0, expected_contents); +} + +void test_trailer__no_newline_start(void) +{ + struct contents expected_contents[] = { 0 }; + + t_trailer_iterator("Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 0, + expected_contents); +} + +void test_trailer__newline_start(void) +{ + struct contents expected_contents[] = { + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 3, + expected_contents); +} + +void test_trailer__no_body_text(void) +{ + struct contents expected_contents[] = { + + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("subject: foo bar\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 3, + expected_contents); +} + +void test_trailer__body_text_no_divider(void) +{ + struct contents expected_contents[] = { + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n" + "Signed-off-by: x\n", + 4, + expected_contents); +} + +void test_trailer__body_no_divider_2nd_block(void) +{ + struct contents expected_contents[] = { + { + .raw = "Helped-by: x\n", + .key = "Helped-by", + .val = "x", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n" + "Signed-off-by: x\n" + "\n" + /* + * Because this is the last trailer block, it takes + * precedence over the first one encountered above. + */ + "Helped-by: x\n" + "Signed-off-by: x\n", + 2, + expected_contents); +} + +void test_trailer__body_and_divider(void) +{ + struct contents expected_contents[] = { + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "---\n" + "\n" + /* + * This trailer still counts because the iterator + * always ignores the divider. + */ + "Signed-off-by: x\n", + 1, + expected_contents); +} + +void test_trailer__non_trailer_in_block(void) +{ + struct contents expected_contents[] = { + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }; + + t_trailer_iterator("subject: foo bar\n" + "\n" + /* + * Even though this trailer block has a non-trailer line + * in it, it's still a valid trailer block because it's + * at least 25% trailers and is Git-generated (see + * git_generated_prefixes[] in trailer.c). + */ + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "Signed-off-by: x\n", + /* + * Even though there is only really 1 real "trailer" + * (Signed-off-by), we still have 4 trailer objects + * because we still want to iterate through the entire + * block. + */ + 4, + expected_contents); +} + +void test_trailer__too_many_non_trailers(void) +{ + struct contents expected_contents[] = { 0 }; + + t_trailer_iterator("subject: foo bar\n" + "\n" + /* + * This block has only 20% trailers, so it's below the + * 25% threshold. + */ + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "Signed-off-by: x\n", + 0, + expected_contents); +} + +void test_trailer__one_non_trailer_no_git_trailers(void) +{ + struct contents expected_contents[] = { 0 }; + + t_trailer_iterator("subject: foo bar\n" + "\n" + /* + * This block has only 1 non-trailer out of 10 (IOW, 90% + * trailers) but is not considered a trailer block + * because the 25% threshold only applies to cases where + * there was a Git-generated trailer. + */ + "Reviewed-by: x\n" + "Reviewed-by: x\n" + "Reviewed-by: x\n" + "Helped-by: x\n" + "Helped-by: x\n" + "Helped-by: x\n" + "Acked-by: x\n" + "Acked-by: x\n" + "Acked-by: x\n" + "not a trailer line\n", + 0, + expected_contents); +} diff --git a/t/unit-tests/t-urlmatch-normalization.c b/t/unit-tests/u-urlmatch-normalization.c index 1769c357b9..39f6e1ba26 100644 --- a/t/unit-tests/t-urlmatch-normalization.c +++ b/t/unit-tests/u-urlmatch-normalization.c @@ -1,12 +1,11 @@ -#include "test-lib.h" +#include "unit-test.h" #include "urlmatch.h" static void check_url_normalizable(const char *url, unsigned int normalizable) { char *url_norm = url_normalize(url, NULL); - if (!check_int(normalizable, ==, url_norm ? 1 : 0)) - test_msg("input url: %s", url); + cl_assert_equal_i(normalizable, url_norm ? 1 : 0); free(url_norm); } @@ -14,8 +13,7 @@ static void check_normalized_url(const char *url, const char *expect) { char *url_norm = url_normalize(url, NULL); - if (!check_str(url_norm, expect)) - test_msg("input url: %s", url); + cl_assert_equal_s(url_norm, expect); free(url_norm); } @@ -26,13 +24,9 @@ static void compare_normalized_urls(const char *url1, const char *url2, char *url2_norm = url_normalize(url2, NULL); if (equal) { - if (!check_str(url1_norm, url2_norm)) - test_msg("input url1: %s\n input url2: %s", url1, - url2); - } else if (!check_int(strcmp(url1_norm, url2_norm), !=, 0)) { - test_msg(" normalized url1: %s\n normalized url2: %s\n" - " input url1: %s\n input url2: %s", - url1_norm, url2_norm, url1, url2); + cl_assert_equal_s(url1_norm, url2_norm); + } else { + cl_assert(strcmp(url1_norm, url2_norm) != 0); } free(url1_norm); free(url2_norm); @@ -43,14 +37,12 @@ static void check_normalized_url_length(const char *url, size_t len) struct url_info info; char *url_norm = url_normalize(url, &info); - if (!check_int(info.url_len, ==, len)) - test_msg(" input url: %s\n normalized url: %s", url, - url_norm); + cl_assert_equal_i(info.url_len, len); free(url_norm); } /* Note that only "file:" URLs should be allowed without a host */ -static void t_url_scheme(void) +void test_urlmatch_normalization__scheme(void) { check_url_normalizable("", 0); check_url_normalizable("_", 0); @@ -73,7 +65,7 @@ static void t_url_scheme(void) check_normalized_url("AbCdeF://x.Y", "abcdef://x.y/"); } -static void t_url_authority(void) +void test_urlmatch_normalization__authority(void) { check_url_normalizable("scheme://user:pass@", 0); check_url_normalizable("scheme://?", 0); @@ -109,7 +101,7 @@ static void t_url_authority(void) check_url_normalizable("scheme://invalid....:[", 0); } -static void t_url_port(void) +void test_urlmatch_normalization__port(void) { check_url_normalizable("xyz://q@some.host:", 1); check_url_normalizable("xyz://q@some.host:456/", 1); @@ -139,7 +131,7 @@ static void t_url_port(void) check_url_normalizable("xyz://[::1]:030f/", 0); } -static void t_url_port_normalization(void) +void test_urlmatch_normalization__port_normalization(void) { check_normalized_url("http://x:800", "http://x:800/"); check_normalized_url("http://x:0800", "http://x:800/"); @@ -154,7 +146,7 @@ static void t_url_port_normalization(void) check_normalized_url("https://x:000000443", "https://x/"); } -static void t_url_general_escape(void) +void test_urlmatch_normalization__general_escape(void) { check_url_normalizable("http://x.y?%fg", 0); check_normalized_url("X://W/%7e%41^%3a", "x://w/~A%5E%3A"); @@ -164,7 +156,7 @@ static void t_url_general_escape(void) check_normalized_url("X://W?!", "x://w/?!"); } -static void t_url_high_bit(void) +void test_urlmatch_normalization__high_bit(void) { check_normalized_url( "x://q/\x01\x02\x03\x04\x05\x06\x07\x08\x0e\x0f\x10\x11\x12", @@ -198,26 +190,26 @@ static void t_url_high_bit(void) "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"); } -static void t_url_utf8_escape(void) +void test_urlmatch_normalization__utf8_escape(void) { check_normalized_url( "x://q/\xc2\x80\xdf\xbf\xe0\xa0\x80\xef\xbf\xbd\xf0\x90\x80\x80\xf0\xaf\xbf\xbd", "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD"); } -static void t_url_username_pass(void) +void test_urlmatch_normalization__username_pass(void) { check_normalized_url("x://%41%62(^):%70+d@foo", "x://Ab(%5E):p+d@foo/"); } -static void t_url_length(void) +void test_urlmatch_normalization__length(void) { check_normalized_url_length("Http://%4d%65:%4d^%70@The.Host", 25); check_normalized_url_length("http://%41:%42@x.y/%61/", 17); check_normalized_url_length("http://@x.y/^", 15); } -static void t_url_dots(void) +void test_urlmatch_normalization__dots(void) { check_normalized_url("x://y/.", "x://y/"); check_normalized_url("x://y/./", "x://y/"); @@ -244,7 +236,7 @@ static void t_url_dots(void) * "http://foo" specifies neither a user name nor a password. * So they should not be equivalent. */ -static void t_url_equivalents(void) +void test_urlmatch_normalization__equivalents(void) { compare_normalized_urls("httP://x", "Http://X/", 1); compare_normalized_urls("Http://%4d%65:%4d^%70@The.Host", "hTTP://Me:%4D^p@the.HOST:80/", 1); @@ -253,19 +245,3 @@ static void t_url_equivalents(void) compare_normalized_urls("https://@x.y/^/../abc", "httpS://@x.y:0443/abc", 1); compare_normalized_urls("https://@x.y/^/..", "httpS://@x.y:0443/", 1); } - -int cmd_main(int argc UNUSED, const char **argv UNUSED) -{ - TEST(t_url_scheme(), "url scheme"); - TEST(t_url_authority(), "url authority"); - TEST(t_url_port(), "url port checks"); - TEST(t_url_port_normalization(), "url port normalization"); - TEST(t_url_general_escape(), "url general escapes"); - TEST(t_url_high_bit(), "url high-bit escapes"); - TEST(t_url_utf8_escape(), "url utf8 escapes"); - TEST(t_url_username_pass(), "url username/password escapes"); - TEST(t_url_length(), "url normalized lengths"); - TEST(t_url_dots(), "url . and .. segments"); - TEST(t_url_equivalents(), "url equivalents"); - return test_done(); -} @@ -5,7 +5,7 @@ #include "environment.h" #include "tag.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "tree.h" #include "blob.h" diff --git a/templates/meson.build b/templates/meson.build index 1faf9a44ce..02e6eebe80 100644 --- a/templates/meson.build +++ b/templates/meson.build @@ -1,6 +1,6 @@ template_config = configuration_data() -template_config.set('PERL_PATH', perl.found() ? fs.as_posix(perl.full_path()) : '') -template_config.set('SHELL_PATH', fs.as_posix(shell.full_path())) +template_config.set('PERL_PATH', target_perl.found() ? fs.as_posix(target_perl.full_path()) : '') +template_config.set('SHELL_PATH', fs.as_posix(target_shell.full_path())) template_config.set('GITWEBDIR', fs.as_posix(get_option('prefix') / get_option('datadir') / 'gitweb')) configure_file( diff --git a/tmp-objdir.c b/tmp-objdir.c index 31d16a4c2c..c38fbeb5e8 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -10,7 +10,7 @@ #include "strbuf.h" #include "strvec.h" #include "quote.h" -#include "object-store-ll.h" +#include "object-store.h" #include "repository.h" struct tmp_objdir { diff --git a/tree-diff.c b/tree-diff.c index 60c558c2b5..e00fc2f450 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -181,7 +181,7 @@ static void emit_path(struct combine_diff_path ***tail, strbuf_add(base, path, pathlen); p = combine_diff_path_new(base->buf, base->len, mode, - oid ? oid : null_oid(), + oid ? oid : null_oid(the_hash_algo), nparent); strbuf_setlen(base, old_baselen); @@ -206,7 +206,7 @@ static void emit_path(struct combine_diff_path ***tail, mode_i = tp[i].entry.mode; } else { - oid_i = null_oid(); + oid_i = null_oid(the_hash_algo); mode_i = 0; } diff --git a/tree-walk.c b/tree-walk.c index a033397965..90655d5237 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -6,7 +6,7 @@ #include "gettext.h" #include "hex.h" #include "object-file.h" -#include "object-store-ll.h" +#include "object-store.h" #include "trace2.h" #include "tree.h" #include "pathspec.h" @@ -4,7 +4,7 @@ #include "hex.h" #include "tree.h" #include "object-name.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "alloc.h" #include "tree-walk.h" diff --git a/unpack-trees.c b/unpack-trees.c index cf5b73c84b..471837f032 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -26,7 +26,7 @@ #include "symlinks.h" #include "trace2.h" #include "fsmonitor.h" -#include "object-store-ll.h" +#include "object-store.h" #include "promisor-remote.h" #include "entry.h" #include "parallel-checkout.h" diff --git a/upload-pack.c b/upload-pack.c index 7498b45e2e..30e4630f3a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -10,7 +10,7 @@ #include "pkt-line.h" #include "sideband.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "oid-array.h" #include "object.h" #include "commit.h" @@ -665,8 +665,8 @@ static int do_reachable_revlist(struct child_process *cmd, cmd_in = xfdopen(cmd->in, "w"); - for (i = get_max_object_index(); 0 < i; ) { - o = get_indexed_object(--i); + for (i = get_max_object_index(the_repository); 0 < i; ) { + o = get_indexed_object(the_repository, --i); if (!o) continue; if (reachable && o->type == OBJ_COMMIT) @@ -734,8 +734,8 @@ static int get_reachable_list(struct upload_pack_data *data, o->flags &= ~TMP_MARK; } } - for (i = get_max_object_index(); 0 < i; i--) { - o = get_indexed_object(i - 1); + for (i = get_max_object_index(the_repository); 0 < i; i--) { + o = get_indexed_object(the_repository, i - 1); if (o && o->type == OBJ_COMMIT && (o->flags & TMP_MARK)) { add_object_array(o, NULL, reachable); @@ -1449,7 +1449,7 @@ void upload_pack(const int advertise_refs, const int stateless_rpc, for_each_namespaced_ref_1(send_ref, &data); if (!data.sent_capabilities) { const char *refname = "capabilities^{}"; - write_v0_ref(&data, refname, refname, null_oid()); + write_v0_ref(&data, refname, refname, null_oid(the_hash_algo)); } /* * fflush stdout before calling advertise_shallow_grafts because send_ref @@ -1557,7 +1557,7 @@ static int parse_want_ref(struct packet_writer *writer, const char *line, } if (!o) - o = parse_object_or_die(&oid, refname_nons); + o = parse_object_or_die(the_repository, &oid, refname_nons); if (!(o->flags & WANTED)) { o->flags |= WANTED; @@ -1793,7 +1793,7 @@ int upload_pack_v2(struct repository *r, struct packet_reader *request) enum fetch_state state = FETCH_PROCESS_ARGS; struct upload_pack_data data; - clear_object_flags(ALL_FLAGS); + clear_object_flags(the_repository, ALL_FLAGS); upload_pack_data_init(&data); data.use_sideband = LARGE_PACKET_MAX; diff --git a/userdiff.c b/userdiff.c index 340c4eb4f7..da75625020 100644 --- a/userdiff.c +++ b/userdiff.c @@ -211,6 +211,10 @@ PATTERNS("html", "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$", /* -- */ "[^<>= \t]+"), +PATTERNS("ini", + "^[ \t]*\\[[^]]+\\]", + /* -- */ + "[^ \t]+"), PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" /* Class, enum, interface, and record declarations */ @@ -5,7 +5,7 @@ #include "hex.h" #include "walker.h" #include "repository.h" -#include "object-store-ll.h" +#include "object-store.h" #include "commit.h" #include "strbuf.h" #include "tree.h" diff --git a/wildmatch.c b/wildmatch.c index 8ea29141bd..69a2ae7000 100644 --- a/wildmatch.c +++ b/wildmatch.c @@ -223,7 +223,7 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) p_ch = '['; if (t_ch == p_ch) matched = 1; - continue; + goto next; } if (CC_EQ(s,i, "alnum")) { if (ISALNUM(t_ch)) @@ -268,7 +268,10 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (t_ch == p_ch) matched = 1; - } while (prev_ch = p_ch, (p_ch = *++p) != ']'); +next: + prev_ch = p_ch; + p_ch = *++p; + } while (p_ch != ']'); if (matched == negated || ((flags & WM_PATHNAME) && t_ch == '/')) return WM_NOMATCH; @@ -829,3 +829,51 @@ uint32_t git_rand(unsigned flags) return result; } + +static void mmap_limit_check(size_t length) +{ + static size_t limit = 0; + if (!limit) { + limit = git_env_ulong("GIT_MMAP_LIMIT", 0); + if (!limit) + limit = SIZE_MAX; + } + if (length > limit) + die(_("attempting to mmap %"PRIuMAX" over limit %"PRIuMAX), + (uintmax_t)length, (uintmax_t)limit); +} + +void *xmmap_gently(void *start, size_t length, + int prot, int flags, int fd, off_t offset) +{ + void *ret; + + mmap_limit_check(length); + ret = mmap(start, length, prot, flags, fd, offset); + if (ret == MAP_FAILED && !length) + ret = NULL; + return ret; +} + +const char *mmap_os_err(void) +{ + static const char blank[] = ""; +#if defined(__linux__) + if (errno == ENOMEM) { + /* this continues an existing error message: */ + static const char enomem[] = +", check sys.vm.max_map_count and/or RLIMIT_DATA"; + return enomem; + } +#endif /* OS-specific bits */ + return blank; +} + +void *xmmap(void *start, size_t length, + int prot, int flags, int fd, off_t offset) +{ + void *ret = xmmap_gently(start, length, prot, flags, fd, offset); + if (ret == MAP_FAILED) + die_errno(_("mmap failed%s"), mmap_os_err()); + return ret; +} diff --git a/wt-status.c b/wt-status.c index 1da5732f57..454601afa1 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1824,10 +1824,10 @@ void wt_status_get_state(struct repository *r, if (!sequencer_get_last_command(r, &action)) { if (action == REPLAY_PICK && !state->cherry_pick_in_progress) { state->cherry_pick_in_progress = 1; - oidcpy(&state->cherry_pick_head_oid, null_oid()); + oidcpy(&state->cherry_pick_head_oid, null_oid(the_hash_algo)); } else if (action == REPLAY_REVERT && !state->revert_in_progress) { state->revert_in_progress = 1; - oidcpy(&state->revert_head_oid, null_oid()); + oidcpy(&state->revert_head_oid, null_oid(the_hash_algo)); } } if (get_detached_from) diff --git a/xdiff-interface.c b/xdiff-interface.c index 3bd61f26e9..1edcd319e6 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -5,7 +5,7 @@ #include "gettext.h" #include "config.h" #include "hex.h" -#include "object-store-ll.h" +#include "object-store.h" #include "strbuf.h" #include "xdiff-interface.h" #include "xdiff/xtypes.h" @@ -181,7 +181,7 @@ void read_mmblob(mmfile_t *ptr, const struct object_id *oid) unsigned long size; enum object_type type; - if (oideq(oid, null_oid())) { + if (oideq(oid, null_oid(the_hash_algo))) { ptr->ptr = xstrdup(""); ptr->size = 0; return; diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 8889b8b62a..5a96e36dfb 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -211,8 +211,10 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, for (d = fmax; d >= fmin; d -= 2) { i1 = XDL_MIN(kvdf[d], lim1); i2 = i1 - d; - if (lim2 < i2) - i1 = lim2 + d, i2 = lim2; + if (lim2 < i2) { + i1 = lim2 + d; + i2 = lim2; + } if (fbest < i1 + i2) { fbest = i1 + i2; fbest1 = i1; @@ -223,8 +225,10 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, for (d = bmax; d >= bmin; d -= 2) { i1 = XDL_MAX(off1, kvdb[d]); i2 = i1 - d; - if (i2 < off2) - i1 = off2 + d, i2 = off2; + if (i2 < off2) { + i1 = off2 + d; + i2 = off2; + } if (i1 + i2 < bbest) { bbest = i1 + i2; bbest1 = i1; |
