diff options
291 files changed, 4851 insertions, 7661 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/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index b787dce3da..f13e64694b 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,25 @@ 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. + Fixes since v2.49 ----------------- @@ -107,6 +162,39 @@ 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. + * 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 +212,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/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/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-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-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/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 8b9e692c59..8d9cd98548 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 = [ ] @@ -254,7 +254,7 @@ if docs_backend == 'asciidoc' 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 = [ @@ -296,7 +296,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 +417,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/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. @@ -1042,6 +1042,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 +1070,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 @@ -1366,6 +1366,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 @@ -1380,8 +1382,6 @@ 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_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 +1411,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 @@ -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, @@ -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; @@ -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); @@ -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/blame.c b/builtin/blame.c index c470654c7e..9436f70aec 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -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 { diff --git a/builtin/cat-file.c b/builtin/cat-file.c index b13561cf73..ead7554a57 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -15,11 +15,13 @@ #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" @@ -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..e69ea06713 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -17,7 +17,7 @@ #include "merge-ll.h" #include "lockfile.h" #include "mem-pool.h" -#include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "object-name.h" #include "object-store-ll.h" #include "parse-options.h" @@ -130,8 +130,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 +710,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 +907,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..7d5b64a6a0 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -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)) { @@ -1525,7 +1527,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/describe.c b/builtin/describe.c index e2e73f3d75..23df333fd0 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -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", 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/fast-export.c b/builtin/fast-export.c index 126980f724..170126d41a 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -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..63880b595c 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -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; @@ -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..097a98628f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -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) { diff --git a/builtin/fsck.c b/builtin/fsck.c index 8fbd1d6334..9c8a6d6a8d 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -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..d5c75be252 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -33,6 +33,7 @@ #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 +54,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 +288,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) { /* @@ -667,15 +719,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, @@ -723,7 +768,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 +1073,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 +1207,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 +1232,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 +1253,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 +1321,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 +1339,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 +1397,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 +1456,7 @@ enum maintenance_task_label { TASK_GC, TASK_COMMIT_GRAPH, TASK_PACK_REFS, + TASK_REFLOG_EXPIRE, /* Leave as final value */ TASK__COUNT @@ -1428,6 +1493,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_) diff --git a/builtin/grep.c b/builtin/grep.c index d1427290f7..283d64cab8 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -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; } @@ -1144,7 +1144,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/index-pack.c b/builtin/index-pack.c index 50573ba049..de127c0ff1 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -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/log.c b/builtin/log.c index 04a6ef97bc..0d4c579dad 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -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/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.c b/builtin/merge.c index ba9faf126a..9efd585842 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; @@ -750,12 +749,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 +1311,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 +1511,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/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/pack-objects.c b/builtin/pack-objects.c index 6b06d159d2..3973267e9e 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -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; diff --git a/builtin/prune.c b/builtin/prune.c index 1c357fffd8..8f52da8bd6 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -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/rebase.c b/builtin/rebase.c index d4715ed35d..965bd048a8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -925,7 +925,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); @@ -1575,11 +1575,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 +1838,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..b3e2a9d0c6 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -363,7 +363,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..d2aeb5ba1f 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -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/rev-list.c b/builtin/rev-list.c index bb26bee0d4..4a84f18f9e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -16,6 +16,7 @@ #include "object-file.h" #include "object-store-ll.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..2654f769a8 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -252,8 +252,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/submodule--helper.c b/builtin/submodule--helper.c index c1a8029714..570226ea16 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -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; @@ -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..7c173535cb 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -172,7 +172,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)) diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index c5a6dca856..3bbcaf2de9 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -505,7 +505,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 +553,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-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..87ccd47794 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -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/bulk-checkin.c b/bulk-checkin.c index a5a3395188..79696e80f2 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -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); @@ -240,7 +240,7 @@ static void prepare_to_stream(struct bulk_checkin_packfile *state, if (!(flags & HASH_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 */ diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 0df74610d0..be9ba5e30a 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. @@ -119,7 +119,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..553bf59fed 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -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..8286d5dda2 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -2090,11 +2090,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 +2718,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, @@ -1378,7 +1378,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/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: @@ -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/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( 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/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); @@ -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 @@ -6647,8 +6649,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, @@ -4035,7 +4035,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; diff --git a/environment.c b/environment.c index 9e4c7781be..3c32367c28 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; 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/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/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')) @@ -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); } } @@ -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/log-tree.c b/log-tree.c index 8b184d6776..5dd1b63076 100644 --- a/log-tree.c +++ b/log-tree.c @@ -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 */ 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..6dbb680ede 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" @@ -1817,7 +1818,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 +2200,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 +2232,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 +2740,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 +2753,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 +3124,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 +4007,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 +4054,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 +4176,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 +4188,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 +4317,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 +4958,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 +5323,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 e98cfa4909..c47cb79af0 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() @@ -311,6 +344,7 @@ libgit_sources = [ 'graph.c', 'grep.c', 'hash-lookup.c', + 'hash.c', 'hashmap.c', 'help.c', 'hex.c', @@ -338,7 +372,6 @@ libgit_sources = [ 'merge-ll.c', 'merge-ort.c', 'merge-ort-wrappers.c', - 'merge-recursive.c', 'merge.c', 'midx.c', 'midx-write.c', @@ -705,7 +738,7 @@ 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()) + '"', ] libgit_include_directories = [ '.' ] libgit_dependencies = [ ] @@ -714,6 +747,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 +802,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 @@ -779,7 +814,7 @@ endif # features. It is optional if you want to neither execute tests nor use any of # these optional features. perl_required = get_option('perl') -if get_option('tests') or get_option('gitweb').enabled() or 'netrc' in get_option('credential_helpers') or get_option('docs') != [] +if get_option('gitweb').enabled() or 'netrc' in get_option('credential_helpers') or get_option('docs') != [] perl_required = true endif @@ -797,9 +832,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 +887,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') @@ -1114,7 +1151,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 +1177,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 +1732,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 +2020,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/midx-write.c b/midx-write.c index 0897cbd829..48a4dc5e94 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; @@ -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); @@ -747,7 +747,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-merge.c b/notes-merge.c index 67a472020d..5008faef45 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -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"); @@ -1353,7 +1353,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..f3810871f0 100644 --- a/object-file.c +++ b/object-file.c @@ -45,283 +45,6 @@ /* 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 @@ -1362,7 +1085,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 +1108,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 +1159,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; } /* @@ -1580,6 +1301,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 +1323,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; @@ -1793,7 +1516,7 @@ static int oid_object_info_convert(struct repository *r, if (type == -1) return -1; if (type != OBJ_BLOB) { - ret = convert_object_file(&outbuf, + ret = convert_object_file(the_repository, &outbuf, the_hash_algo, input_algo, content, size, type, !do_die); free(content); @@ -2510,7 +2233,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 +2273,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); @@ -2681,7 +2405,7 @@ static int index_mem(struct index_state *istate, 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); } @@ -2803,7 +2527,8 @@ 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); @@ -3080,7 +2805,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 +2851,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); @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -18,14 +17,15 @@ #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 +283,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,12 +492,12 @@ void object_array_clear(struct object_array *array) array->nr = array->alloc = 0; } -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; } @@ -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 @@ -330,7 +332,7 @@ void object_array_filter(struct object_array *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/pack-bitmap-write.c b/pack-bitmap-write.c index 8a30853d2e..02051c6e71 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.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..5299f49d59 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -745,6 +745,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 +1424,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 +1640,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 +1677,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 +1688,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 +1730,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 +2039,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 +2119,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 +2129,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 +2577,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 +3279,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 +3296,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 +3311,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..95dcbbe985 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" @@ -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-revindex.c b/pack-revindex.c index d3faab6a37..f035a33a5a 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "gettext.h" #include "pack-revindex.h" @@ -9,6 +7,7 @@ #include "strbuf.h" #include "trace2.h" #include "parse.h" +#include "repository.h" #include "midx.h" #include "csum-file.h" @@ -137,7 +136,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 +192,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 +204,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 +232,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 +283,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 +327,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 +380,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 +398,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 +431,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 +477,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/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/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/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..299e129249 100644 --- a/reachable.c +++ b/reachable.c @@ -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..2f9e21c897 100644 --- a/read-cache.c +++ b/read-cache.c @@ -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); @@ -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; @@ -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 "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; @@ -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..5057dd02ed 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); @@ -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; @@ -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/system.h b/reftable/system.h index 10055fbff2..072d9daea0 100644 --- a/reftable/system.h +++ b/reftable/system.h @@ -11,6 +11,7 @@ https://developers.google.com/open-source/licenses/bsd /* 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/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) @@ -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/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); @@ -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..3fb4c4adb7 100644 --- a/revision.c +++ b/revision.c @@ -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) @@ -3612,7 +3604,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/sequencer.c b/sequencer.c index c625a39111..9ea678364d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -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 @@ -4683,7 +4667,7 @@ static void create_autostash_internal(struct repository *r, 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); @@ -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..018b794d25 100644 --- a/streaming.c +++ b/streaming.c @@ -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..d82b404b73 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -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..0821507eca 100644 --- a/submodule.c +++ b/submodule.c @@ -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); @@ -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/Makefile b/t/Makefile index 2994eb5fa9..791e0a0978 100644 --- a/t/Makefile +++ b/t/Makefile @@ -59,16 +59,21 @@ CHAINLINTSUPPRESS = GIT_TEST_EXT_CHAIN_LINT=0 && export GIT_TEST_EXT_CHAIN_LINT all:: $(DEFAULT_TEST_TARGET) -test: pre-clean check-chainlint check-meson $(TEST_LINT) +test: pre-clean check-meson $(TEST_LINT) $(CHAINLINTSUPPRESS) $(MAKE) aggregate-results-and-cleanup +ifneq ($(PERL_PATH),) +test: check-chainlint +prove: check-chainlint +endif + failed: @failed=$$(cd '$(TEST_RESULTS_DIRECTORY_SQ)' && \ grep -l '^failed [1-9]' *.counts | \ sed -n 's/\.counts$$/.sh/p') && \ test -z "$$failed" || $(MAKE) $$failed -prove: pre-clean check-chainlint $(TEST_LINT) +prove: pre-clean $(TEST_LINT) @echo "*** prove (shell & unit tests) ***" @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) $(MAKE) clean-except-prove-cache @@ -132,8 +137,13 @@ check-meson: fi; \ done -test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ +test-lint: test-lint-duplicates test-lint-executable \ test-lint-filenames +ifneq ($(PERL_PATH),) +test-lint: test-lint-shell-syntax +else +GIT_TEST_CHAIN_LINT = 0 +endif ifneq ($(GIT_TEST_CHAIN_LINT),0) test-lint: test-chainlint endif diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index f3c59e5028..086238c826 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -323,6 +323,19 @@ int cmd__path_utils(int argc, const char **argv) return 0; } + if (argc >= 2 && !strcmp(argv[1], "readlink")) { + struct strbuf target = STRBUF_INIT; + while (argc > 2) { + if (strbuf_readlink(&target, argv[2], 0) < 0) + die_errno("cannot read link at '%s'", argv[2]); + puts(target.buf); + argc--; + argv++; + } + strbuf_release(&target); + return 0; + } + if (argc >= 2 && !strcmp(argv[1], "absolute_path")) { while (argc > 2) { puts(absolute_path(argv[2])); diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index e00fce592b..2ff67c067a 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -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-sha1.sh b/t/helper/test-sha1.sh index bf387d3db1..f03b784ddc 100755 --- a/t/helper/test-sha1.sh +++ b/t/helper/test-sha1.sh @@ -15,7 +15,7 @@ do { test -z "$pfx" || echo "$pfx" dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | - perl -pe 'y/\000/g/' + tr "\000" "g" } | ./t/helper/test-tool $sha1 $cnt ) if test "$expect" = "$actual" @@ -61,7 +61,7 @@ do { test -z "$pfx" || echo "$pfx" dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | - perl -pe 'y/\000/g/' + tr "\000" "g" } | sha1sum | sed -e 's/ .*//' ) 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/lib-diff.sh b/t/lib-diff.sh index c4606bd4b7..12b3c8fcc6 100644 --- a/t/lib-diff.sh +++ b/t/lib-diff.sh @@ -21,8 +21,8 @@ compare_diff_raw_z () { # Also we do not check SHA1 hash generation in this test, which # is a job for t0000-basic.sh - perl -pe 'y/\000/\012/' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1 - perl -pe 'y/\000/\012/' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2 + tr "\000" "\012" <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1 + tr "\000" "\012" <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2 test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 } diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 3845b6ac44..937b876bd0 100644 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -192,9 +192,5 @@ test_lazy_prereq GPGSSH_VERIFYTIME ' ' sanitize_pgp() { - perl -ne ' - /^-----END PGP/ and $in_pgp = 0; - print unless $in_pgp; - /^-----BEGIN PGP/ and $in_pgp = 1; - ' + sed "/^-----BEGIN PGP/,/^-----END PGP/{/^-/p;d;}" } diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index d83bafeab3..5091db949b 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -165,7 +165,7 @@ prepare_httpd() { install_script broken-smart-http.sh install_script error-smart-http.sh install_script error.sh - install_script apply-one-time-perl.sh + install_script apply-one-time-script.sh install_script nph-custom-auth.sh ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 022276a6b9..e631ab0eb5 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -135,7 +135,7 @@ SetEnv PERL_PATH ${PERL_PATH} SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> -<LocationMatch /one_time_perl/> +<LocationMatch /one_time_script/> SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> @@ -159,7 +159,7 @@ ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error_smart/ error-smart-http.sh/ ScriptAlias /error/ error.sh/ -ScriptAliasMatch /one_time_perl/(.*) apply-one-time-perl.sh/$1 +ScriptAliasMatch /one_time_script/(.*) apply-one-time-script.sh/$1 ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1 <Directory ${GIT_EXEC_PATH}> Options FollowSymlinks @@ -182,7 +182,7 @@ ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1 <Files error.sh> Options ExecCGI </Files> -<Files apply-one-time-perl.sh> +<Files apply-one-time-script.sh> Options ExecCGI </Files> <Files ${GIT_EXEC_PATH}/git-http-backend> diff --git a/t/lib-httpd/apply-one-time-perl.sh b/t/lib-httpd/apply-one-time-perl.sh deleted file mode 100644 index d7f9fed6ae..0000000000 --- a/t/lib-httpd/apply-one-time-perl.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -# If "one-time-perl" exists in $HTTPD_ROOT_PATH, run perl on the HTTP response, -# using the contents of "one-time-perl" as the perl command to be run. If the -# response was modified as a result, delete "one-time-perl" so that subsequent -# HTTP responses are no longer modified. -# -# This can be used to simulate the effects of the repository changing in -# between HTTP request-response pairs. -if test -f one-time-perl -then - LC_ALL=C - export LC_ALL - - "$GIT_EXEC_PATH/git-http-backend" >out - "$PERL_PATH" -pe "$(cat one-time-perl)" out >out_modified - - if cmp -s out out_modified - then - cat out - else - cat out_modified - rm one-time-perl - fi -else - "$GIT_EXEC_PATH/git-http-backend" -fi diff --git a/t/lib-httpd/apply-one-time-script.sh b/t/lib-httpd/apply-one-time-script.sh new file mode 100644 index 0000000000..b1682944e2 --- /dev/null +++ b/t/lib-httpd/apply-one-time-script.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# If "one-time-script" exists in $HTTPD_ROOT_PATH, run the script on the HTTP +# response. If the response was modified as a result, delete "one-time-script" +# so that subsequent HTTP responses are no longer modified. +# +# This can be used to simulate the effects of the repository changing in +# between HTTP request-response pairs. +if test -f one-time-script +then + LC_ALL=C + export LC_ALL + + "$GIT_EXEC_PATH/git-http-backend" >out + ./one-time-script out >out_modified + + if cmp -s out out_modified + then + cat out + else + cat out_modified + rm one-time-script + fi +else + "$GIT_EXEC_PATH/git-http-backend" +fi 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/lib-t6000.sh b/t/lib-t6000.sh index fba6778ca3..35c5472465 100644 --- a/t/lib-t6000.sh +++ b/t/lib-t6000.sh @@ -109,13 +109,12 @@ check_output () { # All alphanums translated into -'s which are then compressed and stripped # from front and back. name_from_description () { - perl -pe ' - s/[^A-Za-z0-9.]/-/g; - s/-+/-/g; - s/-$//; - s/^-//; - y/A-Z/a-z/; - ' + sed \ + -e 's/[^A-Za-z0-9.]/-/g' \ + -e 's/--*/-/g' \ + -e 's/-$//' \ + -e 's/^-//' \ + -e 'y/A-Z/a-z/' } diff --git a/t/meson.build b/t/meson.build index 8b3aed14ea..bfb744e886 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 = [ @@ -60,8 +62,6 @@ unit_test_programs = [ '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', ] 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/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/t0008-ignores.sh b/t/t0008-ignores.sh index c9376dffb5..273d71411f 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -39,11 +39,11 @@ test_stderr () { } broken_c_unquote () { - "$PERL_PATH" -pe 's/^"//; s/\\//; s/"$//; tr/\n/\0/' "$@" + sed -e 's/^"//' -e 's/\\//' -e 's/"$//' "$1" | tr '\n' '\0' } broken_c_unquote_verbose () { - "$PERL_PATH" -pe 's/ "/ /; s/\\//; s/"$//; tr/:\t\n/\0/' "$@" + sed -e 's/ "/ /' -e 's/\\//' -e 's/"$//' "$1" | tr ':\t\n' '\000' } stderr_contains () { diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 3f6433d304..bf10d253ec 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -20,8 +20,7 @@ EOF generate_random_characters () { LEN=$1 NAME=$2 - test-tool genrandom some-seed $LEN | - perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME" + test-tool genrandom some-seed | tr -dc 'a-z' | test_copy_bytes "$LEN" >"$TEST_ROOT/$NAME" } filter_git () { @@ -841,7 +840,7 @@ test_expect_success 'process filter abort stops processing of all further files' ) ' -test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' +test_expect_success 'invalid process filter must fail (and not hang!)' ' test_config_global filter.protocol.process cat && test_config_global filter.protocol.required true && rm -rf repo && @@ -1111,19 +1110,19 @@ do branch) opt='-f HEAD' ;; esac - test_expect_success PERL,TTY "delayed checkout shows progress by default on tty ($mode checkout)" ' + test_expect_success TTY "delayed checkout shows progress by default on tty ($mode checkout)" ' test_delayed_checkout_progress test_terminal git checkout $opt ' - test_expect_success PERL "delayed checkout omits progress on non-tty ($mode checkout)" ' + test_expect_success "delayed checkout omits progress on non-tty ($mode checkout)" ' test_delayed_checkout_progress ! git checkout $opt ' - test_expect_success PERL,TTY "delayed checkout omits progress with --quiet ($mode checkout)" ' + test_expect_success TTY "delayed checkout omits progress with --quiet ($mode checkout)" ' test_delayed_checkout_progress ! test_terminal git checkout --quiet $opt ' - test_expect_success PERL,TTY "delayed checkout honors --[no]-progress ($mode checkout)" ' + test_expect_success TTY "delayed checkout honors --[no]-progress ($mode checkout)" ' test_delayed_checkout_progress ! test_terminal git checkout --no-progress $opt && test_delayed_checkout_progress test_terminal git checkout --quiet --progress $opt ' diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index ab80c9ef13..d901588294 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -128,7 +128,7 @@ test_expect_success 'second commit has cache-tree' ' test_cache_tree ' -test_expect_success PERL 'commit --interactive gives cache-tree on partial commit' ' +test_expect_success 'commit --interactive gives cache-tree on partial commit' ' test_when_finished "git reset --hard" && cat <<-\EOT >foo.c && int foo() @@ -162,7 +162,7 @@ test_expect_success PERL 'commit --interactive gives cache-tree on partial commi test_cache_tree expected.status ' -test_expect_success PERL 'commit -p with shrinking cache-tree' ' +test_expect_success 'commit -p with shrinking cache-tree' ' mkdir -p deep/very-long-subdir && echo content >deep/very-long-subdir/file && git add deep && diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh index 4287ed3fbb..96c68f65df 100755 --- a/t/t0210-trace2-normal.sh +++ b/t/t0210-trace2-normal.sh @@ -53,10 +53,41 @@ GIT_TRACE2_BRIEF=1 && export GIT_TRACE2_BRIEF # # Implicit return from cmd_<verb> function propagates <code>. +scrub_normal () { + # Scrub the variable fields from the normal trace2 output to make + # testing easier: + # + # 1. Various messages include an elapsed time in the middle of the + # message. Replace the time with a placeholder to simplify our + # HEREDOC in the test script. + # + # 2. We expect: + # + # start <argv0> [<argv1> [<argv2> [...]]] + # + # where argv0 might be a relative or absolute path, with or + # without quotes, and platform dependent. Replace argv0 with a + # token for HEREDOC matching in the test script. + # + # 3. Likewise, the 'cmd_path' message breaks out argv[0]. + # + # This line is only emitted when RUNTIME_PREFIX is defined, + # so just omit it for testing purposes. + # + # 4. 'cmd_ancestry' is not implemented everywhere, so for portability's + # sake, skip it when parsing normal. + sed \ + -e 's/elapsed:[0-9]*\.[0-9][0-9]*\([eE][-+]\{0,1\}[0-9][0-9]*\)\{0,1\}/elapsed:_TIME_/g' \ + -e "s/^start '[^']*' \(.*\)/start _EXE_ \1/" \ + -e 's/^start [^ ][^ ]* \(.*\)/start _EXE_ \1/' \ + -e '/^cmd_path/d' \ + -e '/^cmd_ancestry/d' +} + test_expect_success 'normal stream, return code 0' ' test_when_finished "rm trace.normal actual expect" && GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 001return 0 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 001return 0 @@ -70,7 +101,7 @@ test_expect_success 'normal stream, return code 0' ' test_expect_success 'normal stream, return code 1' ' test_when_finished "rm trace.normal actual expect" && test_must_fail env GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 001return 1 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 001return 1 @@ -85,7 +116,7 @@ test_expect_success 'automatic filename' ' test_when_finished "rm -r traces actual expect" && mkdir traces && GIT_TRACE2="$(pwd)/traces" test-tool trace2 001return 0 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <"$(ls traces/*)" >actual && + scrub_normal <"$(ls traces/*)" >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 001return 0 @@ -103,7 +134,7 @@ test_expect_success 'automatic filename' ' test_expect_success 'normal stream, exit code 0' ' test_when_finished "rm trace.normal actual expect" && GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 002exit 0 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 002exit 0 @@ -117,7 +148,7 @@ test_expect_success 'normal stream, exit code 0' ' test_expect_success 'normal stream, exit code 1' ' test_when_finished "rm trace.normal actual expect" && test_must_fail env GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 002exit 1 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 002exit 1 @@ -135,7 +166,7 @@ test_expect_success 'normal stream, exit code 1' ' test_expect_success 'normal stream, error event' ' test_when_finished "rm trace.normal actual expect" && GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 003error "hello world" "this is a test" && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 003error '\''hello world'\'' '\''this is a test'\'' @@ -155,7 +186,7 @@ test_expect_success 'normal stream, error event' ' test_expect_success 'BUG messages are written to trace2' ' test_when_finished "rm trace.normal actual expect" && test_must_fail env GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 007bug && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 007bug @@ -179,7 +210,7 @@ test_expect_success 'bug messages with BUG_if_bug() are written to trace2' ' sed "s/^.*: //" <err >actual && test_cmp expect actual && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 008bug @@ -205,7 +236,7 @@ test_expect_success 'bug messages without explicit BUG_if_bug() are written to t sed "s/^.*: //" <err >actual && test_cmp expect actual && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 009bug_BUG @@ -230,7 +261,7 @@ test_expect_success 'bug messages followed by BUG() are written to trace2' ' sed "s/^.*: //" <err >actual && test_cmp expect actual && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 010bug_BUG @@ -262,7 +293,7 @@ test_expect_success 'using global config, normal stream, return code 0' ' test_config_global trace2.normalBrief 1 && test_config_global trace2.normalTarget "$(pwd)/trace.normal" && test-tool trace2 001return 0 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 001return 0 @@ -280,7 +311,7 @@ test_expect_success 'using global config with include' ' mv "$(pwd)/.gitconfig" "$(pwd)/real.gitconfig" && test_config_global include.path "$(pwd)/real.gitconfig" && test-tool trace2 001return 0 && - perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + scrub_normal <trace.normal >actual && cat >expect <<-EOF && version $V start _EXE_ trace2 001return 0 diff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl deleted file mode 100644 index 7cc4de392a..0000000000 --- a/t/t0210/scrub_normal.perl +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/perl -# -# Scrub the variable fields from the normal trace2 output to -# make testing easier. - -use strict; -use warnings; - -my $float = '[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?'; - -# This code assumes that the trace2 data was written with bare -# turned on (which omits the "<clock> <file>:<line>" prefix. - -while (<>) { - # Various messages include an elapsed time in the middle - # of the message. Replace the time with a placeholder to - # simplify our HEREDOC in the test script. - s/elapsed:$float/elapsed:_TIME_/g; - - my $line = $_; - - # we expect: - # start <argv0> [<argv1> [<argv2> [...]]] - # - # where argv0 might be a relative or absolute path, with - # or without quotes, and platform dependent. Replace argv0 - # with a token for HEREDOC matching in the test script. - - if ($line =~ m/^start/) { - $line =~ /^start\s+(.*)/; - my $argv = $1; - $argv =~ m/(\'[^\']*\'|[^ ]+)\s+(.*)/; - my $argv_0 = $1; - my $argv_rest = $2; - - print "start _EXE_ $argv_rest\n"; - } - elsif ($line =~ m/^cmd_path/) { - # Likewise, the 'cmd_path' message breaks out argv[0]. - # - # This line is only emitted when RUNTIME_PREFIX is defined, - # so just omit it for testing purposes. - # print "cmd_path _EXE_\n"; - } - elsif ($line =~ m/^cmd_ancestry/) { - # 'cmd_ancestry' is not implemented everywhere, so for portability's - # sake, skip it when parsing normal. - # - # print "$line"; - } - else { - print "$line"; - } -} diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh index bac9046540..760cf69087 100755 --- a/t/t0211-trace2-perf.sh +++ b/t/t0211-trace2-perf.sh @@ -4,6 +4,12 @@ test_description='test trace2 facility (perf target)' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping trace2 tests; Perl not available' + test_done +fi + # Turn off any inherited trace2 settings for this test. sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT sane_unset GIT_TRACE2_PERF_BRIEF diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh index 002a75dee8..1be534a895 100755 --- a/t/t0610-reftable-basics.sh +++ b/t/t0610-reftable-basics.sh @@ -653,9 +653,8 @@ test_expect_success 'basic: commit and list refs' ' test_expect_success 'basic: can write large commit message' ' test_when_finished "rm -rf repo" && git init repo && - perl -e " - print \"this is a long commit message\" x 50000 - " >commit-msg && + + awk "BEGIN { for (i = 0; i < 50000; i++) printf \"%s\", \"this is a long commit message\" }" >commit-msg && git -C repo commit --allow-empty --file=../commit-msg ' diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index e2708e11d5..42aa1592f8 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh @@ -145,7 +145,7 @@ test_expect_success 'small block size fails with large reflog message' ' ( cd repo && test_commit A && - perl -e "print \"a\" x 500" >logmsg && + test-tool genzeros 500 | tr "\000" "a" >logmsg && cat >expect <<-EOF && fatal: update_ref failed for ref ${SQ}refs/heads/logme${SQ}: reftable: transaction failure: entry too large EOF diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 398865d6eb..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 && @@ -1270,7 +1323,7 @@ extract_batch_output () { ' "$@" } -test_expect_success 'cat-file --batch-all-objects --batch ignores replace' ' +test_expect_success PERL_TEST_HELPERS 'cat-file --batch-all-objects --batch ignores replace' ' git cat-file --batch-all-objects --batch >actual.raw && extract_batch_output $orig <actual.raw >actual && { @@ -1323,7 +1376,7 @@ test_expect_success 'batch-command flush without --buffer' ' grep "^fatal:.*flush is only for --buffer mode.*" err ' -script=' +perl_script=' use warnings; use strict; use IPC::Open2; @@ -1345,12 +1398,115 @@ $? == 0 or die "\$?=$?"; expect="$hello_oid blob $hello_size" -test_expect_success PERL '--batch-check is unbuffered by default' ' - perl -e "$script" -- --batch-check $hello_oid "$expect" +test_lazy_prereq PERL_IPC_OPEN2 ' + perl -MIPC::Open2 -e "exit 0" +' + +test_expect_success PERL_IPC_OPEN2 '--batch-check is unbuffered by default' ' + perl -e "$perl_script" -- --batch-check $hello_oid "$expect" +' + +test_expect_success PERL_IPC_OPEN2 '--batch-command info is unbuffered by default' ' + 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 ' -test_expect_success PERL '--batch-command info is unbuffered by default' ' - perl -e "$script" -- --batch-command $hello_oid "$expect" "info " +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/t1007-hash-object.sh b/t/t1007-hash-object.sh index a0481139de..b3cf53ff8c 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -205,7 +205,7 @@ test_expect_success 'too-short tree' ' grep "too-short tree object" err ' -test_expect_success 'malformed mode in tree' ' +test_expect_success PERL_TEST_HELPERS 'malformed mode in tree' ' hex_oid=$(echo foo | git hash-object --stdin -w) && bin_oid=$(echo $hex_oid | hex2oct) && printf "9100644 \0$bin_oid" >tree-with-malformed-mode && @@ -213,7 +213,7 @@ test_expect_success 'malformed mode in tree' ' grep "malformed mode in tree entry" err ' -test_expect_success 'empty filename in tree' ' +test_expect_success PERL_TEST_HELPERS 'empty filename in tree' ' hex_oid=$(echo foo | git hash-object --stdin -w) && bin_oid=$(echo $hex_oid | hex2oct) && printf "100644 \0$bin_oid" >tree-with-empty-filename && @@ -221,7 +221,7 @@ test_expect_success 'empty filename in tree' ' grep "empty filename in tree entry" err ' -test_expect_success 'duplicate filename in tree' ' +test_expect_success PERL_TEST_HELPERS 'duplicate filename in tree' ' hex_oid=$(echo foo | git hash-object --stdin -w) && bin_oid=$(echo $hex_oid | hex2oct) && { diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh index c291a2b33d..e9973f7494 100755 --- a/t/t1010-mktree.sh +++ b/t/t1010-mktree.sh @@ -42,13 +42,13 @@ test_expect_success 'ls-tree piped to mktree (2)' ' ' test_expect_success 'ls-tree output in wrong order given to mktree (1)' ' - perl -e "print reverse <>" <top | + sort -r <top | git mktree >actual && test_cmp tree actual ' test_expect_success 'ls-tree output in wrong order given to mktree (2)' ' - perl -e "print reverse <>" <top.withsub | + sort -r <top.withsub | git mktree >actual && test_cmp tree.withsub actual ' 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/t1450-fsck.sh b/t/t1450-fsck.sh index 8a456b1142..0105045376 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -346,7 +346,7 @@ test_expect_success 'unparseable tree object' ' test_grep ! "fatal: empty filename in tree entry" out ' -test_expect_success 'tree entry with type mismatch' ' +test_expect_success PERL_TEST_HELPERS 'tree entry with type mismatch' ' test_when_finished "remove_object \$blob" && test_when_finished "remove_object \$tree" && test_when_finished "remove_object \$commit" && @@ -364,7 +364,7 @@ test_expect_success 'tree entry with type mismatch' ' test_grep ! "dangling blob" out ' -test_expect_success 'tree entry with bogus mode' ' +test_expect_success PERL_TEST_HELPERS 'tree entry with bogus mode' ' test_when_finished "remove_object \$blob" && test_when_finished "remove_object \$tree" && blob=$(echo blob | git hash-object -w --stdin) && @@ -984,7 +984,7 @@ corrupt_index_checksum () { # Corrupt the checksum on the index and then # verify that only fsck notices. -test_expect_success 'detect corrupt index file in fsck' ' +test_expect_success PERL_TEST_HELPERS 'detect corrupt index file in fsck' ' cp .git/index .git/index.backup && test_when_finished "mv .git/index.backup .git/index" && corrupt_index_checksum && 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/t3300-funny-names.sh b/t/t3300-funny-names.sh index f5bf16abcd..dd0586b007 100755 --- a/t/t3300-funny-names.sh +++ b/t/t3300-funny-names.sh @@ -70,7 +70,7 @@ test_expect_success 'ls-files -z does not quote funny filename' ' tabs ," (dq) and spaces EOF git ls-files -z >ls-files.z && - perl -pe "y/\000/\012/" <ls-files.z >current && + tr "\000" "\012" <ls-files.z >current && test_cmp expected current ' @@ -107,7 +107,7 @@ test_expect_success 'diff-index -z does not quote funny filename' ' tabs ," (dq) and spaces EOF git diff-index -z --name-status $t0 >diff-index.z && - perl -pe "y/\000/\012/" <diff-index.z >current && + tr "\000" "\012" <diff-index.z >current && test_cmp expected current ' @@ -117,7 +117,7 @@ test_expect_success 'diff-tree -z does not quote funny filename' ' tabs ," (dq) and spaces EOF git diff-tree -z --name-status $t0 $t1 >diff-tree.z && - perl -pe y/\\000/\\012/ <diff-tree.z >current && + tr "\000" "\012" <diff-tree.z >current && test_cmp expected current ' 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/t4013-diff-various.sh b/t/t4013-diff-various.sh index 3855d68dbc..782d97fb7d 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -11,6 +11,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping diff various tests; Perl not available' + test_done +fi + test_expect_success setup ' GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 884f83fb8a..2782b1fc18 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -448,7 +448,7 @@ cat >>expect.no-threading <<EOF --- EOF -test_expect_success 'no threading' ' +test_expect_success PERL_TEST_HELPERS 'no threading' ' git checkout side && check_threading expect.no-threading main ' @@ -466,11 +466,11 @@ In-Reply-To: <0> References: <0> EOF -test_expect_success 'thread' ' +test_expect_success PERL_TEST_HELPERS 'thread' ' check_threading expect.thread --thread main ' -test_expect_success '--thread overrides format.thread=deep' ' +test_expect_success PERL_TEST_HELPERS '--thread overrides format.thread=deep' ' test_config format.thread deep && check_threading expect.thread --thread main ' @@ -490,7 +490,7 @@ In-Reply-To: <1> References: <1> EOF -test_expect_success 'thread in-reply-to' ' +test_expect_success PERL_TEST_HELPERS 'thread in-reply-to' ' check_threading expect.in-reply-to --in-reply-to="<test.message>" \ --thread main ' @@ -512,7 +512,7 @@ In-Reply-To: <0> References: <0> EOF -test_expect_success 'thread cover-letter' ' +test_expect_success PERL_TEST_HELPERS 'thread cover-letter' ' check_threading expect.cover-letter --cover-letter --thread main ' @@ -538,12 +538,12 @@ References: <1> <0> EOF -test_expect_success 'thread cover-letter in-reply-to' ' +test_expect_success PERL_TEST_HELPERS 'thread cover-letter in-reply-to' ' check_threading expect.cl-irt --cover-letter \ --in-reply-to="<test.message>" --thread main ' -test_expect_success 'thread explicit shallow' ' +test_expect_success PERL_TEST_HELPERS 'thread explicit shallow' ' check_threading expect.cl-irt --cover-letter \ --in-reply-to="<test.message>" --thread=shallow main ' @@ -562,7 +562,7 @@ References: <0> <1> EOF -test_expect_success 'thread deep' ' +test_expect_success PERL_TEST_HELPERS 'thread deep' ' check_threading expect.deep --thread=deep main ' @@ -584,7 +584,7 @@ References: <1> <2> EOF -test_expect_success 'thread deep in-reply-to' ' +test_expect_success PERL_TEST_HELPERS 'thread deep in-reply-to' ' check_threading expect.deep-irt --thread=deep \ --in-reply-to="<test.message>" main ' @@ -609,7 +609,7 @@ References: <0> <2> EOF -test_expect_success 'thread deep cover-letter' ' +test_expect_success PERL_TEST_HELPERS 'thread deep cover-letter' ' check_threading expect.deep-cl --cover-letter --thread=deep main ' @@ -638,27 +638,27 @@ References: <1> <3> EOF -test_expect_success 'thread deep cover-letter in-reply-to' ' +test_expect_success PERL_TEST_HELPERS 'thread deep cover-letter in-reply-to' ' check_threading expect.deep-cl-irt --cover-letter \ --in-reply-to="<test.message>" --thread=deep main ' -test_expect_success 'thread via config' ' +test_expect_success PERL_TEST_HELPERS 'thread via config' ' test_config format.thread true && check_threading expect.thread main ' -test_expect_success 'thread deep via config' ' +test_expect_success PERL_TEST_HELPERS 'thread deep via config' ' test_config format.thread deep && check_threading expect.deep main ' -test_expect_success 'thread config + override' ' +test_expect_success PERL_TEST_HELPERS 'thread config + override' ' test_config format.thread deep && check_threading expect.thread --thread main ' -test_expect_success 'thread config + --no-thread' ' +test_expect_success PERL_TEST_HELPERS 'thread config + --no-thread' ' test_config format.thread deep && check_threading expect.no-threading --no-thread main ' 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/t4020-diff-external.sh b/t/t4020-diff-external.sh index f1efe482a5..c8a23d5148 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -237,7 +237,7 @@ check_external_diff 0 empty empty 0 on --quiet check_external_diff 1 empty empty 1 on --quiet check_external_diff 128 empty error 2 on --quiet -echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file +echo NULZbetweenZwords | tr "Z" "\000" > file test_expect_success 'force diff with "diff"' ' after=$(git hash-object file) && diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh index 32b6e9a4e7..90cdde88d8 100755 --- a/t/t4029-diff-trailing-space.sh +++ b/t/t4029-diff-trailing-space.sh @@ -31,7 +31,8 @@ test_expect_success "$test_description" ' git config --bool diff.suppressBlankEmpty true && git diff f > actual && test_cmp exp actual && - perl -i.bak -p -e "s/^\$/ /" exp && + sed "s/^\$/ /" exp >exp.munged && + mv exp.munged exp && git config --bool diff.suppressBlankEmpty false && git diff f > actual && test_cmp exp actual && diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index daebf9796f..f904fc19f6 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -20,13 +20,10 @@ cat >expect.text <<'EOF' +1 EOF -cat >hexdump <<'EOF' -#!/bin/sh -"$PERL_PATH" -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1" -EOF -chmod +x hexdump - test_expect_success 'setup binary file with history' ' + write_script hexdump <<-\EOF && + tr "\000\001" "01" <"$1" + EOF test_commit --printf one file "\\0\\n" && test_commit --printf --append two file "\\01\\n" ' diff --git a/t/t4031-diff-rewrite-binary.sh b/t/t4031-diff-rewrite-binary.sh index c4394a27b5..15e012ccc7 100755 --- a/t/t4031-diff-rewrite-binary.sh +++ b/t/t4031-diff-rewrite-binary.sh @@ -57,24 +57,19 @@ test_expect_success 'diff --stat counts binary rewrite as 0 lines' ' grep " rewrite file" diff ' -{ - echo "#!$SHELL_PATH" - cat <<'EOF' -"$PERL_PATH" -e '$/ = undef; $_ = <>; s/./ord($&)/ge; print $_' < "$1" -EOF -} >dump -chmod +x dump - test_expect_success 'setup textconv' ' + write_script dump <<-\EOF && + test-tool hexdump <"$1" + EOF echo file diff=foo >.gitattributes && git config diff.foo.textconv "\"$(pwd)\""/dump ' test_expect_success 'rewrite diff respects textconv' ' git diff -B >diff && - grep "dissimilarity index" diff && - grep "^-61" diff && - grep "^-0" diff + test_grep "dissimilarity index" diff && + test_grep "^-3d 0a 00" diff && + test_grep "^+3d 0a 01" diff ' test_done diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh index 2fce4a9897..16266dff2a 100755 --- a/t/t4058-diff-duplicates.sh +++ b/t/t4058-diff-duplicates.sh @@ -13,6 +13,12 @@ test_description='test tree diff when trees have duplicate entries' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping diff duplicates tests; Perl not available' + test_done +fi + # make_tree_entry <mode> <mode> <sha1> # # We have to rely on perl here because not all printfs understand 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/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index d370ecfe0d..8e302a5a57 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -26,10 +26,10 @@ test_expect_success 'setup' ' git commit -m "Initial Version" 2>/dev/null && git checkout -b binary && - perl -pe "y/x/\000/" <file1 >file3 && + tr "x" "\000" <file1 >file3 && cat file3 >file4 && git add file2 && - perl -pe "y/\000/v/" <file3 >file1 && + tr "y" "\000" <file3 >file1 && rm -f file2 && git update-index --add --remove file1 file2 file3 file4 && git commit -m "Second Version" && @@ -158,7 +158,7 @@ test_expect_success 'apply binary -p0 diff' ' test -z "$(git diff --name-status binary -- file3)" ' -test_expect_success 'reject truncated binary diff' ' +test_expect_success PERL_TEST_HELPERS 'reject truncated binary diff' ' do_reset && # this length is calculated to get us very close to diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh index 0784ba033a..1e7beab001 100755 --- a/t/t4116-apply-reverse.sh +++ b/t/t4116-apply-reverse.sh @@ -13,14 +13,14 @@ test_description='git apply in reverse test_expect_success setup ' test_write_lines a b c d e f g h i j k l m n >file1 && - perl -pe "y/ijk/\\000\\001\\002/" <file1 >file2 && + tr "ijk" "\000\001\002" <file1 >file2 && git add file1 file2 && git commit -m initial && git tag initial && test_write_lines a b c g h i J K L m o n p q >file1 && - perl -pe "y/mon/\\000\\001\\002/" <file1 >file2 && + tr "mon" "\000\001\002" <file1 >file2 && git commit -a -m second && git tag second && diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 5e2b6c80ea..2ae93d3c96 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -1084,13 +1084,13 @@ test_expect_success 'am works with multi-line in-body headers' ' Body test" --author="$LONG <long@example.com>" && git format-patch --stdout -1 >patch && # bump from, date, and subject down to in-body header - perl -lpe " - if (/^From:/) { + awk " + /^From:/{ print \"From: x <x\@example.com>\"; print \"Date: Sat, 1 Jan 2000 00:00:00 +0000\"; print \"Subject: x\n\"; - } - " patch >msg && + }; 1 + " <patch >msg && git checkout HEAD^ && git am msg && # Ensure that the author and full message are present diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index b0a3e84984..204325f4d5 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -81,7 +81,7 @@ test_expect_success 'activate rerere, old style (conflicting merge)' ' test_might_fail git config --unset rerere.enabled && test_must_fail git merge first && - sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + sha1=$(sed "s/ .*//" .git/MERGE_RR) && rr=.git/rr-cache/$sha1 && grep "^=======\$" $rr/preimage && ! test -f $rr/postimage && @@ -94,7 +94,7 @@ test_expect_success 'rerere.enabled works, too' ' git reset --hard && test_must_fail git merge first && - sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + sha1=$(sed "s/ .*//" .git/MERGE_RR) && rr=.git/rr-cache/$sha1 && grep ^=======$ $rr/preimage ' @@ -104,7 +104,7 @@ test_expect_success 'set up rr-cache' ' git config rerere.enabled true && git reset --hard && test_must_fail git merge first && - sha1=$(perl -pe "s/ .*//" .git/MERGE_RR) && + sha1=$(sed "s/ .*//" .git/MERGE_RR) && rr=.git/rr-cache/$sha1 ' @@ -188,7 +188,7 @@ test_expect_success 'rerere updates postimage timestamp' ' test_expect_success 'rerere clear' ' mv $rr/postimage .git/post-saved && - echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR && + echo "$sha1 a1" | tr "\012" "\000" >.git/MERGE_RR && git rerere clear && ! test -d $rr ' diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index f81e42a84d..8f2ba98963 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -698,7 +698,7 @@ test_expect_success '%(trailers:only=no,only=true) shows only "key: value" trail test_cmp expect actual ' -test_expect_success '%(trailers:unfold) unfolds trailers' ' +test_expect_success PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' ' git log --no-walk --pretty="%(trailers:unfold)" >actual && { unfold <trailers && @@ -707,7 +707,7 @@ test_expect_success '%(trailers:unfold) unfolds trailers' ' test_cmp expect actual ' -test_expect_success ':only and :unfold work together' ' +test_expect_success PERL_TEST_HELPERS ':only and :unfold work together' ' git log --no-walk --pretty="%(trailers:only,unfold)" >actual && git log --no-walk --pretty="%(trailers:unfold,only)" >reverse && test_cmp actual reverse && @@ -754,7 +754,7 @@ test_expect_success '%(trailers:key=foo) handles multiple lines even if folded' test_cmp expect actual ' -test_expect_success '%(trailers:key=foo,unfold) properly unfolds' ' +test_expect_success PERL_TEST_HELPERS '%(trailers:key=foo,unfold) properly unfolds' ' git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by,unfold)" >actual && unfold <trailers | grep Signed-off-by >expect && test_cmp expect actual diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh index 3f163dc396..8910d53cac 100755 --- a/t/t4216-log-bloom.sh +++ b/t/t4216-log-bloom.sh @@ -738,20 +738,20 @@ check_corrupt_graph () { test_cmp expect.out out } -test_expect_success 'Bloom reader notices too-small data chunk' ' +test_expect_success PERL_TEST_HELPERS 'Bloom reader notices too-small data chunk' ' check_corrupt_graph BDAT clear 00000000 && echo "warning: ignoring too-small changed-path chunk" \ "(4 < 12) in commit-graph file" >expect.err && test_cmp expect.err err ' -test_expect_success 'Bloom reader notices out-of-bounds filter offsets' ' +test_expect_success PERL_TEST_HELPERS 'Bloom reader notices out-of-bounds filter offsets' ' check_corrupt_graph BIDX 12 FFFFFFFF && # use grep to avoid depending on exact chunk size grep "warning: ignoring out-of-range offset (4294967295) for changed-path filter at pos 3 of .git/objects/info/commit-graph" err ' -test_expect_success 'Bloom reader notices too-small index chunk' ' +test_expect_success PERL_TEST_HELPERS 'Bloom reader notices too-small index chunk' ' # replace the index with a single entry, making most # lookups out-of-bounds check_corrupt_graph BIDX clear 00000000 && @@ -760,7 +760,7 @@ test_expect_success 'Bloom reader notices too-small index chunk' ' test_cmp expect.err err ' -test_expect_success 'Bloom reader notices out-of-order index offsets' ' +test_expect_success PERL_TEST_HELPERS 'Bloom reader notices out-of-order index offsets' ' # we do not know any real offsets, but we can pick # something plausible; we should not get to the point of # actually reading from the bogus offsets anyway. 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/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 50344e17ca..5174995191 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -4,6 +4,12 @@ test_description='test corner cases of git-archive' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping archive corner cases tests; Perl not available' + test_done +fi + # the 10knuls.tar file is used to test for an empty git generated tar # without having to invoke tar because an otherwise valid empty GNU tar # will be considered broken by {Open,Net}BSD tar diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 5ac8d39094..a5932b6a8b 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -9,9 +9,9 @@ test_description='git pack-object' test_expect_success 'setup' ' rm -f .git/index* && - perl -e "print \"a\" x 4096;" >a && - perl -e "print \"b\" x 4096;" >b && - perl -e "print \"c\" x 4096;" >c && + test-tool genzeros 4096 | tr "\000" "a" >a && + test-tool genzeros 4096 | tr "\000" "b" >b && + test-tool genzeros 4096 | tr "\000" "c" >c && test-tool genrandom "seed a" 2097152 >a_big && test-tool genrandom "seed b" 2097152 >b_big && git update-index --add a a_big b b_big c && @@ -140,7 +140,7 @@ test_expect_success 'pack-object <stdin parsing: --stdin-packs handles garbage' # usage: check_deltas <stderr_from_pack_objects> <cmp_op> <nr_deltas> # e.g.: check_deltas stderr -gt 0 check_deltas() { - deltas=$(perl -lne '/delta (\d+)/ and print $1' "$1") && + deltas=$(sed -n 's/Total [0-9][0-9]* (delta \([0-9][0-9]*\)).*/\1/p' "$1") && shift && if ! test "$deltas" "$@" then @@ -215,7 +215,7 @@ test_expect_success 'unpack with OFS_DELTA (core.fsyncmethod=batch)' ' check_unpack test-3-${packname_3} obj-list "$BATCH_CONFIGURATION" ' -test_expect_success 'compare delta flavors' ' +test_expect_success PERL_TEST_HELPERS 'compare delta flavors' ' perl -e '\'' defined($_ = -s $_) or die for @ARGV; exit 1 if $ARGV[0] <= $ARGV[1]; diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index de58ca654a..ab99c8b685 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -103,7 +103,8 @@ test_expect_success 'create corruption in data of first object' ' create_new_pack && git prune-packed && chmod +w ${pack}.pack && - perl -i.bak -pe "s/ base /abcdef/" ${pack}.pack && + sed "s/ base /abcdef/" ${pack}.pack >${pack}.pack.munged && + mv ${pack}.pack.munged ${pack}.pack && test_must_fail git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null @@ -160,7 +161,8 @@ test_expect_success 'create corruption in data of first delta' ' create_new_pack && git prune-packed && chmod +w ${pack}.pack && - perl -i.bak -pe "s/ delta1 /abcdefgh/" ${pack}.pack && + sed "s/ delta1 /abcdefgh/" ${pack}.pack >${pack}.pack.munged && + mv ${pack}.pack.munged ${pack}.pack && git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 621bbbdd26..a62b463eaf 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -421,7 +421,7 @@ test_bitmap_cases () { # mark the commits which did not receive bitmaps as preferred, # and generate the bitmap again - perl -pe "s{^}{create refs/tags/include/$. }" <before | + sed "s|\(.*\)|create refs/tags/include/\1 \1|" before | git update-ref --stdin && git -c pack.preferBitmapTips=refs/tags/include repack -adb && diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh index 32cf422745..defaa06d65 100755 --- a/t/t5316-pack-delta-depth.sh +++ b/t/t5316-pack-delta-depth.sh @@ -76,11 +76,11 @@ test_expect_success 'create series of packs' ' max_chain() { git index-pack --verify-stat-only "$1" >output && - perl -lne ' - BEGIN { $len = 0 } - /chain length = (\d+)/ and $len = $1; - END { print $len } - ' output + awk ' + BEGIN { len=0 } + /chain length = [0-9]+:/{ len=$4 } + END { print len } + ' <output | tr -d ':' } # Note that this whole setup is pretty reliant on the current diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index f68f64cd85..0b3404f58f 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -837,7 +837,7 @@ check_corrupt_chunk () { test_cmp expect.out out } -test_expect_success 'reader notices too-small oid fanout chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small oid fanout chunk' ' # make it big enough that the graph file is plausible, # otherwise we hit an earlier check check_corrupt_chunk OIDF clear $(printf "000000%02x" $(test_seq 250)) && @@ -848,7 +848,7 @@ test_expect_success 'reader notices too-small oid fanout chunk' ' test_cmp expect.err err ' -test_expect_success 'reader notices fanout/lookup table mismatch' ' +test_expect_success PERL_TEST_HELPERS 'reader notices fanout/lookup table mismatch' ' check_corrupt_chunk OIDF 1020 "FFFFFFFF" && cat >expect.err <<-\EOF && error: commit-graph OID lookup chunk is the wrong size @@ -857,7 +857,7 @@ test_expect_success 'reader notices fanout/lookup table mismatch' ' test_cmp expect.err err ' -test_expect_success 'reader notices out-of-bounds fanout' ' +test_expect_success PERL_TEST_HELPERS 'reader notices out-of-bounds fanout' ' # Rather than try to corrupt a specific hash, we will just # wreck them all. But we cannot just set them all to 0xFFFFFFFF or # similar, as they are used for hi/lo starts in a binary search (so if @@ -873,7 +873,7 @@ test_expect_success 'reader notices out-of-bounds fanout' ' test_cmp expect.err err ' -test_expect_success 'reader notices too-small commit data chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small commit data chunk' ' check_corrupt_chunk CDAT clear 00000000 && cat >expect.err <<-\EOF && error: commit-graph commit data chunk is wrong size @@ -882,7 +882,7 @@ test_expect_success 'reader notices too-small commit data chunk' ' test_cmp expect.err err ' -test_expect_success 'reader notices out-of-bounds extra edge' ' +test_expect_success PERL_TEST_HELPERS 'reader notices out-of-bounds extra edge' ' check_corrupt_chunk EDGE clear && cat >expect.err <<-\EOF && error: commit-graph extra-edges pointer out of bounds @@ -890,7 +890,7 @@ test_expect_success 'reader notices out-of-bounds extra edge' ' test_cmp expect.err err ' -test_expect_success 'reader notices too-small generations chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small generations chunk' ' check_corrupt_chunk GDA2 clear 00000000 && cat >expect.err <<-\EOF && error: commit-graph generations chunk is wrong size diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 0f215ad2e8..bd75dea950 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -1120,7 +1120,7 @@ corrupt_chunk () { corrupt_chunk_file $midx "$@" } -test_expect_success 'reader notices too-small oid fanout chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small oid fanout chunk' ' corrupt_chunk OIDF clear 00000000 && test_must_fail git log 2>err && cat >expect <<-\EOF && @@ -1130,7 +1130,7 @@ test_expect_success 'reader notices too-small oid fanout chunk' ' test_cmp expect err ' -test_expect_success 'reader notices too-small oid lookup chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small oid lookup chunk' ' corrupt_chunk OIDL clear 00000000 && test_must_fail git log 2>err && cat >expect <<-\EOF && @@ -1140,7 +1140,7 @@ test_expect_success 'reader notices too-small oid lookup chunk' ' test_cmp expect err ' -test_expect_success 'reader notices too-small pack names chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small pack names chunk' ' # There is no NUL to terminate the name here, so the # chunk is too short. corrupt_chunk PNAM clear 70656666 && @@ -1151,7 +1151,7 @@ test_expect_success 'reader notices too-small pack names chunk' ' test_cmp expect err ' -test_expect_success 'reader handles unaligned chunks' ' +test_expect_success PERL_TEST_HELPERS 'reader handles unaligned chunks' ' # A 9-byte PNAM means all of the subsequent chunks # will no longer be 4-byte aligned, but it is still # a valid one-pack chunk on its own (it is "foo.pack\0"). @@ -1165,7 +1165,7 @@ test_expect_success 'reader handles unaligned chunks' ' test_cmp expect.err err ' -test_expect_success 'reader notices too-small object offset chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small object offset chunk' ' corrupt_chunk OOFF clear 00000000 && test_must_fail git log 2>err && cat >expect <<-\EOF && @@ -1175,7 +1175,7 @@ test_expect_success 'reader notices too-small object offset chunk' ' test_cmp expect err ' -test_expect_success 'reader bounds-checks large offset table' ' +test_expect_success PERL_TEST_HELPERS 'reader bounds-checks large offset table' ' # re-use the objects64 dir here to cheaply get access to a midx # with large offsets. git init repo && @@ -1197,7 +1197,7 @@ test_expect_success 'reader bounds-checks large offset table' ' ) ' -test_expect_success 'reader notices too-small revindex chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader notices too-small revindex chunk' ' # We only get a revindex with bitmaps (and likewise only # load it when they are asked for). test_config repack.writeBitmaps true && @@ -1214,7 +1214,7 @@ test_expect_success 'reader notices too-small revindex chunk' ' test_cmp expect.err err ' -test_expect_success 'reader notices out-of-bounds fanout' ' +test_expect_success PERL_TEST_HELPERS 'reader notices out-of-bounds fanout' ' # This is similar to the out-of-bounds fanout test in t5318. The values # in adjacent entries should be large but not identical (they # are used as hi/lo starts for a binary search, which would then abort diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh index a32be3867d..49a057cc2e 100755 --- a/t/t5324-split-commit-graph.sh +++ b/t/t5324-split-commit-graph.sh @@ -401,7 +401,7 @@ test_expect_success 'verify across alternates' ' ) ' -test_expect_success 'reader bounds-checks base-graph chunk' ' +test_expect_success PERL_TEST_HELPERS 'reader bounds-checks base-graph chunk' ' git clone --no-hardlinks . corrupt-base-chunk && ( cd corrupt-base-chunk && diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index d27557b9b0..892aeb09e4 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -176,8 +176,8 @@ test_midx_bitmap_cases () { comm -13 bitmaps commits >before && test_line_count = 1 before && - perl -ne "printf(\"create refs/tags/include/%d \", $.); print" \ - <before | git update-ref --stdin && + sed "s|\(.*\)|create refs/tags/include/\1 \1|" before | + git update-ref --stdin && rm -fr $midx-$(midx_checksum $objdir).bitmap && rm -fr $midx && diff --git a/t/t5328-commit-graph-64bit-time.sh b/t/t5328-commit-graph-64bit-time.sh index a766a3e3f8..d8891e6a92 100755 --- a/t/t5328-commit-graph-64bit-time.sh +++ b/t/t5328-commit-graph-64bit-time.sh @@ -74,7 +74,7 @@ test_expect_success 'single commit with generation data exceeding UINT32_MAX' ' git -C repo-uint32-max commit-graph verify ' -test_expect_success 'reader notices out-of-bounds generation overflow' ' +test_expect_success PERL_TEST_HELPERS 'reader notices out-of-bounds generation overflow' ' graph=.git/objects/info/commit-graph && test_when_finished "rm -rf $graph" && git commit-graph write --reachable && diff --git a/t/t5333-pseudo-merge-bitmaps.sh b/t/t5333-pseudo-merge-bitmaps.sh index 3905cb6e4f..56674db562 100755 --- a/t/t5333-pseudo-merge-bitmaps.sh +++ b/t/t5333-pseudo-merge-bitmaps.sh @@ -28,9 +28,8 @@ test_pseudo_merges_reused () { tag_everything () { git rev-list --all --no-object-names >in && - perl -lne ' - print "create refs/tags/" . $. . " " . $1 if /([0-9a-f]+)/ - ' <in | git update-ref --stdin + sed 's|\(.*\)|create refs/tags/\1 \1|' in | + git update-ref --stdin } test_expect_success 'setup' ' @@ -102,7 +101,7 @@ test_expect_success 'stale bitmap traversal with pseudo-merges' ' test_cmp expect actual ' -test_expect_success 'bitmapPseudoMerge.sampleRate adjusts commit selection rate' ' +test_expect_success PERL_TEST_HELPERS 'bitmapPseudoMerge.sampleRate adjusts commit selection rate' ' test_config bitmapPseudoMerge.test.pattern "refs/tags/" && test_config bitmapPseudoMerge.test.maxMerges 1 && test_config bitmapPseudoMerge.test.stableThreshold never && @@ -235,8 +234,7 @@ test_expect_success 'pseudo-merge pattern with capture groups' ' test_commit_bulk 16 && git rev-list HEAD~16.. >in && - - perl -lne "print \"create refs/remotes/$r/tags/\$. \$_\"" <in | + sed "s|\(.*\)|create refs/remotes/$r/tags/\1 \1" in | git update-ref --stdin || return 1 done && @@ -252,7 +250,7 @@ test_expect_success 'pseudo-merge pattern with capture groups' ' do test_pseudo_merge_commits $m >oids && grep -f oids refs | - perl -lne "print \$1 if /refs\/remotes\/([0-9]+)/" | + sed -n "s|refs/remotes/\([0-9][0-9]*\)/|\1|p" && sort -u || return 1 done >remotes && diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8f018d2f23..83b42ff073 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -275,7 +275,7 @@ extract_ref_advertisement () { ' } -test_expect_success 'receive-pack de-dupes .have lines' ' +test_expect_success PERL_TEST_HELPERS 'receive-pack de-dupes .have lines' ' git init shared && git -C shared commit --allow-empty -m both && git clone -s shared fork && diff --git a/t/t5410-receive-pack-alternates.sh b/t/t5410-receive-pack-alternates.sh index 0b28e4e452..4e82fd102e 100755 --- a/t/t5410-receive-pack-alternates.sh +++ b/t/t5410-receive-pack-alternates.sh @@ -17,7 +17,7 @@ test_expect_success 'setup' ' ' extract_haves () { - depacketize | perl -lne '/^(\S+) \.have/ and print $1' + depacketize | sed -n 's/^\([^ ][^ ]*\) \.have/\1/p' } test_expect_success 'with core.alternateRefsCommand' ' diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 845ca43ea0..febe441041 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -7,6 +7,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping tagfollow tests; Perl not available' + test_done +fi + # End state of the repository: # # T - tag1 S - tag2 diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 58074506c5..438250c75e 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -359,7 +359,7 @@ test_expect_success \ grep "Cannot demote unterminatedheader" act ' -test_expect_success 'badFilemode is not a strict error' ' +test_expect_success PERL_TEST_HELPERS 'badFilemode is not a strict error' ' git init --bare badmode.git && tree=$( cd badmode.git && 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 5f350facf5..ebc696546b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -8,6 +8,12 @@ test_description='Per branch config variables affects "git fetch". . ./test-lib.sh . "$TEST_DIRECTORY"/lib-bundle.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping fetch tests; Perl not available' + test_done +fi + D=$(pwd) test_expect_success setup ' @@ -119,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 ) ' @@ -250,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 && @@ -537,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/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh index 3755822629..95d0f33b29 100755 --- a/t/t5532-fetch-proxy.sh +++ b/t/t5532-fetch-proxy.sh @@ -4,6 +4,12 @@ test_description='fetching via git:// using core.gitproxy' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping fetch proxy tests; Perl not available' + test_done +fi + test_expect_success 'setup remote repo' ' git init remote && (cd remote && diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index c91a62b77a..2a782214ee 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -205,7 +205,7 @@ test_expect_success GPG 'inconsistent push options in signed push not allowed' ' # Tweak the push output to make the push option outside the cert # different, then replay it on a fresh dst, checking that ff is not # deleted. - perl -pe "s/([^ ])bar/\$1baz/" push >push.tweak && + sed "s/\([^ ]\)bar/\1baz/" push >push.tweak && prepare_dst && git -C dst config receive.certnonceseed sekrit && git -C dst config receive.advertisepushoptions 1 && diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 37f7547a4c..6588ce6226 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -271,22 +271,21 @@ test_expect_success 'shallow fetches check connectivity before writing shallow f git -C "$REPO" config protocol.version 2 && git -C client config protocol.version 2 && - git -C client fetch --depth=2 "$HTTPD_URL/one_time_perl/repo" main:a_branch && + git -C client fetch --depth=2 "$HTTPD_URL/one_time_script/repo" main:a_branch && # Craft a situation in which the server sends back an unshallow request # with an empty packfile. This is done by refetching with a shorter # depth (to ensure that the packfile is empty), and overwriting the # shallow line in the response with the unshallow line we want. - printf "$(test_oid perl)" \ - "$(git -C "$REPO" rev-parse HEAD)" \ - "$(git -C "$REPO" rev-parse HEAD^)" \ - >"$HTTPD_ROOT_PATH/one-time-perl" && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-EOF && + sed "$(printf "$(test_oid perl)" "$(git -C "$REPO" rev-parse HEAD)" "$(git -C "$REPO" rev-parse HEAD^)")" "\$1" + EOF test_must_fail env GIT_TEST_SIDEBAND_ALL=0 git -C client \ - fetch --depth=1 "$HTTPD_URL/one_time_perl/repo" \ + fetch --depth=1 "$HTTPD_URL/one_time_script/repo" \ main:a_branch && - # Ensure that the one-time-perl script was used. - ! test -e "$HTTPD_ROOT_PATH/one-time-perl" && + # Ensure that the one-time-script script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-script" && # Ensure that the resulting repo is consistent, despite our failure to # fetch. diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 761fdfcfe6..b0d4ea7801 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -7,6 +7,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh + +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping http fetch smart tests; Perl not available' + test_done +fi + test "$HTTP_PROTO" = "HTTP/2" && enable_http2 start_httpd diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index f3b158274c..b6ee06f5c8 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -4,6 +4,12 @@ test_description='test git-http-backend respects CONTENT_LENGTH' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping http backend content tests; Perl not available' + test_done +fi + test_lazy_prereq GZIP 'gzip --version' verify_http_result() { 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/t5601-clone.sh b/t/t5601-clone.sh index d0c18660e3..d743d986c4 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -649,7 +649,7 @@ test_expect_success 'GIT_TRACE_PACKFILE produces a usable pack' ' git -C replay.git index-pack -v --stdin <tmp.pack ' -test_expect_success 'clone on case-insensitive fs' ' +test_expect_success PERL_TEST_HELPERS 'clone on case-insensitive fs' ' git init icasefs && ( cd icasefs && @@ -662,7 +662,7 @@ test_expect_success 'clone on case-insensitive fs' ' ) ' -test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' ' +test_expect_success PERL_TEST_HELPERS,CASE_INSENSITIVE_FS 'colliding file detection' ' grep X icasefs/warning && grep x icasefs/warning && test_grep "the following paths have collided" icasefs/warning 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/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 4650451964..1e354e057f 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -737,18 +737,22 @@ intersperse () { sed 's/\(..\)/'$1'\1/g' } -# Create a one-time-perl command to replace the existing packfile with $1. +# Create a one-time-script command to replace the existing packfile with $1. replace_packfile () { - # The protocol requires that the packfile be sent in sideband 1, hence - # the extra \x01 byte at the beginning. - cp $1 "$HTTPD_ROOT_PATH/one-time-pack" && - echo 'if (/packfile/) { - print; - my $length = -s "one-time-pack"; - printf "%04x\x01", $length + 5; - print `cat one-time-pack` . "0000"; - last - }' >"$HTTPD_ROOT_PATH/one-time-perl" + cp "$1" one-time-pack && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-EOF + if grep packfile "\$1" >/dev/null + then + sed '/packfile/q' "\$1" && + # The protocol requires that the packfile be sent in sideband + # 1, hence the extra \001 byte at the beginning. + printf "%04x\001" \$((\$(wc -c <"$PWD/one-time-pack") + 5)) && + cat "$PWD/one-time-pack" && + printf "0000" + else + cat "\$1" + fi + EOF } test_expect_success 'upon cloning, check that all refs point to objects' ' @@ -776,12 +780,12 @@ test_expect_success 'upon cloning, check that all refs point to objects' ' # section header. test_config -C "$SERVER" protocol.version 2 && test_must_fail git -c protocol.version=2 clone \ - --filter=blob:none $HTTPD_URL/one_time_perl/server repo 2>err && + --filter=blob:none $HTTPD_URL/one_time_script/server repo 2>err && test_grep "did not send all necessary objects" err && - # Ensure that the one-time-perl script was used. - ! test -e "$HTTPD_ROOT_PATH/one-time-perl" + # Ensure that the one-time-script script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-script" ' test_expect_success 'when partial cloning, tolerate server not sending target of tag' ' @@ -818,14 +822,14 @@ test_expect_success 'when partial cloning, tolerate server not sending target of # Exercise to make sure it works. git -c protocol.version=2 clone \ - --filter=blob:none $HTTPD_URL/one_time_perl/server repo 2> err && + --filter=blob:none $HTTPD_URL/one_time_script/server repo 2> err && ! grep "missing object referenced by" err && - # Ensure that the one-time-perl script was used. - ! test -e "$HTTPD_ROOT_PATH/one-time-perl" + # Ensure that the one-time-script script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-script" ' -test_expect_success 'tolerate server sending REF_DELTA against missing promisor objects' ' +test_expect_success PERL_TEST_HELPERS 'tolerate server sending REF_DELTA against missing promisor objects' ' SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && rm -rf "$SERVER" repo && test_create_repo "$SERVER" && @@ -845,7 +849,7 @@ test_expect_success 'tolerate server sending REF_DELTA against missing promisor # Clone. The client has deltabase_have but not deltabase_missing. git -c protocol.version=2 clone --no-checkout \ - --filter=blob:none $HTTPD_URL/one_time_perl/server repo && + --filter=blob:none $HTTPD_URL/one_time_script/server repo && git -C repo hash-object -w -- "$SERVER/have.txt" && # Sanity check to ensure that the client does not have @@ -899,8 +903,8 @@ test_expect_success 'tolerate server sending REF_DELTA against missing promisor grep "want $(cat deltabase_missing)" trace && ! grep "want $(cat deltabase_have)" trace && - # Ensure that the one-time-perl script was used. - ! test -e "$HTTPD_ROOT_PATH/one-time-perl" + # Ensure that the one-time-script script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-script" ' # DO NOT add non-httpd-specific tests here, because the last part of this diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 678a346ed0..d4c28bae39 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -228,7 +228,10 @@ test_expect_success 'ignore very large set of prefixes' ' echo command=ls-refs && echo object-format=$(test_oid algo) && echo 0001 && - perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" && + awk "{ + for (i = 1; i <= 65536; i++) + print \"ref-prefix refs/heads/\", \$i + }" && echo 0000 } | test-tool pkt-line pack >in && diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 4d0cbe9872..f826ac46a5 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -1174,11 +1174,12 @@ test_expect_success 'when server sends "ready", expect DELIM' ' # After "ready" in the acknowledgments section, pretend that a FLUSH # (0000) was sent instead of a DELIM (0001). - printf "\$ready = 1 if /ready/; \$ready && s/0001/0000/" \ - >"$HTTPD_ROOT_PATH/one-time-perl" && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-\EOF && + sed "/ready/{n;s/0001/0000/;}" "$1" + EOF test_must_fail git -C http_child -c protocol.version=2 \ - fetch "$HTTPD_URL/one_time_perl/http_parent" 2> err && + fetch "$HTTPD_URL/one_time_script/http_parent" 2> err && test_grep "expected packfile to be sent after .ready." err ' @@ -1199,12 +1200,13 @@ test_expect_success 'when server does not send "ready", expect FLUSH' ' # After the acknowledgments section, pretend that a DELIM # (0001) was sent instead of a FLUSH (0000). - printf "\$ack = 1 if /acknowledgments/; \$ack && s/0000/0001/" \ - >"$HTTPD_ROOT_PATH/one-time-perl" && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-\EOF && + sed "/acknowledgments/,//{s/0000/0001/;}" "$1" + EOF test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" git -C http_child \ -c protocol.version=2 \ - fetch "$HTTPD_URL/one_time_perl/http_parent" 2> err && + fetch "$HTTPD_URL/one_time_script/http_parent" 2> err && grep "fetch< .*acknowledgments" log && ! grep "fetch< .*ready" log && test_grep "expected no other sections to be sent after no .ready." err @@ -1490,12 +1492,13 @@ test_expect_success 'http:// --negotiate-only' ' test_expect_success 'http:// --negotiate-only without wait-for-done support' ' SERVER="server" && - URI="$HTTPD_URL/one_time_perl/server" && + URI="$HTTPD_URL/one_time_script/server" && setup_negotiate_only "$SERVER" "$URI" && - echo "s/ wait-for-done/ xxxx-xxx-xxxx/" \ - >"$HTTPD_ROOT_PATH/one-time-perl" && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-\EOF && + sed "s/ wait-for-done/ xxxx-xxx-xxxx/" "$1" + EOF test_must_fail git -c protocol.version=2 -C client fetch \ --no-tags \ diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 191097171b..249137b467 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -83,18 +83,15 @@ test_expect_success 'setup repository' ' test_expect_success 'config controls ref-in-want advertisement' ' test-tool serve-v2 --advertise-capabilities >out && - perl -ne "/ref-in-want/ and print" out >out.filter && - test_must_be_empty out.filter && + test_grep ! "ref-in-want" out && git config uploadpack.allowRefInWant false && test-tool serve-v2 --advertise-capabilities >out && - perl -ne "/ref-in-want/ and print" out >out.filter && - test_must_be_empty out.filter && + test_grep ! "ref-in-want" out && git config uploadpack.allowRefInWant true && test-tool serve-v2 --advertise-capabilities >out && - perl -ne "/ref-in-want/ and print" out >out.filter && - test_file_not_empty out.filter + test_grep "ref-in-want" out ' test_expect_success 'invalid want-ref line' ' @@ -462,7 +459,7 @@ test_expect_success 'setup repos for change-while-negotiating test' ' test_commit m3 && git tag -d m2 m3 ) && - git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_perl/repo" && + git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_script/repo" && git -C "$LOCAL_PRISTINE" config protocol.version 2 ' @@ -475,10 +472,12 @@ inconsistency () { # RPCs during a single negotiation. oid1=$(git -C "$REPO" rev-parse $1) && oid2=$(git -C "$REPO" rev-parse $2) && - echo "s/$oid1/$oid2/" >"$HTTPD_ROOT_PATH/one-time-perl" + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-EOF + sed "s/$oid1/$oid2/" "\$1" + EOF } -test_expect_success 'server is initially ahead - no ref in want' ' +test_expect_success PERL_TEST_HELPERS 'server is initially ahead - no ref in want' ' git -C "$REPO" config uploadpack.allowRefInWant false && rm -rf local && cp -r "$LOCAL_PRISTINE" local && @@ -487,7 +486,7 @@ test_expect_success 'server is initially ahead - no ref in want' ' test_grep "fatal: remote error: upload-pack: not our ref" err ' -test_expect_success 'server is initially ahead - ref in want' ' +test_expect_success PERL_TEST_HELPERS 'server is initially ahead - ref in want' ' git -C "$REPO" config uploadpack.allowRefInWant true && rm -rf local && cp -r "$LOCAL_PRISTINE" local && @@ -499,7 +498,7 @@ test_expect_success 'server is initially ahead - ref in want' ' test_cmp expected actual ' -test_expect_success 'server is initially behind - no ref in want' ' +test_expect_success PERL_TEST_HELPERS 'server is initially behind - no ref in want' ' git -C "$REPO" config uploadpack.allowRefInWant false && rm -rf local && cp -r "$LOCAL_PRISTINE" local && @@ -511,7 +510,7 @@ test_expect_success 'server is initially behind - no ref in want' ' test_cmp expected actual ' -test_expect_success 'server is initially behind - ref in want' ' +test_expect_success PERL_TEST_HELPERS 'server is initially behind - ref in want' ' git -C "$REPO" config uploadpack.allowRefInWant true && rm -rf local && cp -r "$LOCAL_PRISTINE" local && @@ -523,11 +522,13 @@ test_expect_success 'server is initially behind - ref in want' ' test_cmp expected actual ' -test_expect_success 'server loses a ref - ref in want' ' +test_expect_success PERL_TEST_HELPERS 'server loses a ref - ref in want' ' git -C "$REPO" config uploadpack.allowRefInWant true && rm -rf local && cp -r "$LOCAL_PRISTINE" local && - echo "s/main/rain/" >"$HTTPD_ROOT_PATH/one-time-perl" && + write_script "$HTTPD_ROOT_PATH/one-time-script" <<-\EOF && + sed "s/main/rain/" "$1" + EOF test_must_fail git -C local fetch 2>err && test_grep "fatal: remote error: unknown ref refs/heads/rain" err diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index b35b774235..cb061b1f35 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh @@ -4,6 +4,12 @@ test_description='handling of promisor remote advertisement' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping promisor remote capabilities tests; Perl not available' + test_done +fi + GIT_TEST_MULTI_PACK_INDEX=0 GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0 diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 6289a2e8b0..33881274a4 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -182,4 +182,55 @@ test_expect_success 'rev-list --unpacked' ' test_cmp expect 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/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh index bad02cf5b8..b6f3344dbf 100755 --- a/t/t6011-rev-list-with-bad-commit.sh +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -35,11 +35,15 @@ test_expect_success 'verify number of revisions' \ first_commit=$(git rev-parse HEAD~3) ' -test_expect_success 'corrupt second commit object' \ - ' - perl -i.bak -pe "s/second commit/socond commit/" .git/objects/pack/*.pack && - test_must_fail git fsck --full - ' +test_expect_success 'corrupt second commit object' ' + for p in .git/objects/pack/*.pack + do + sed "s/second commit/socond commit/" "$p" >"$p.munged" && + mv "$p.munged" "$p" || + return 1 + done && + test_must_fail git fsck --full +' test_expect_success 'rev-list should fail' ' test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git -c core.commitGraph=false rev-list --all > /dev/null diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh index 39793cbbd6..273196f52b 100755 --- a/t/t6013-rev-list-reverse-parents.sh +++ b/t/t6013-rev-list-reverse-parents.sh @@ -26,17 +26,19 @@ test_expect_success 'set up --reverse example' ' commit five ' +reverse () { + awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' +} + test_expect_success '--reverse --parents --full-history combines correctly' ' - git rev-list --parents --full-history main -- foo | - perl -e "print reverse <>" > expected && + git rev-list --parents --full-history main -- foo | reverse >expected && git rev-list --reverse --parents --full-history main -- foo \ > actual && test_cmp expected actual ' test_expect_success '--boundary does too' ' - git rev-list --boundary --parents --full-history main ^root -- foo | - perl -e "print reverse <>" > expected && + git rev-list --boundary --parents --full-history main ^root -- foo | reverse >expected && git rev-list --boundary --reverse --parents --full-history \ main ^root -- foo > actual && test_cmp expected actual 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/t6102-rev-list-unexpected-objects.sh b/t/t6102-rev-list-unexpected-objects.sh index 22dfd6d978..eb98b3919c 100755 --- a/t/t6102-rev-list-unexpected-objects.sh +++ b/t/t6102-rev-list-unexpected-objects.sh @@ -4,6 +4,12 @@ test_description='git rev-list should handle unexpected object types' . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping rev-list unexpected objects tests; Perl not available' + test_done +fi + test_expect_success 'setup well-formed objects' ' blob="$(printf "foo" | git hash-object -w --stdin)" && tree="$(printf "100644 blob $blob\tfoo" | git mktree)" && diff --git a/t/t6115-rev-list-du.sh b/t/t6115-rev-list-du.sh index 3385fe9f13..04c577dad6 100755 --- a/t/t6115-rev-list-du.sh +++ b/t/t6115-rev-list-du.sh @@ -22,7 +22,7 @@ test_expect_success 'set up repository' ' disk_usage_slow () { git rev-list --no-object-names "$@" | git cat-file --batch-check="%(objectsize:disk)" | - perl -lne '$total += $_; END { print $total}' + awk '{ i += $1 } END { print i }' } # check behavior with given rev-list options; note that diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 9b4f4306c4..ce9af79ab1 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -1216,7 +1216,7 @@ test_expect_success '%(raw) with --tcl must fail' ' test_must_fail git for-each-ref --format="%(raw)" --tcl ' -test_expect_success '%(raw) with --perl' ' +test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' ' git for-each-ref --format="\$name= %(raw); print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual && cmp blob1 actual && @@ -1443,9 +1443,14 @@ test_expect_success 'set up trailers for next test' ' ' test_trailer_option () { + if test "$#" -eq 3 + then + prereq="$1" + shift + fi && title=$1 option=$2 cat >expect - test_expect_success "$title" ' + test_expect_success $prereq "$title" ' git for-each-ref --format="%($option)" refs/heads/main >actual && test_cmp expect actual && git for-each-ref --format="%(contents:$option)" refs/heads/main >actual && @@ -1453,7 +1458,7 @@ test_trailer_option () { ' } -test_trailer_option '%(trailers:unfold) unfolds trailers' \ +test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \ 'trailers:unfold' <<-EOF $(unfold <trailers) @@ -1483,13 +1488,13 @@ test_trailer_option '%(trailers:only=no) shows all trailers' \ EOF -test_trailer_option '%(trailers:only) and %(trailers:unfold) work together' \ +test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \ 'trailers:only,unfold' <<-EOF $(grep -v patch.description <trailers | unfold) EOF -test_trailer_option '%(trailers:unfold) and %(trailers:only) work together' \ +test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \ 'trailers:unfold,only' <<-EOF $(grep -v patch.description <trailers | unfold) 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/t7006-pager.sh b/t/t7006-pager.sh index 932c26cb45..9717e825f0 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -662,9 +662,9 @@ test_expect_success 'setup trace2' ' ' test_expect_success 'setup large log output' ' - perl -e " - print \"this is a long commit message\" x 50000 - " >commit-msg && + test-tool genzeros 50000 | + tr "\000" "a" | + sed "s/a/this is a long commit message/g" >commit-msg && git commit --allow-empty -F commit-msg ' 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/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh index 0c605fd271..3d944a00e0 100755 --- a/t/t7416-submodule-dash-url.sh +++ b/t/t7416-submodule-dash-url.sh @@ -33,7 +33,8 @@ test_expect_success 'fsck accepts protected dash' ' ' test_expect_success 'remove ./ protection from .gitmodules url' ' - perl -i -pe "s{\./}{}" .gitmodules && + sed "s|\./||" .gitmodules >.gitmodules.munged && + mv .gitmodules.munged .gitmodules && git commit -am "drop protection" ' diff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh index cc12f99f11..a37509f004 100755 --- a/t/t7501-commit-basic-functionality.sh +++ b/t/t7501-commit-basic-functionality.sh @@ -46,7 +46,7 @@ test_expect_success 'paths and -a do not mix' ' test_must_fail git commit -m foo -a file ' -test_expect_success PERL 'can use paths with --interactive' ' +test_expect_success 'can use paths with --interactive' ' echo bong-o-bong >file && # 2: update, 1:st path, that is all, 7: quit test_write_lines 2 1 "" 7 | @@ -345,12 +345,12 @@ test_expect_success 'overriding author from command line' ' grep Rubber.Duck output ' -test_expect_success PERL 'interactive add' ' +test_expect_success 'interactive add' ' echo 7 | test_must_fail git commit --interactive >out && grep "What now" out ' -test_expect_success PERL "commit --interactive doesn't change index if editor aborts" ' +test_expect_success "commit --interactive doesn't change index if editor aborts" ' echo zoo >file && test_must_fail git diff --exit-code >diff1 && test_write_lines u "*" q | diff --git a/t/t7508-status.sh b/t/t7508-status.sh index b2070d4e39..cdc1d6fcc7 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1066,7 +1066,7 @@ test_expect_success 'status -s submodule summary (clean submodule)' ' test_expect_success 'status -z implies porcelain' ' git status --porcelain | - perl -pe "s/\012/\000/g" >expect && + tr "\012" "\000" >expect && git status -z >output && test_cmp expect output ' 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 90ebb64f46..3bd91da970 100755 --- a/t/t7815-grep-binary.sh +++ b/t/t7815-grep-binary.sh @@ -114,13 +114,10 @@ test_expect_success 'grep respects not-binary diff attribute' ' test_cmp expect actual ' -cat >nul_to_q_textconv <<'EOF' -#!/bin/sh -"$PERL_PATH" -pe 'y/\000/Q/' < "$1" -EOF -chmod +x nul_to_q_textconv - test_expect_success 'setup textconv filters' ' + write_script nul_to_q_textconv <<-\EOF && + tr "\000" "Q" <"$1" + EOF echo a diff=foo >.gitattributes && git config diff.foo.textconv "\"$(pwd)\""/nul_to_q_textconv ' 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/t8001-annotate.sh b/t/t8001-annotate.sh index d7167f5539..609845aeb1 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -7,6 +7,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping annotate tests; Perl not available' + test_done +fi + PROG='git annotate' . "$TEST_DIRECTORY"/annotate-tests.sh diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index e98993276a..7822947f02 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -7,6 +7,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping blame colors tests; Perl not available' + test_done +fi + PROG='git blame -c' . "$TEST_DIRECTORY"/annotate-tests.sh @@ -101,7 +107,7 @@ test_expect_success 'set up abbrev tests' ' expect=$1 && shift && echo $sha1 | cut -c 1-$expect >expect && git blame "$@" abbrev.t >actual && - perl -lne "/[0-9a-f]+/ and print \$&" <actual >actual.sha && + sed -n "s/^[\^]\{0,1\}\([0-9a-f][0-9a-f]*\).*/\1/p" actual >actual.sha && test_cmp expect actual.sha } ' diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh index 07a287ffd3..db1e2afb2c 100755 --- a/t/t8006-blame-textconv.sh +++ b/t/t8006-blame-textconv.sh @@ -11,7 +11,7 @@ find_blame() { cat >helper <<'EOF' #!/bin/sh grep -q '^bin: ' "$1" || { echo "E: $1 is not \"binary\" file" 1>&2; exit 1; } -"$PERL_PATH" -p -e 's/^bin: /converted: /' "$1" +sed 's/^bin: /converted: /' "$1" EOF chmod +x helper diff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh index c66494f5ba..388057245c 100755 --- a/t/t8011-blame-split-file.sh +++ b/t/t8011-blame-split-file.sh @@ -81,7 +81,7 @@ do git blame --root -C --$output combined >output ' - test_expect_success "$output output finds correct commits" ' + test_expect_success PERL_TEST_HELPERS "$output output finds correct commits" ' generate_expect >expect <<-\EOF && 5 base 1 modified @@ -93,7 +93,7 @@ do test_cmp expect actual ' - test_expect_success "$output output shows correct filenames" ' + test_expect_success PERL_TEST_HELPERS "$output output shows correct filenames" ' generate_expect >expect <<-\EOF && 11 one 11 two @@ -102,7 +102,7 @@ do test_cmp expect actual ' - test_expect_success "$output output shows correct previous pointer" ' + test_expect_success PERL_TEST_HELPERS "$output output shows correct previous pointer" ' generate_expect >expect <<-EOF && 5 NONE 1 $(git rev-parse modified^) one diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh index c3a5f6d01f..3d77352650 100755 --- a/t/t8012-blame-colors.sh +++ b/t/t8012-blame-colors.sh @@ -7,6 +7,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh +if ! test_have_prereq PERL_TEST_HELPERS +then + skip_all='skipping blame colors tests; Perl not available' + test_done +fi + PROG='git blame -c' . "$TEST_DIRECTORY"/annotate-tests.sh 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/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh index 067b15bad2..b57a362bb9 100755 --- a/t/t9137-git-svn-dcommit-clobber-series.sh +++ b/t/t9137-git-svn-dcommit-clobber-series.sh @@ -20,8 +20,8 @@ test_expect_success '(supposedly) non-conflicting change from SVN' ' test x"$(sed -n -e 61p < file)" = x61 && svn_cmd co "$svnrepo" tmp && (cd tmp && - perl -i.bak -p -e "s/^58$/5588/" file && - perl -i.bak -p -e "s/^61$/6611/" file && + sed -e "s/^58$/5588/" -e "s/^61$/6611/" file >file.munged && + mv file.munged file && poke file && test x"$(sed -n -e 58p < file)" = x5588 && test x"$(sed -n -e 61p < file)" = x6611 && @@ -40,8 +40,10 @@ test_expect_success 'some unrelated changes to git' " test_expect_success 'change file but in unrelated area' " test x\"\$(sed -n -e 4p < file)\" = x4 && test x\"\$(sed -n -e 7p < file)\" = x7 && - perl -i.bak -p -e 's/^4\$/4444/' file && - perl -i.bak -p -e 's/^7\$/7777/' file && + sed -e 's/^4\$/4444/' \ + -e 's/^7\$/7777/' \ + file >file.munged && + mv file.munged file && test x\"\$(sed -n -e 4p < file)\" = x4444 && test x\"\$(sed -n -e 7p < file)\" = x7777 && git commit -m '4 => 4444, 7 => 7777' file && diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 304bac5b1d..dda9e7c3e7 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -726,7 +726,7 @@ test_expect_success 'directory becomes symlink' ' (cd result && git show main:foo) ' -test_expect_success 'fast-export quotes pathnames' ' +test_expect_success PERL_TEST_HELPERS 'fast-export quotes pathnames' ' git init crazy-paths && test_config -C crazy-paths core.protectNTFS false && (cd crazy-paths && diff --git a/t/t9850-shell.sh b/t/t9850-shell.sh index 36566ace21..21c3af48bd 100755 --- a/t/t9850-shell.sh +++ b/t/t9850-shell.sh @@ -30,7 +30,7 @@ test_expect_success 'shell allows interactive command' ' ' test_expect_success 'shell complains of overlong commands' ' - perl -e "print \"a\" x 2**12 for (0..2**19)" | + test-tool genzeros | tr "\000" "a" | test_must_fail git shell 2>err && grep "too long" err ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 16eaaaf4c3..bee4a2ca34 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -88,15 +88,15 @@ test_decode_color () { } lf_to_nul () { - perl -pe 'y/\012/\000/' + tr '\012' '\000' } nul_to_q () { - perl -pe 'y/\000/Q/' + tr '\000' 'Q' } q_to_nul () { - perl -pe 'y/Q/\000/' + tr 'Q' '\000' } q_to_cr () { @@ -1645,17 +1645,7 @@ test_match_signal () { # Read up to "$1" bytes (or to EOF) from stdin and write them to stdout. test_copy_bytes () { - perl -e ' - my $len = $ARGV[1]; - while ($len > 0) { - my $s; - my $nread = sysread(STDIN, $s, $len); - die "cannot read: $!" unless defined($nread); - last unless $nread; - print $s; - $len -= $nread; - } - ' - "$1" + dd ibs=1 count="$1" 2>/dev/null } # run "$@" inside a non-git directory @@ -1994,7 +1984,7 @@ test_remote_https_urls() { # Print the destination of symlink(s) provided as arguments. Basically # the same as the readlink command, but it's not available everywhere. test_readlink () { - perl -le 'print readlink($_) for @ARGV' "$@" + test-tool path-utils readlink "$@" } # Set mtime to a fixed "magic" timestamp in mid February 2009, before we diff --git a/t/test-lib.sh b/t/test-lib.sh index fffbfb89ef..af722d383d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -499,24 +499,20 @@ EDITOR=: # /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets # deriving from the command substitution clustered with the other # ones. -unset VISUAL EMAIL LANGUAGE $("$PERL_PATH" -e ' - my @env = keys %ENV; - my $ok = join("|", qw( - TRACE - DEBUG - TEST - .*_TEST - PROVE - VALGRIND - UNZIP - PERF_ - CURL_VERBOSE - TRACE_CURL - BUILD_DIR - )); - my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); - print join("\n", @vars); -') +unset VISUAL EMAIL LANGUAGE $(env | sed -n \ + -e '/^GIT_TRACE/d' \ + -e '/^GIT_DEBUG/d' \ + -e '/^GIT_TEST/d' \ + -e '/^GIT_.*_TEST/d' \ + -e '/^GIT_PROVE/d' \ + -e '/^GIT_VALGRIND/d' \ + -e '/^GIT_UNZIP/d' \ + -e '/^GIT_PERF_/d' \ + -e '/^GIT_CURL_VERBOSE/d' \ + -e '/^GIT_TRACE_CURL/d' \ + -e '/^GIT_BUILD_DIR/d' \ + -e 's/^\(GIT_[^=]*\)=.*/\1/p' +) unset XDG_CACHE_HOME unset XDG_CONFIG_HOME unset GITPERLLIB @@ -544,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 @@ -1523,6 +1517,22 @@ then export LSAN_OPTIONS fi +if test -z "$PERL_PATH" +then + case "${GIT_TEST_CHAIN_LINT:-unset}" in + unset) + GIT_TEST_CHAIN_LINT=0 + ;; + 0) + # The user has explicitly disabled the chain linter, so we + # don't have anything to worry about. + ;; + *) + BAIL_OUT 'You need Perl for the chain linter' + ;; + esac +fi + if test "${GIT_TEST_CHAIN_LINT:-1}" != 0 && test "${GIT_TEST_EXT_CHAIN_LINT:-1}" != 0 then @@ -1694,6 +1704,7 @@ test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK test -n "$GIT_VALGRIND_ENABLED" && test_set_prereq VALGRIND +test -n "$PERL_PATH" && test_set_prereq PERL_TEST_HELPERS if test -z "$GIT_TEST_CHECK_CACHE_TREE" then 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-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(); -} 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/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/upload-pack.c b/upload-pack.c index 7498b45e2e..02ce633602 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -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 */ 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; 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..77712811ff 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -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; |
